美文网首页
Flutter—Android混合开发之下载安装的实现

Flutter—Android混合开发之下载安装的实现

作者: 吉哈达 | 来源:发表于2020-07-07 09:28 被阅读0次

    前言

    该功能已加入Bedrock快速开发框架,链接:
    

    https://github.com/bladeofgod/Bedrock

    后语

    一般应用的更新方式:
        IOS端是直接跳转app store
        Android端跳转应用市场,或者从服务器下载自行安装。
    我们这里实现一下Android的下载安装功能(其实我不会IOS)
    

    简介

    现在flutter也有一些下载和安装的插件:

    flutter_downloader
    
    install_plugin
    

    但是我了解了一下,不是与第三方插件冲突,要么就是很久不更新了,再适配修复的话还不如自己写一个,而且也更灵活。

    Flutter端

    我们需要的插件有:

    path_provider用来获取存储路径
    dio用来下载你的安装包。
    permission_handler 用来请求权限
    

    权限

    首先我们要申请权限(安卓端需要进行声明,这个在 Android端 说):

      checkPermission()async{
        Map<Permission,PermissionStatus> permissions =
        await [
          Permission.storage,
        ].request();
        if(permissions.values.first.isGranted){
          downloadAPK();
    
        }else{
          showToast('permissions denied');
        }
    
      }
    

    下载

    dio的download功能非常好用,具体使用方法如下:

      downloadAPK()async{
        try{
          await dio.download(url, getSavePath(),
              cancelToken:cancelToken,
              onReceiveProgress: (receive,total){
                //debugPrint('apk info $receive   $total');
                setProgress((receive/total *100).toStringAsFixed(1));
              } ).then((response){
                if(response.statusCode == 200){
                  installAPK();
                }
          });
        }catch(e,s){
          ///如果通过cancelToken取消任务,那么会抛出一个dio exception . cancel
          ///你可以对它进行捕捉并操作,也可以啥都不做
        }
      }
    

    参数简介:

    url是下载的路径

    cancelToken可选参数,可以用来取消下载任务

    onReceiveProgress 是一个回调,receive是当前下载的大小,total是整个资源的大小

    getSavePath() 提供一个保存路径,代码如下:

      String getSavePath(){
        String path = StorageManager.externalDirectory.path + '/test/bedrock.apk';
        return path;
      }
    

    当我们下载完成后,便可以进行安装步骤了,这部分需要原生端配合来实现,我们先把拉起原生端的Channel实现。

    channel 有三个,这里做一下介绍,有兴趣的可以去百度:
    
     BasicMessageChannel:用于传递字符串和半结构化的信息,这个用的比较少
    
     MethodChannel:用于传递方法调用(method invocation)通常用来调用native中某个方法
    
     EventChannel: 用于数据流(event streams)的通信。有监听功能,比如电量变化之后直接推送数据给flutter端。
    

    这里我们要用到method_channel用以和原生端进行通行:

    先造一个channel:

    static const MethodChannel _channel = const MethodChannel('com.lijiaqi.bedrock');
    构造函数传一个名字进去,具体随意起,但要保证与原生端一致。
    

    再定义一个方法名:

    static final String methodInstall = 'install_apk'; //这个同样要与原生端保持一致
    

    之后我们就可以拉起原生端对应的方法了:

      //path是apk的安装路径
      void installApk(String path)async{
        ///ios建议直接取应用市场
        if(Platform.isAndroid){
          await _channel.invokeMethod(methodInstall,
              {"path":path});
        }
      }
    

    下面实现安卓端

    Android端

    用Android studio打开项目下的android,依次打开:
    app-src-main-java/kotlin 如下图:
    
    image
    我没学kotlin,所以以java来写,两者差不多。
    

    我们在java文件夹下新建一个包,名字你随意,我这里叫 com.lijiaqi.bedrock

    再依次新建:

    Mainactivity    因为项目默认是kotlin,所以我得新建一个这个
    
    plugin/BedrockPlugin 用于处理flutter的请求
    

    MainActivity

    import com.lijiaqi.bedrock.plugin.BedrockPlugin;
    
    import io.flutter.embedding.android.FlutterActivity;
    import io.flutter.embedding.engine.FlutterEngine;
    import io.flutter.plugins.GeneratedPluginRegistrant;
    
    public class MainActivity extends FlutterActivity {
        @Override
        public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
            GeneratedPluginRegistrant.registerWith(flutterEngine);
            flutterEngine.getPlugins().add(new BedrockPlugin(this));
        }
    }
    

    通过:

    flutterEngine.getPlugins().add(new BedrockPlugin(this));
    

    我们可以对我们的BedrockPlugin进行注册,其代码如下

    BedrockPlugin,代码比较多,我把注解写在代码里,方便对应

    public class BedrockPlugin implements FlutterPlugin, ActivityAware, MethodChannel.MethodCallHandler {
        
        ///这里要与flutter端的一致
        private static final String PLUGIN_NAME = "com.lijiaqi.bedrock";
        
        ///channel
        private MethodChannel mMethodChannel;
        
        private Application mApplication;
        private WeakReference<Activity> mActivity;
    
        public BedrockPlugin(Activity mActivity) {
            this.mActivity = new WeakReference<Activity>(mActivity);
        }
    
        @Override
        public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
            ///这里我们进行channel的初始化
            mMethodChannel = new MethodChannel(binding.getBinaryMessenger(),PLUGIN_NAME);
            mApplication = (Application) binding.getApplicationContext();
            mMethodChannel.setMethodCallHandler(this);
    
        }
    
        @Override
        public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
            ///对channel进行释放
            mMethodChannel.setMethodCallHandler(null);
            mMethodChannel = null;
    
        }
    
        ///这里是处理flutter请求的地方了
        
        @Override
        public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
            log("call");
            switch (call.method){
            ///通过call.method可以获得flutter端调用的那个方法的名字,一定要两端一致
            ///这里我们知道flutter 调用的名字是:install_apk
                case "install_apk":
                ///通过call.argument按键path 就可以取到对应的值
                ///开始调用安装
                    invokeInstall(call.argument("path"));
                    break;
                default:
                    break;
            }
    
        }
        
        ///下面单独说一下
        private static final String provider = "com.lijiaqi.flutter_bedrock.fileProvider";
    
        //安装
        //安卓7.0以前是可以直接通过路径创建File进行安装的,
        //但是之后的则需要fileProvider来协助安装(这个东西也可以应用之间共享文件,跨进程的)
        private void invokeInstall(String apkPath){
            if(apkPath != null && ! apkPath.isEmpty()){
                log(apkPath);
                Intent intent = new Intent(Intent.ACTION_VIEW);
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                ///< 判断是否是AndroidN以及更高的版本
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                    // 不能再用setFlags了, setflags会重置之前的设置, 要么 setflags 多个|拼接,要么addflag
                    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                    Uri contentUri = FileProvider.getUriForFile(mActivity.get(), provider, new File(apkPath));
                    intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
                } else {
                    intent.setDataAndType(Uri.fromFile(new File(apkPath)), "application/vnd.android.package-archive");
                }
                mActivity.get().startActivity(intent);
    
            }
        }
    
        private void log(String msg){
            Log.i("native method",msg);
        }
        
        ///这以下的暂时不用管
    
        @Override
        public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) {
    
        }
    
        @Override
        public void onDetachedFromActivityForConfigChanges() {
    
        }
    
        @Override
        public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) {
    
        }
    
        @Override
        public void onDetachedFromActivity() {
    
        }
    
    }
    

    以上方法就可以进行安装页面的拉起,但是在运行之前我们还需要进行相应的权限配置,在上面的目录结构图中找到AndroidManifest.xml,打开添加如下权限:

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
    

    如果你跟我一样也是用java,且项目默认是kotlin的话,还需要更改<activity 标签里的name属性为咱们刚才自己创建的那个。

    image

    之后,我们在<application 标签内的底部添加provider,如下:

    image

    authorities,这个属性你可以填写包名+任意字符,确保和之前的代码中的 provider一致即可。

    之后我们在res文件夹下新建一个xml文件夹,再创建一个xml文件,名字随意,我这里就叫filepaths 内容如下:

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
    <paths xmlns:android="http://schemas.android.com/apk/res/android">
        <files-path name="my_images"
            path="images"/>
    
        <external-path
            name="files"
            path="."/>
    
        <cache-path
            name="cache_place"
            path="."/>
    </paths>
    

    </resources>

    至此整个功能就实现了。

    DEMO

    DEMO地址

    可以直接运行,在“综合demo演示”里,有一个“更新”按钮,点一下就能抵达。

    相关文章

      网友评论

          本文标题:Flutter—Android混合开发之下载安装的实现

          本文链接:https://www.haomeiwen.com/subject/zyooqktx.html