Flutter 应用热更新

Flutter 热更新简介

所谓热更新,指的是当应用代码出现缺陷问题时,不须要从新打包提交App Store便可完成缺陷的修复。众所周知,使用原生技术开发的应用体验虽然好,但开发、上线周期长也经常被诟病,特别是当应用出现线上问题时,不得不从新打包发布,大大的影响了用户体验,而热更新技术就是为有效解决线上缺陷而提出的。android

不过,热更新虽然具备很大的优势,可是滥用热修复也会给应用带来很差的体验,而且苹果对于热更新和修复是明令禁止的,因此热更新主要针对的是国内Android市场。目前,Flutter对外开放的SDK是不支持热更新的,可是在Flutter的源码里有一部分预埋的热更新相关的代码,能够经过一些必要的手段在Android端实现动态更新功能。编程

众所周知,不管是新建立的Flutter项目,仍是原生工程以Moudle或者aar的方式集成Flutter,最终Flutter在原生Android端应用中都是以混合的形式存在的。因此,当咱们拆开一个Flutter在release模式下编译生成的aar包时,其目录结构下图所示。bash

在这里插入图片描述
实际开发中,只须要关注assets、jni、libs这三个目录便可,其余都是原生的壳工程产物。

  • jni:该文件目录下存放的是libflutter.so文件,该文件是Flutter引擎层的C++实现,提供skia绘制引擎、Dart和Text纹理绘制等支持。
  • libs:该文件目录下存放的是flutter.jar文件,该文件为Flutter嵌入层的 Java实现,主要为Flutter的原生层提供平台功能支持,好比建立线程。
  • assets:该文件目录主要用于存放Flutter应用层的资源,包括images、font等。

而众观目前全部的Flutter热更新方案中,其基本原理实现都是同样的,即经过修改libapp.so的加载路径,把它替换成开发者本身的libapp_hot.so来实现热更新。咱们能够打开io.flutter.embedding.engine.loader包中的FlutterLoader类来查看libapp.so包的加载逻辑。网络

在原生Android开发中,咱们可使用Tinker、AndFix、Sophix和Robust等热修复框架来实现应用的热更新操做。综合比较,Tinker是一款不错的热修复框架,而且它支持修复so文件,既然Flutter热更新的基本原理是替换libapp.so,那么咱们就能够利用Tinker热更新功能将须要修复的libapp_hot.so送达客户端,而后再加载libapp_hot.so文件便可实现代码的热更新。架构

接入Bugly

在使用Tinker以前,须要咱们在原生Android平台接入Tinker。不过,在Flutter开发中,咱们能够经过集成Bugly来集成Tinker,由于Bugly默认集成了Tinker,而且还提供缺陷上报和应用升级等功能,可谓一箭双雕。 接入Bugly以前,须要先开通Bugly帐号,并在官网注册本身的产品,若是尚未帐号,能够打开Bugly官网注册一个。使用Android Studio打开Flutter项目的Android工程,而后在根目录的build.gradle文件中添加以下脚本,以下所示。app

buildscript {

dependencies {
// tinkersupport插件, 其中lastest.release表示拉取最新版本
        classpath "com.tencent.bugly:tinker-support:1.2.0"
    }
}

复制代码

接着,打开Android工程app目录下的build.gradle文件,并在android节点和dependencies节点添加以下配置脚本。框架

android {
     …  //省略其余配置
     defaultConfig {
       ndk {
         //设置支持的so库架构
         abiFilters 'armeabi', 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a'
        }
      }
    }

dependencies {
     implementation 'com.android.support:multidex:1.0.3'
     implementation 'com.tencent.bugly:crashreport_upgrade:1.4.2'
     //指定tinker依赖版本
     implementation 'com.tencent.tinker:tinker-android-lib:1.9.14' 
  }

复制代码

当须要更新升级SDK的时候,只须要更新配置脚本中的版本号便可。接下来,在build.gradle的同级目录下建立一个名为tinker-support.gradle的配置文件,并添加以下脚本。异步

apply plugin: 'com.tencent.bugly.tinker-support'

def bakPath = file("${buildDir}/bakApk/")
//填写每次构建生成的基准包目录
def baseApkDir = "app-0208-15-10-00"

tinkerSupport {
    //开启tinker-support插件,默认值true
enable = true

    //指定归档目录,默认值当前module的子目录tinker
autoBackupApkDir = "${bakPath}"

    //是否启用覆盖tinkerPatch配置功能,默认值false
overrideTinkerPatchConfiguration = true

    // @{link tinkerPatch.oldApk }
    baseApk = "${bakPath}/${baseApkDir}/app-release.apk"
    baseApkProguardMapping = "${bakPath}/${baseApkDir}/app-release-mapping.txt"
baseApkResourceMapping = "${bakPath}/${baseApkDir}/app-release-R.txt"

    //构建基准包和补丁包都要指定不一样的tinkerId,而且必须保证惟一性
    tinkerId = "base-1.0.1"
    enableProxyApplication = false
    supportHotplugComponent = true
}

tinkerPatch {
    ignoreWarning = false
    useSign = true
    dex {
        dexMode = "jar"
        pattern = ["classes*.dex"]
        loader = []
    }
    lib {
        pattern = ["lib/*/*.so"]
    }
    res {
       pattern = ["res/*", "r/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
       ignoreChange = []
       largeModSize = 100
    }
    packageConfig {
    }
    sevenZip {
        zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
    }
    buildConfig {
        keepDexApply = false
    }
}

复制代码

在上面的配置脚本中,咱们须要重点关注baseApkDir和tinkerId两个属性。其中,baseApkDir表示每次构建生成的基准包目录,而tinkerId表示构建基准包和补丁包须要指定的惟一标识。而后,再次打开build.gradle文件,在build.gradle文件的头部引入tinker-support.gradle配置脚本文件,以下所示。ide

//依赖Tinker插件脚本
apply from: 'tinker-support.gradle'

复制代码

Bugly提供了两种初始化SDK的方式,区别是是否须要反射Application。首先,自定义一个继承自TinkerApplication的Application类,以下所示。异步编程

public class SampleApplication extends TinkerApplication {
    public SampleApplication() {
        super(ShareConstants.TINKER_ENABLE_ALL, "com.xzh.hotreload.SampleApplicationLike",
                "com.tencent.tinker.loader.TinkerLoader", true);
    }
}

复制代码

能够发现,SampleApplication须要传入四个参数,须要变动的就是第二个参数,表示自定义的ApplicationLike,以下所示。

public class SampleApplicationLike extends DefaultApplicationLike {

    public static final String TAG = "Tinker.SampleApplicationLike";

    public SampleApplicationLike(Application application, int tinkerFlags,
            boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime,
            long applicationStartMillisTime, Intent tinkerResultIntent) {
        super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);
    }


    @Override
    public void onCreate() {
        super.onCreate();
        // SDK初始化,appId替换成你的在Bugly平台申请的appId, 调试时,将第三个参数改成true
        Bugly.init(getApplication(), "900029763", false);
    }

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    @Override
    public void onBaseContextAttached(Context base) {
        super.onBaseContextAttached(base);
        MultiDex.install(base);

        // 安装tinker
        // TinkerManager.installTinker(this); 替换成下面Bugly提供的方法
        Beta.installTinker(this);
    }

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    public void registerActivityLifecycleCallback(Application.ActivityLifecycleCallbacks callbacks) {
        getApplication().registerActivityLifecycleCallbacks(callbacks);
    }
}

复制代码

SampleApplicationLike类中Bugly初始化时须要用到appId,若是没有能够到Bugly官网注册应用,而后系统会生成一个对应的appId,以下图所示。

在这里插入图片描述
因为Tinker须要开启MultiDex,因此集成Bugly时还须要在build.gradle配置文件中添加multidex插件才可使用MultiDex.install()方法。完成上述操做后,还须要在AndroidManifest.xml配置中添加以下配置。

<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_LOGS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

//兼容Android N或者以上设备,必须配置FileProvider来访问共享路径的文件
<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="${applicationId}.fileProvider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/provider_paths"/>
</provider>

复制代码

须要说明的是,制做Android正式环境的基准包时,还须要在build.gradle文件中配置签名信息,以下所示。

android {
     …  //省略其余配置
     signingConfigs {
        release {
            try {
                storeFile file("./keystore/release.keystore")
                storePassword "testres"
                keyAlias "testres"
                keyPassword "testres"
            } catch (ex) {
                throw new InvalidUserDataException(ex.toString())
            }
        }
    }

复制代码

当制做签名正式包时,为了不由于混淆而形成代码功能异常,还须要在Proguard混淆文件中增长配置以下来避免混淆SDK。

-dontwarn com.tencent.bugly.**
-keep public class com.tencent.bugly.**{*;}
# tinker混淆规则
-dontwarn com.tencent.tinker.**
-keep class com.tencent.tinker.** { *; }
-keep class android.support.**{*;}

复制代码

到此,原生Android接入Bugly就大致完成了。若是接入过程有任何问题,能够参考Bugly官网接入文档

Flutter应用热更新

接下来,咱们经过 执行Android的热更新操做以前,须要先制做应用的基准包。执行flutter build apk –release打包命令,或者打开Android Studio右边的Gradle面板执行打包操做,以下图所示。

在这里插入图片描述
执行assemble操做后,系统会在Flutter项目的build/app/bakApk文件夹下生成对应的基准包,而且每一个正式签名的的基准包目录都会包含基准包、混淆配置文件和资源Id文件,以下图所示。
在这里插入图片描述
而后,将生成的app-release.apk正式基准包上传到Bugly的后台。当线上版本出现缺陷问题时,就可使用tinker-support插件生成对应的补丁包。 修复代码里面的缺陷问题后,还须要修改tinker-support.gradle文件里面对应的baseApkDir和tinkerId的属性值,而后才能执行补丁包生成操做,以下图所示。
在这里插入图片描述
执行完补丁包生成操做以后,就会在Flutter工程的根目录的build/outputs文件夹下生成对应的补丁包文件,以下图所示。
在这里插入图片描述
而后,将生成的带签名的补丁包文件上传到Bugly后台,选择补丁的下发范围,点击当即下发让补丁生效,而后从新启动Flutter应用就能够看到应用更新后的效果。

注: 本文部分摘自《Flutter应用跨平台开发实战》(即将出版) 参考资料:

1,移动跨平台方案对比:WEEX、React Native、Flutter和PWA
2,Flutter入门与环境搭建
3,Flutter开发之Dart语言基础
4,Flutter基础知识
5,Flutter开发之基础Widgets
6,Flutter 应用程序调试
7,Flutter For Web入门实战
8,Flutter开发之异步编程
9,Flutter开发之网络请求
10,Flutter开发之JSON解析
11,Flutter开发之路由与导航
12,Flutter 必备开源项目
13,Flutter 国际化适配实战
14,Flutter应用集成极光推送
15,Flutter混合开发
16,构建属于本身的Flutter混合开发框架
17,Flutter 应用性能检测与优化
18,Flutter异常监测与上报
19,Flutter的Hot Reload是如何作到的
20,Apple为何不封杀 Flutter,之后会封杀吗
21,《Flutter in action》开源

相关文章
相关标签/搜索