Flutter学习(9)——Flutter插件实现(Flutter调用Android原生
原文地址: Flutter学习(9)——Flutter插件实现(Flutter调用Android原生) | Stars-One的杂货小窝
最近需要给一个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 |
代码参考
点击查看源码(ApkMd5CheckPlugin类)
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.common.MethodCall;
import io.flutter.plugin.common.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 plugin之路:flutter与原生交互传值OC/java版(一) - 简书
- Flutter调用android原生方法_guanyueliuxing的博客-CSDN博客_flutter 调用原生方法
Flutter学习(9)——Flutter插件实现(Flutter调用Android原生的更多相关文章
- Flutter学习笔记(29)--Flutter如何与native进行通信
如需转载,请注明出处:Flutter学习笔记(29)--Flutter如何与native进行通信 前言:在我们开发Flutter项目的时候,难免会遇到需要调用native api或者是其他的情况,这时 ...
- PhoneGap或者Cordova框架下实现Html5中JS调用Android原生代码
PhoneGap或者Cordova框架下实现Html5中JS调用Android原生代码 看看新闻网>看引擎>开源产品 0人收藏此文章, 发表于8小时前(2013-09-06 00:39) ...
- uni-app&H5&Android混合开发三 || uni-app调用Android原生方法的三种方式
前言: 关于H5的调用Android原生方法的方式有很多,在该片文章中我主要简单介绍三种与Android原生方法交互的方式. 一.H5+方法调用android原生方法 H5+ Android开发规范官 ...
- 【Flutter学习】之VSCode下Flutter常用终端命令行
Flutter 常用命令行 相关项目操作 查看Flutter版本 查看当前版本 flutter --version 查看所有版本 flutter version 打印所有命令行用法信息 flutter ...
- Flutter学习之路---------第一个Flutter项目
Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面. Flutter可以与现有的代码一起工作.在全世界,Flutter正在被越来越多的开发者和组织使用,并且 ...
- Flutter学习笔记(四)--Flutter几个小知识点
Flutter的Widget采用的是现代化的React风格,该风格的设计灵感来源于React这么语言.最核心的理念是你可以使用Widget设计界面.Widget通过当前的state和注册信息来描述vi ...
- 通过js调用android原生方法
有时候我们有这样一个需求,监听html中控件的一些事件.例如点击html中某个按钮,跳转到别的activity,复制某段文本. 首先是对webview的设置: myWebView = (WebView ...
- react native 调用Android原生方法
来源:https://www.youtube.com/watch?v=WmJpHHmOKM8 教程:https://www.youtube.com/watch?v=GiUo88TGebs Breaki ...
- plus调用android原生页面
var main = plus.android.runtimeMainActivity(); var Intent = plus.android.importClass("android.c ...
随机推荐
- Codeforces 512D - Fox And Travelling(树上背包)
题面传送门 题意: 给出一张无向图,每次你可以选择一个度数 \(\leq 1\) 的点并将其删除. 问对于 \(k=0,1,2,\dots,n\),有多少个删除 \(k\) 个点的序列,答案模 \(1 ...
- R语言中的read.table()
参考资料:http://www.cnblogs.com/xianghang123/archive/2012/06/06/2538274.html read.table(file, header = F ...
- Rust 指定安装目录
集群home目录被管理员限制了存储空间,rust安装要100多M,默认安装home目录下,查了一圈,没找到rust指定安装目录的办法. 这里记录下解决办法: 在想要安装的目录执行 mkdir -p c ...
- Oracle-trunc函数、round 函数、ceil函数和floor函数---处理数字函数使用
0.round函数 按照指定小数位数进行四舍五入运算. SELECT ROUND( number, [ decimal_places ] ) FROM DUAL #number : 待处理数值 de ...
- Python3调用C程序(超详解)
Python3调用C程序(超详解) Python为什么要调用C? 1.要提高代码的运算速度,C比Python快50倍以上 2.对于C语言里很多传统类库,不想用Python重写,想对从内存到文件接口这样 ...
- javaSE高级篇2 — 流技术 — 更新完毕
1.先认识一个类----File类 前言:IO相关的一些常识 I / O----输入输出 I 输入 input 0 输出 output I / o 按数据的流动方向来分- ...
- Linux基础命令---ab测试apache性能
ab ab指令是apache的性能测试工具,它可以测试当前apache服务器的运行性能,显示每秒中可以处理多少个http请求. 此命令的适用范围:RedHat.RHEL.Ubuntu.CentOS.F ...
- SpringBoot(1):初始SpringBoot
一. SpringBoot 简介 1. SpringBoot介绍 Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化Spring应用的初始搭建以及开发过程.该框架使用了特 ...
- Dubbo服务分组
服务分组与多版本控制的使用方式几乎是相同的,只要将version替换为group即可.但使用目的不同.使用版本控制的目的是为了升级,将原有老版本替换掉,将来不再提供老版本的服务,所以不同版本间不能出现 ...
- 【C/C++】二维数组的传参的方法/二维字符数组的声明,使用,输入,传参
[问题] 定义了一个子函数,传参的内容是一个二维数组 编译提示错误 因为多维数组作为形参传入时,必须声明除第一位维外的确定值,否则系统无法编译(算不出偏移地址) [二维数组的传参] 方法一:形参为二维 ...