Android热修复Tinker

一、简介

之前项目分包dex加载出现了问题,解决之后,为了加深理解,网上找了些有关内容学习了, 而热修复技术很重要,现在刚好有时间,研究学习下,热修复涉及的技术比较多,能更深层的剖析Android app启动加载底层的原理;当前市面热修复方案比较多,对比了下,微信官方的Tinker热补丁方案比较强大,(下图取自Tinker官网)今天在公众号上看到阿里又推出个新的热修复方案Sophix,看了文章介绍,很屌,现在先学习Tinker;


详情对比可阅读微信Android热补丁实践演进之路

1、Tinker是什么
Tinker是微信官方的Android热补丁解决方案,它支持动态下发代码、So库以及资源,让应用能够在不需要重新安装的情况下实现更新。当然,你也可以使用Tinker来更新你的插件。


2、Tinker原理
简单来说,在编译时通过新旧两个Dex生成差异path.dex。在运行时,将差异patch.dex重新跟原始安装包的旧Dex还原为新的Dex。这个过程可能比较耗费时间与内存,所以我们是单独放在一个后台进程:patch中。为了补丁包尽量的小,微信自研了DexDiff算法,它深度利用Dex的格式来减少差异的大小。它的粒度是Dex格式的每一项,可以充分利用原本Dex的信息,而BsDiff的粒度是文件,AndFix/QZone的粒度为class。


apk解压后,就可以看到dex文件,app在安装启动时也是要解压apk,加载各个资源;开发时,我上线发布后,用户手机上的就是旧apk,或叫基准包,当我们修改代码后,重新编译生成的新包时,通过tinker提供了patch的生成工具,会对基准包和新包做对比处理,生成一个补丁包apk,然后把补丁包下载都手机本地,和手机上的基准包进行合并,生成新的apk,来完成代码的更新;

3、Tinker的已知问题
由于原理与系统限制,Tinker有以下已知问题:
1>Tinker不支持修改AndroidManifest.xml,Tinker不支持新增四大组件;
2>由于Google Play的开发者条款限制,不建议在GP渠道动态更新代码;
3>在Android N上,补丁对应用启动时间有轻微的影响;
4>不支持部分三星android-21机型,加载补丁时会主动抛出"TinkerRuntimeException:checkDexInstall failed";
5>对于资源替换,不支持修改remoteView。例如transition动画,notification icon以及桌面图标。

二、Tinker接入

tinker官方推荐采用gradle接入,同时也给出了使用例子tinker-sample-android,根据它把相关类和配置引入我们的项目

1、在项目的build.gradle中,添加tinker-patch-gradle-plugin的依赖

dependencies {
   //省略
    classpath ('com.tencent.tinker:tinker-patch-gradle-plugin:1.7.11')
}
2、然后在app的gradle文件app/build.gradle,我们需要添加tinker的库依赖以及apply tinker的gradle插件

dependencies {
    //可选,用于生成application类
    provided('com.tencent.tinker:tinker-android-anno:1.7.11')
    //tinker的核心库
    compile('com.tencent.tinker:tinker-android-lib:1.7.11')
}
3、此外还需设置jumboMode=true,防止由于字符串增多导致force-jumbol,导致更多的变更

dexOptions {
    jumboMode = true
}
4、还有gradle相关参数配置,下面只是不完整的一小部分,具体可以参考例子中的gradle

if (buildWithTinker()) {
    apply plugin: 'com.tencent.tinker.patch'
    tinkerPatch {
        oldApk = getOldApkPath()
        //其他相关
5、对Application进行改造,因为程序默认启动时会加载Application,导致补丁无法对其实进行修改,Tinker通过代码框架的方式来避免,将原来的Application类隔离起来,即其他任何类都不能再引用我们自己的Application,官方推荐
使用tinker-android-anno在运行时生成你的Application类。这样保证你无法修改你的Application类,不会因为错误操作导致引入更多无法修改的类。

@DefaultLifeCycle(
        application = "com.daitu_liang.study.mytest.app.GetFightApplication",//application类名
        flags = TINKER_ENABLE_ALL, //tinkerFlags
        loadVerifyFlag = false)
public class GetFightApplicationTinker extends DefaultApplicationLike {
    public static Context CONTEXT;
    public static PreferencesManager preferenceManager;
    public static RefWatcher getRefWatcher(Context context) {
        return refWatcher;
    }
    private static final String TAG = "GetFightApplicationTinker";
    public GetFightApplicationTinker(Application application,
                                     int tinkerFlags,
                                     boolean tinkerLoadVerifyFlag,
                                     long applicationStartElapsedTime,
                                     long applicationStartMillisTime, Intent tinkerResultIntent) {
        super(application, tinkerFlags, tinkerLoadVerifyFlag,
                applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);
    }
    /**
     * install multiDex before install tinker
     * so we don't need to put the tinker lib classes in the main dex
     *
     * @param base
     */
    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    @Override
    public void onBaseContextAttached(Context base) {
        super.onBaseContextAttached(base);
        //you must install multiDex whatever tinker is installed!
        MultiDex.install(base);
        SampleApplicationContext.application = getApplication();
        SampleApplicationContext.context = getApplication();
        TinkerManager.setTinkerApplicationLike(this);
        TinkerManager.initFastCrashProtect();
        //should set before tinker is installed
        TinkerManager.setUpgradeRetryEnable(true);
        //optional set logIml, or you can use default debug log
        TinkerInstaller.setLogIml(new MyLogImp());

        //installTinker after load multiDex
        //or you can put com.tencent.tinker.** to main dex
        TinkerManager.installTinker(this);
        Tinker tinker = Tinker.with(getApplication());
        setContext(this.getApplication());
    }
    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    public void registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks callback) {
        getApplication().registerActivityLifecycleCallbacks(callback);
    }

    private  static RefWatcher refWatcher;

    @Override
    public void onCreate() {
        super.onCreate();
        init();
    }
    private void init() {
        preferenceManager = PreferencesManager.getInstance(this.getApplication());
        refWatcher = LeakCanary.install(this.getApplication());
        Fresco.initialize(this.getApplication());
        LitePal.initialize(this.getApplication());
    }

    private static void setContext(Context mContext) {
        CONTEXT = mContext;
    }

    public static Context getContext() {
        return CONTEXT;
    }

    public static PreferencesManager getPreferenceManager() {
        return preferenceManager;
    }
}

清单里application我们需要设置为DefaultLifeCycle里application的值,

<application
    android:name=".app.GetFightApplication"
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
我们发现这个里的GetFightApplication没有报错,因为通过Annotation自动生成了我们的application,点击可以查看

/**
 *
 * Generated application for tinker life cycle
 *
 */
public class GetFightApplication extends TinkerApplication {

    public GetFightApplication() {
        super(7, "com.daitu_liang.study.mytest.app.GetFightApplicationTinker", "com.tencent.tinker.loader.TinkerLoader", false);
    }

}

若采用Annotation生成Application,需要将原来的Application类删掉。还有以下要求

1>Application的attachBaseContext方法实现要单独移动到onBaseContextAttached中;
2>对GetFightApplicationTinker 中,引用application的地方改成getApplication();
3>对其他引用GetFightApplicationTinker 或者它的静态对象与方法的地方,改成引用GetFightApplicationTinker 的静态对象与方法;

此外,不想用Annotation生成application的话,也可以,

1>创建一个类SampleApplicationLike继承DefaultApplicationLike,将原自己的Application的初始化等需要的业务放在onBaseContextAttached里

2>原自己的Application改为继承TinkerApplication,必须有个构造,且第二个参数必须为1>里的SampleApplicationLike

public GetFightApplication() {
    super(
            //tinkerFlags, which types is supported
            //dex only, library only, all support
            ShareConstants.TINKER_ENABLE_ALL,
            // This is passed as a string so the shell application does not
            // have a binary dependency on your ApplicationLifeCycle class.
            "com.daitu_liang.study.mytest.app.SampleApplicationLike");
}
3>清单里application name为原自己的Application,添加读写权限;

接入到此结束,

6、添加加载补丁代码

//合成path.dex
TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(),
        Environment.getExternalStorageDirectory().getAbsolutePath() + "/tinker.zip");

7.演示下看下效果,主要将背景颜色由红色改为蓝色,setText一句我终于热修复成功了;



1>打开as右侧的gradle找到自己module,点击build,再点击assembleDebug编译,我们会将编译过的包保存在build/bakApk中,如何上传左侧。然后我们将它安装到手机,这也就是我们的基准包,点击show_tip按钮,可以看到补丁并没有加载.

2>先修改代码,把红色变为蓝色,然后修改build.gradle中的参数,将步骤一编译保存的安装包路径拷贝到tinkerPatch中的oldApk参数中,以及txt文件,如上图。

3>在as右侧gradle找到自己module,点击tinker,再点击tinkerPatchDebug编译, 补丁包与相关日志会保存在/build/outputs/tinkerPatch/。如上图左侧,然后我们将patch_signed_7zip.apk,它就是补丁包,推送到手机的sdcard中,它最终在手机上会合成一个全量的资源apk,这个过程是比较耗时的。patch_signed_7zip.apk是已签名并且经过7z压缩的补丁包,但是最好重命名一下,不要让它以.apk结尾,这是因为有些运营商会挟持以.apk结尾的资源。

4>点击加载补丁包,如果看到patch success, please restart process的toast,即可锁屏或者重启,可看到修改后的效果,即热修复成功;或者点击show_tip查看日志,判断是否成功修复;

还有比较重要的东西,就是dex有关的,查看Tinker的源码,在内部dex的合成处理,需要了解dex文件的格式以及DexDiff算法,

Dex文件格式   http://blog.csdn.net/feglass/article/details/51761902

对dex文件格式分析,可以借助工具010Editor进行分析,如下图,左边是dex文件16进制的展现方式,右下角是dex各个区,具体看上面Dex文件格式链接内容,


DexDiff算法解析


返回的一些错误码可以在查看Tinker自定义扩展

https://github.com/Tencent/tinker

微信Android热补丁实践演进之路

微信Tinker的一切都在这里,包括源码(一)







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值