Flutter系列教程之(9)——Flutter调用Android原生
- 电脑硬件
- 2025-09-18 20:15:02

目录
步骤说明
1.打开android文件夹
2.新建Activity
3.原生代码编写
4.Activity中注册插件
5.flutter中插件初始化和封装
6.flutter页面中使用插件
传参补充
代码参考
最近需要给一个Flutter项目加个apk完整性检测,需要去拿到当前安装apk的md5数值,由于Flutter中无法实现,需要调用原生Android代码才能实现,于是花了些时间研究了下插件的实现,特此记录
步骤说明 1.打开android文件夹flutter中有个ios和android的文件夹,分别对应的Android和Ios的原生代码
我们想要实现FLutter调用原生代码,在里面写原生代码即可
在android文件夹中,新建有个类,Android可以选择Java或者是Kotlin代码编写即可
android目录结构其实就是常见的Android项目目录
然后使用Android Studio打开,右键菜单,选择flutter -> Open Android module in Android Studio
之后可以看到已经像Android开发一样打开了一个项目(当然,这里你也可以自己使用Android Studio去选择那个android文件夹,将其当做项目打开即可)
2.新建Activity此Activity需要继承FlutterActivity,并重写configureFlutterEngine方法,在此方法中进行插件的初始化
public class MainActivity extends FlutterActivity { @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { //插件实例的注册... //这个是必写,别删除!! GeneratedPluginRegistrant.registerWith(flutterEngine); } }那么这里需要插件的实例,插件的实例怎么来呢?其实就是自己写个类,然后实现Flutter提供的FlutterPlugin接口
3.原生代码编写新建一个类,实现FlutterPlugin接口,创建一个MethodChannel对象,利用此对象的setMethodCallHandler方法设置方法处理回调,里面通过判断方法名来调我们原生写的方法
public class MyTestPlugin implements FlutterPlugin { @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { //可以利用binding对象获取到Android中需要的Context对象 //Context applicationContext = binding.getApplicationContext(); //设置channel名称,之后flutter中也要一样 MethodChannel channel = new MethodChannel(binding.getFlutterEngine().getDartExecutor(), "test-plugin"); //把当前的MethodCallHandler设置 channel.setMethodCallHandler(new MethodChannel.MethodCallHandler() { @Override public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { String method = call.method; if (method.equals("getText")) { //调用原生的方法,这里为了方便,我就把方法写在当前类了 String str = getText(); //将结果返回给flutter result.success(str); //这里也有error的方法,可以看情况使用 //result.error("code", "message", "detail"); } else { //Flutter传过来id方法名没有找到,就调此方法 result.notImplemented(); } } }); } private String getText() { return "hello world"; } @Override public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { } }如果你想要一个Application的context上下文对象,可以在onAttachedToEngine()方法中使用binding的getApplicationContext()方法获取,如下代码
Context applicationContext = binding.getApplicationContext();如果是想要获取当前Activity的context对象,可以让当前类实现ActivityAware接口,不过略显繁琐,一般用Application的context对象应该可以满足大部分要求了,看情况选择吧
private Context context; @Override public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { context = binding.getActivity(); } @Override public void onDetachedFromActivityForConfigChanges() { } @Override public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { } @Override public void onDetachedFromActivity() { context = null; } 4.Activity中注册插件之前在第二步中的Activity中,补上注册的代码
public class MainActivity extends FlutterActivity { @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { //插件实例的注册... flutterEngine.getPlugins().add(new MyTestPlugin()); GeneratedPluginRegistrant.registerWith(flutterEngine); } } 5.flutter中插件初始化和封装在flutter中创建一个文件,文件名和class名任意,只是用来声明和初始化上述的Java类
class Md5Plugin{ //注意,这里的名称需要和Android原生中定义的一样 static const MethodChannel _channel = MethodChannel("apk_md5"); static Future<String> getMd5() async{ //传递一个方法名,即调用Android的原生方法 return await _channel.invokeMethod("getMd5"); } }还记得之前写的方法名的判断吗?这里就是传一个方法名,之后就会触发回调,之后即可得到返回结果
PS:注意,调Android原生的方法都是异步操作!
6.flutter页面中使用插件 之后在对应的page文件对应代码处中调用即可 Md5Plugin.getMd5().then(value=>{ //相关操作 });如果想使用同步代码,可以这样写
var result = Md5Plugin.getMd5()PS:测试的时候注意,如果是改了原生层代码(Java或Kotlin),最好将项目重新运行,不要使用Flutter的热重载功能(除非你只动了flutter的代码)
传参补充上述的例子中,并没有涉及到传参,这里再补充讲解下我自己的研究使用
这里只讲Flutter如何给Android原生传参
FLutter中调用方法(即上述的第五步操作):
class Md5Plugin{ //注意,这里的名称需要和Android原生中定义的一样 static const MethodChannel _channel = MethodChannel("apk_md5"); static Future<String> getMd5() async{ //传字符串给Android var param = "hello"; //传递一个方法名,即调用Android的原生方法 //注意这里的第二个参数 return await _channel.invokeMethod("getMd5",param); } }Android中的接收(上述的第三步):
在判断方法名之后,即可通过对应的方法获取数据(需要类型转换)
public class MyTestPlugin implements FlutterPlugin { @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { ... //把当前的MethodCallHandler设置 channel.setMethodCallHandler(new MethodChannel.MethodCallHandler() { @Override public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { String method = call.method; if (method.equals("getText")) { //注意这里的获取数据(强转) String packageName = (String)call.arguments; 省略... } else { //Flutter传过来id方法名没有找到,就调此方法 result.notImplemented(); } } }); } ... }上述的代码只是传单个数据,如果是要穿多个数据要怎么办呢?
由于invokeMethod()方法里只支持传单个数据,所以我们需要传map或是json格式的数据给到Android原生
Flutter发送数据:
var param = {"myKey":"hello"} //传递一个方法名,即调用Android的原生方法 //注意这里的第二个参数 return await _channel.invokeMethod("getMd5",param);Android接收数据:
String packageName = call.argument("myKey");这里有点要注意,call中有个arguments属性和arguments()方法,如下图
flutter中传过来的数据是map或json的,就得用arguments()来获取参数据;否则就是使用arguments属性
当然,如果传过来的数据是map或json类型,call提供了一个方便快捷的方法,我们可以直接使用argument(key)来直接获取key对应的数值**(注意这里也需要类型强转,注意类型需要对应)**
最后这里给出Flutter与Java的对应的类型表:
Dart
Android
null
null
bool
java.lang.Boolean
int
java.lang.Integer
int, if 32 bits not enough
java.lang.Long
double
java.lang.Double
String
java.lang.String
Uint8List
byte[]
Int32List
int[]
Int64List
long[]
Float64List
double[]
List
java.util.ArrayList
Map
java.util.HashMap
代码参考 package com.example.taiji_lianjiang.checkplugin; import android.app.Activity; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.text.TextUtils; import com.example.taiji_lianjiang.BuildConfig; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import androidx.annotation.NonNull; import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.embedding.engine.plugins.activity.ActivityAware; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.plugin mon.MethodCall; import io.flutter.plugin mon.MethodChannel; public class ApkMd5CheckPlugin implements MethodChannel.MethodCallHandler, FlutterPlugin, ActivityAware { public static ApkMd5CheckPlugin getInstance() { return new ApkMd5CheckPlugin(); } private MethodChannel channel; private Activity context; @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { //设置channel名称,之后flutter中也要一样 channel = new MethodChannel(binding.getFlutterEngine().getDartExecutor(), "apk_md5"); //把当前的MethodCallHandler设置 channel.setMethodCallHandler(this); } @Override public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { } @Override public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { String method = call.method; if (method.equals("getMd5")) { String md5 = getMd5(context); if (!TextUtils.isEmpty(md5)) { result.success(md5); } else { result.error("101", "获取md5失败", ""); } } else { result.notImplemented(); } } //获取你重新自身的安装包位置 一般在/data/app/包名/xxx.apk private String getApkPath(Context context) { try { PackageInfo packageInfo = context.getPackageManager().getPackageInfo(BuildConfig.APPLICATION_ID, PackageManager.GET_META_DATA); ApplicationInfo applicationInfo = packageInfo.applicationInfo; return applicationInfo.publicSourceDir; // 获取当前apk包的绝对路径 } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } return ""; } //获取hash值 整个apk的 注意 这里代码不太严谨 demo随便敲的 跑通就行了 private String getMd5(Context context) { String apkPath = getApkPath(context); StringBuffer sb = new StringBuffer(""); try { MessageDigest md = MessageDigest.getInstance("MD5"); md.update(readFileToByteArray(new File(apkPath))); byte b[] = md.digest(); int d; for (int i = 0; i < b.length; i++) { d = b[i]; if (d < 0) { d = b[i] & 0xff; // 与上一行效果等同 // i += 256; } if (d < 16) sb.append("0"); sb.append(Integer.toHexString(d)); } } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return sb.toString().toUpperCase(); } private byte[] readFileToByteArray(File file) throws IOException { InputStream in = null; try { in = new FileInputStream(file); return toByteArray(in, file.length()); } finally { in.close(); } } private byte[] toByteArray(InputStream input, long size) throws IOException { if (size > Integer.MAX_VALUE) { throw new IllegalArgumentException("Size cannot be greater than Integer max value: " + size); } return toByteArray(input, (int) size); } private byte[] toByteArray(InputStream input, int size) throws IOException { if (size < 0) { throw new IllegalArgumentException("Size must be equal or greater than zero: " + size); } if (size == 0) { return new byte[0]; } byte[] data = new byte[size]; int offset = 0; int readed; while (offset < size && (readed = input.read(data, offset, size - offset)) != -1) { offset += readed; } if (offset != size) { throw new IOException("Unexpected readed size. current: " + offset + ", excepted: " + size); } return data; } @Override public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { context = binding.getActivity(); } @Override public void onDetachedFromActivityForConfigChanges() { } @Override public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { } @Override public void onDetachedFromActivity() { context = null; } }Flutter系列教程之(9)——Flutter调用Android原生由讯客互联电脑硬件栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“Flutter系列教程之(9)——Flutter调用Android原生”