来自微信的地精修补匠--Tinker

前言

最近公司新上一个项目,天使用户试用阶段每天各种各样的需求要让改。改一次发一个版本改一次发一个版本简直苦不堪言,大多数时候只是界面上的调整改动,交易结果的排版这些问题。毕竟太高频次又没有多大实质变化的更新让人很反感,因此得想一些改进办法。于是决定接入热更新技术,小问题采用发补丁包的方式。定期发布版本对改动进行整体迭代。Android热更新技术已经有很多框架可以直接使用了,至于怎么选择见仁见智,选择自己项目适合的方式。本文主要记录一下我自己项目接入微信的Tinker热修复框架的过程。

使用之前一定仔细阅读Wiki文档和sample中的代码!!!
Tinker Github地址

快速接入过程

一、引入依赖
参考Tinker-接入指南
项目的build.gradle中添加

    repositories {
    //mavenLocal要特别注意别漏了,否则更改资源文件会出空指针异常
        mavenLocal()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.1'
        //集成Tinker,热修复
        classpath ('com.tencent.tinker:tinker-patch-gradle-plugin:1.7.0')
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
    allprojects {
        repositories {
            mavenLocal()
            jcenter()
        }
    }

APP的build.gradle中添加

    dependencies {
        //可选,用于生成application类,(版本号换成最新的发布版)
        compile('com.tencent.tinker:tinker-android-anno:1.7.0')
        //tinker的核心库
        compile('com.tencent.tinker:tinker-android-lib:1.7.0') 
    }
    ...
    ...
    //apply tinker插件
    apply plugin: 'com.tencent.tinker.patch'
}

二、其他配置
比较快速的接入方式是直接对比自己app的build.gradle 文件和sample项目的build.gradle文件将自己的配置和tinker需要的配置合并。修改defaultConfig中的applicationId为自己的包名,注意signConfig中的签名文件路径(Gradle打包签名用的)。修改tinkerPatch中loader数组中的Application为自己的Application。
build.gradle中各个配置的解释wiki文档中都有说明
这里我说一说我在接入时遇到的几个问题(文档里面也都有说明,但不仔细看文档又一定会遇到的问题)

  • 1.tinkerId的问题
TinkerId报错
TinkerId报错

如果项目没有初始化git并commit,官方sample如果不是采用git clone的方式获取也会有这个问题。因为sample配置中

    tinkerId = getTinkerIdValue()
    ...
    ...
    def getTinkerIdValue() {
        return hasProperty("TINKER_ID") ? TINKER_ID : gitSha()
    }

是通过git的版本sha来设置tinkerId的。
解决办法:①直接设置一个任意的字符串②初始化git并提交一次。为了避免补丁混乱,每个基础包发布版本(不是每一次补丁包发布)都对应一个唯一的tinkerId,打补丁时补丁和基础包根据tinkerId关联。可采用发布版的git版本号或者versionName来作为tinkerId。我自己是试用的版本号来作为tinkerId的这样一个版本对应的是一个id

//修改tinkerid的值
def getTinkerIdValue() {
    return android.defaultConfig.versionName
}
  • 2.keep_in_main_dex.txt 找不到的问题
    /**
    * not like proguard, multiDexKeepProguard is not a list, so we can't just
    * add for you in our task. you can copy tinker keep rules at
    * build/intermediates/tinker_intermediates/tinker_multidexkeep.pro
    */
    multiDexKeepProguard file("keep_in_main_dex.txt")

sample中copy过来的配置中有上面这么一条,在sample中的对应目也有这个文件。里面是一些keep规则,复制txt文件到自己项目对应目录即可

  • 3.需要注意的是tinker插件需要读写文件的权限,Android6.0之下mianfest中配置即可,6.0之后还需Java代码中申请权限。
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
  • 4.bapApk目录下不能生成mapping文件的问题

a.

    /**
    * task type, you want to bak
    * taskName与编译选择的方式要一致
    */
    def taskName = "release"
    

b.直接使用AndroidStudio菜单栏build打包也会出现这种问题,这里使用gradle命令打包的方式。

Gradle打包步骤
Gradle打包步骤

点击AndroidStudio的右边栏打开Gradle命令菜单展开build接点如图,根据配置的taskName选择debug方式还是release方式打包,release方式即为要发布的打包版本。如需打包签名的apk则需要配置app的gradle.build中的签名文件信息

    /**
     * Gradle打包签名配置
     */
    signingConfigs {
        release {
            storeFile file('E:/PandaQ/tinkerDemo/test.jks')
            keyAlias 'tinker测试'
            keyPassword 'tinkertest'
            storePassword 'tinkertest'
        }
        debug {
            storeFile file('C:/Users/PandaQ/.android/debug.keystore')
        }
    }

配置后运行gradle打包命令就会按上面的签名文件对应用进行打包,但直接将签名文件密码放在配置中不安全,可以设置在打包过程中输入。具体方式可以看这里

  • 5.clean或者rebuild工程后bakApk文件夹被清除问题
    由于打包补丁的时候需要配置基础包:
    /**
 * you can use assembleRelease to build you base apk
 * use tinkerPatchRelease -POLD_APK=  -PAPPLY_MAPPING=  -PAPPLY_RESOURCE= to build patch
 * add apk from the build/bakApk
 */
ext {
    //for some reason, you may want to ignore tinkerBuild, such as instant run debug build?
    tinkerEnabled = true
    //you should bak the following files
    //old apk file to build patch apk
    //app-debug-1017-11-29-32.apk
    tinkerOldApkPath = "${bakPath}/app-release-1024-16-46-49.apk"
    //proguard mapping file to build patch apk
    tinkerApplyMappingPath = "${bakPath}/app-release-1024-16-46-49-mapping.txt"
    //resource R.txt to build patch apk, must input if there is resource changed
    tinkerApplyResourcePath = "${bakPath}/app-release-1024-16-46-49-R.txt"
}

默认的输出目录为bakApk目录,但是每次clean或者rebuild工程时这个文件夹都会被清理,因此需要手动将发布出去的基础包和对应的mapping.txt及R.txt备份。或者修改Gradle配置中的输出目录。
三、Application类
参考Tinker-自定义扩展

官方文档截图
官方文档截图

Tinker的接入文档中已经对Application类做了说明,为了使真正的Application能被修改将Application中的所有逻辑都移动到ApplicationLike代理类中来。Application继承TinkerApplication并只做一个super的空实现,或者通过在ApplicationLike代理中添加注解直接动态生成Application类,防止不小心在里面写了其他代码。

@DefaultLifeCycle(
        application = "com.seuic.seuickp.KPApplication",             //application name to generate
        flags = ShareConstants.TINKER_ENABLE_ALL)
public class KPApplicationLike extends DefaultApplicationLike {

    public static Context mContext;
    public static ImageLoader mImageLoader;
    public static DisplayImageOptions sOptions;

    public KPApplicationLike(Application application, int tinkerFlags, 
    boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime,Intent tinkerResultIntent, Resources[] resources, ClassLoader[] classLoader, AssetManager[] assetManager) {
        super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime,tinkerResultIntent, resources, classLoader, assetManager);
    }

    @Override
    public void onBaseContextAttached(Context base) {
        super.onBaseContextAttached(base);
        MultiDex.install(base);
        TinkerManager.setTinkerApplicationLike(this);
        TinkerManager.installTinker(this);
    }

    public static ImageLoader getmImageLoader() {
        return mImageLoader;
    }

    public static DisplayImageOptions getOptions() {
        return sOptions;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        mContext = getApplication().getApplicationContext();
        DBTools.init(mContext);
        mImageLoader = ImageLoader.getInstance();
        ImageLoaderConfiguration config = new ImageLoaderConfiguration
                .Builder(mContext)
                .threadPoolSize(4)
                .imageDownloader(new AuthImageDownloader(getApplication().getApplicationContext()))
                .build();
        mImageLoader.init(config);
        sOptions = new DisplayImageOptions
                .Builder()
                .showImageForEmptyUri(R.drawable.loading_pic)
                .showImageOnFail(R.drawable.loading_pic)
                .showImageOnLoading(R.drawable.loading_pic)
                .cacheInMemory(true)
                .cacheOnDisk(true)
//                .displayer(new RoundedBitmapDisplayer(10))
                .build();
        //初始化腾讯Bugly
        CrashReport.initCrashReport(getApplication().getApplicationContext(), "900053571", false);
    }

    public static Context getContext() {
        return mContext;
    }

    public void registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks callback) {
        getApplication().registerActivityLifecycleCallbacks(callback);
    }
}

上面是我接入tinker的一个小项目的Application,将Application的oncreate()方法直接移入ApplicationLike中,在onBaseContextAttached()方法中对tinker进行初始化操作。然后将所有关于Application的引用换成ApplicationLike.getApplication()即可

完成上述操作,配置部分就已经基本完成,接下来就可以测试一下打包更新了。

打包测试

根据配置文件在Gradle的build中选择合适的打包方式(上面图中有标注),打包后未修改默认输出路径的情况下会在工程app->build目录下生产bakAPK文件夹和outputs文件夹。

注意bakapk和outputs
注意bakapk和outputs
  • a 备份bakAPK中的三个文件,很重要!生成补丁包的时候需要的
  • b outputs-->abp-->app-release.apk为需要发布的版本
  • c 打包发布版补丁:将bakApk中的文件配置到build.gradle中
    /**
 * you can use assembleRelease to build you base apk
 * use tinkerPatchRelease -POLD_APK=  -PAPPLY_MAPPING=  -PAPPLY_RESOURCE= to build patch
 * add apk from the build/bakApk
 */
ext {
    //for some reason, you may want to ignore tinkerBuild, such as instant run debug build?
    tinkerEnabled = true
    //you should bak the following files
    //old apk file to build patch apk
    //app-debug-1017-11-29-32.apk
    tinkerOldApkPath = "${bakPath}/app-debug-1024-20-00-21.apk"
    //proguard mapping file to build patch apk
    tinkerApplyMappingPath = "${bakPath}/app-release-1024-20-02-47-mapping.txt"
    //resource R.txt to build patch apk, must input if there is resource changed
    tinkerApplyResourcePath = "${bakPath}/app-debug-1024-20-00-21-R.txt"
}

只修改了方法的情况下只需配置OldApkPath即可,修改了资源文件时还需要配置mapping.txt和R.txt

  • d 选择Gradle-->tinker-->tinkerPatchRelease/Debug打包生成补丁,打包后会在outputs中生成tinkerPatch文件夹,将生成的patch_signed.apk或者patch_signed_7zip.apk(选择小的)推送到手机的文件系统中(文件名可以不以apk结尾)。
  • e 在服务中或者通过事件调用加载补丁
        loadPatchButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), Environment.getExternalStorageDirectory().getAbsolutePath() + "/seuic/kp/patch_signed_7zip.apk");
            }
        });

默认情况下初始化tinker有两种方式查看TinkerInstaller类的install()方法可以发现未指定ResultService时使用DefaultResultService,加载成功后会自动结束进程应用直接关闭。显然不太友好,因此我们可以继承DefaultResultService然后重写他的onPatchResult()方法,来达到在加载完成补丁后进行的操作(弹个对话框让用户重启,因为补丁加载后需进程重启才有效果)。

  • f 一个基础包可以打多次补丁,打不定时配置中的oldApk不变始终为基础包,不能使用前面的补丁包作为后面补丁的oldApk。

上面就是接入tinker热更新的全部步骤。需要更多的场景或者接入按上面方式不适用可以去研究一下tinker的源码和仔细看一看github上的wiki文档跟常见问题一般都能解决

Tinker仓库地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容