Flutter—Android混合開發之下載安裝的實現

前言

該功能已加入Bedrock快速開發框架,連接:
複製代碼

github.com/bladeofgod/…java

框架介紹:
複製代碼

Bedrock——基於MVVM+Provider的Flutter快速開發框架android

後語

通常應用的更新方式:
    IOS端是直接跳轉app store
    Android端跳轉應用市場,或者從服務器下載自行安裝。
咱們這裏實現一下Android的下載安裝功能(其實我不會IOS)
複製代碼

簡介

如今flutter也有一些下載和安裝的插件:ios

flutter_downloader

install_plugin
複製代碼

可是我瞭解了一下,不是與第三方插件衝突,要麼就是好久不更新了,再適配修復的話還不如本身寫一個,並且也更靈活。git

Flutter端

咱們須要的插件有:github

path_provider用來獲取存儲路徑
dio用來下載你的安裝包。
permission_handler 用來請求權限
複製代碼

權限

首先咱們要申請權限(安卓端須要進行聲明,這個在 Android端 說):bash

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
      ///你能夠對它進行捕捉並操做,也能夠啥都不作
    }
  }
複製代碼

參數簡介:app

url是下載的路徑框架

cancelToken可選參數,能夠用來取消下載任務async

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 以下圖:
複製代碼

我沒學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屬性爲我們剛纔本身建立的那個。

以後,咱們在<application 標籤內的底部添加provider,以下:

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>
複製代碼

至此整個功能就實現了。

DEMO

DEMO地址

能夠直接運行,在「綜合demo演示」裏,有一個「更新」按鈕,點一下就能抵達。

相關文章
相關標籤/搜索