[Android]腾讯Tinker热修复框架简单使用

阅读: 评论:0

[Android]腾讯Tinker热修复框架简单使⽤
前⾔
⽬前我们所知的热修复⽅案有阿⾥的AndFix、美团的Robust以及QZone的超级补丁⽅案,还有本篇的Tinker,如何在我们的⾃开发的软件上选⽤合适的⽅案呢?
先看看各家的框架效能对⽐,在作参考。
总体来说:
1. AndFix作为native解决⽅案,⾸先⾯临的是稳定性与兼容性问题,更重要的是它⽆法实现类替换,它是需要⼤量额外的开发成本的;
2. Robust兼容性与成功率较⾼,但是它与AndFix⼀样,⽆法新增变量与类只能⽤做的bugFix⽅案;
3. Qzone⽅案可以做到发布产品功能,但是它主要问题是插桩带来Dalvik的性能问题,以及为了解决Art下内存地址问题⽽导致补丁包急速增⼤的。
可以看出,Tinker热补丁⽅案既⽀持类、So和资源的替换,还⽀持了2.x-7.x平台。我们不仅可以⽤做bugfix,甚⾄可以替代功能的发布,况且Tinker已经在数亿Android端的上运⾏使⽤,这个噱头你还不使⽤说明
Tinker提供了两种接⼊⽅式,gradle接⼊和命令⾏接⼊,这⾥先说明gradle的⽅式,这也是⽐较推荐的⽅式。
1.在项⽬的adle中,添加tinker-patch-gradle-plugin的依赖:
1. buildscript {
2.    dependencies {
3.        classpath ('t.tinker:tinker-patch-gradle-plugin:1.7.10')
4.    }
5. }
2.然后在app的gradle⽂件adle,我们需要添加tinker的库依赖以及apply tinker的gradle插件.
1. dependencies {
2.  //可选,⽤于⽣成application类
3.  provided('t.tinker:tinker-android-anno:1.7.10')
4.    //tinker的核⼼库
5.    compile('t.tinker:tinker-android-lib:1.7.10')
6. }
1. //apply tinker插件
2. apply plugin: 't.tinker.patch'
3.在/adle中加⼊tinkerPatch task 脚本,
1. def bakPath = file("${buildDir}/bakApk/")
2.
3. ext {
4.    //for some reason, you may want to ignore tinkerBuild, such as instant run debug build?
5.    tinkerEnabled = true
6.
7.    //for normal build
8.    //old apk file to build patch apk
9.    tinkerOldApkPath = "${bakPath}/app-release-0601-14-29-28.apk"
10.    //proguard mapping file to build patch apk
11.    tinkerApplyMappingPath = "${bakPath}/"
12.    // to build patch apk, must input if there is resource changed
13.    tinkerApplyResourcePath = "${bakPath}/"
14.
15.    //only use for build all flavor, if not, just ignore this field
16.    // tinkerBuildFlavorDirectory = "${bakPath}/app-0526-17-40-51"
17. }
18.
19. //这个⽅法其实就是定义了⼀个tink_id
20. def getSha() {
21.    try {
22.        String tinkId = "tink_id_000000000"
23.        if (tinkId == null) {
24.            throw new RuntimeException("you should add tinkeId to system path or just input test value, such as 'testTinkerId'")
25.        }
26.        return tinkId
27.    } catch (Exception e) {
28.        throw new RuntimeException("you should add tinkeId to system path or just input test value, such as 'testTinkerId'")
29.    }
链式运输机30. }
30. }
31.
32.
33. def getOldApkPath() {
34.    return hasProperty("OLD_APK") ? OLD_APK : ext.tinkerOldApkPath
35. }
36.
37. def getApplyMappingPath() {
38.    return hasProperty("APPLY_MAPPING") ? APPLY_MAPPING : ext.tinkerApplyMappingPath
39. }
40.
41. def getApplyResourceMappingPath() {
42.    return hasProperty("APPLY_RESOURCE") ? APPLY_RESOURCE : ext.tinkerApplyResourcePath
43. }
44.
45. def getTinkerIdValue() {
46.    return hasProperty("TINKER_ID") ? TINKER_ID : getSha()
47. }
48.
49. def buildWithTinker() {
50.    return hasProperty("TINKER_ENABLE") ? TINKER_ENABLE : ext.tinkerEnabled
51. }
52.
53. def getTinkerBuildFlavorDirectory() {
54.    return ext.tinkerBuildFlavorDirectory
55. }
冯代存
56.
57. if (buildWithTinker()) {
58.    apply plugin: 't.tinker.patch'
59.
60.    tinkerPatch {
61.        /**
62.          * necessary,default 'null'
63.          * the old apk path, use to diff with the new apk to build
64.          * add apk from the build/bakApk
65.          */
66.        oldApk = getOldApkPath()
67.        /**
68.          * optional,default 'false'
69.          * there are some cases we may get some warnings
70.          * if ignoreWarning is true, we would just assert the patch process
71.          * case 1: minSdkVersion is below 14, but you are using dexMode with raw.
72.          *        it must be crash when load.
73.          * case 2: newly added Android Component l,
74.          *        it must be crash when load.
75.          * case 3: loader classes in dex.loader{} are not keep in the main dex,
76.          *        it must be let tinker not work.
77.          * case 4: loader classes in dex.loader{} changes,
78.          *        loader classes is ues to load patch dex. it is useless to change them.
79.          *        it won't crash, but these changes can't effect. you may ignore it
80.          * case 5: resources.arsc has changed, but we don't use applyResourceMapping to build
81.          */
82.        ignoreWarning = false
83.
84.        /**
85.          * optional,default 'true'
86.          * whether sign the patch file
87.          * if not, you must do yourself. otherwise it can't check success during the patch loading
88.          * we will use the sign config with your build type
89.          */
90.        useSign = true
91.
92.        /**
93.          * optional,default 'true'
94.          * whether use tinker to build
95.          */
96.        tinkerEnable = buildWithTinker()
97.
98.        /**
99.          * Warning, applyMapping will affect the normal android build!
100.          */
101.        buildConfig {
102.            /**
103.              * optional,default 'null'
104.              * if we use tinkerPatch to build the patch apk, you'd better to apply the old
105.              * apk mapping file if minifyEnabled is enable!
106.              * Warning:
107.              * you must be careful that it will affect the normal assemble build!
108.              */
109.            applyMapping = getApplyMappingPath()
110.            /**
111.              * optional,default 'null'
112.              * It is nice to keep the resource id file to reduce java changes
113.              */
114.            applyResourceMapping = getApplyResourceMappingPath()
115.
116.            /**
117.              * necessary,default 'null'
118.              * because we don't want to check the base apk with md5 in the runtime(it is slow)
119.              * tinkerId is use to identify the unique base apk when the patch is tried to apply.
120.              * we can use git rev, svn rev or simply versionCode.
120.              * we can use git rev, svn rev or simply versionCode.
121.              * we will gen the tinkerId in your manifest automatic
122.              */
123.            tinkerId = getTinkerIdValue()
124.
125.            /**
126.              * if keepDexApply is true, class in which dex refer to the old apk.
127.              * open this can reduce the dex diff file size.
128.              */
129.            keepDexApply = false
130.
131.            /**
132.              * optional, default 'false'
133.              * Whether tinker should treat the base apk as the one being protected by app
134.              * protection tools.
135.              * If this attribute is true, the generated patch package will contain a
136.              * dex including all changed classes instead of any dexdiff patch-info files.
137.              */
138.            isProtectedApp = false
139.        }
140.
141.        dex {
142.            /**
143.              * optional,default 'jar'
144.              * only can be 'raw' or 'jar'. for raw, we would keep its original format
145.              * for jar, we would repack dexes with zip format.
146.              * if you want to support below 14, you must use jar
147.              * or you want to save rom or check quicker, you can use raw mode also
148.              */
149.            dexMode = "jar"
150.
151.            /**
152.              * necessary,default '[]'
153.              * what dexes in apk are expected to deal with tinkerPatch
154.              * it support * or ? pattern.
155.              */
156.            pattern = ["classes*.dex",
157.                        "assets/secondary-dex-?.jar"]
158.            /**
159.              * necessary,default '[]'
160.              * Warning, it is very very important, loader classes can't change with patch.
161.              * thus, they will be removed from patch dexes.
162.              * you must put the following class into main dex.
163.              * Simply, you should add your own application {@code tinker.sample.android.SampleApplication} 164.              * own tinkerLoader, and the classes you use in them
165.              *
166.              */
167.            loader = [
168.                    //use sample, let BaseBuildInfo unchangeable with tinker
169.                    "t.tinker.loader.*",
170.                    "t.tinker.*",
171.                    "app.MyApplication"
172.            ]
173.        }
174.
175.        lib {
176.            /**
177.              * optional,default '[]'
178.              * what library in apk are expected to deal with tinkerPatch
179.              * it support * or ? pattern.
180.              * for library in assets, we would just recover them in the patch directory
181.              * you can get them in TinkerLoadResult with Tinker
182.              */
183.            pattern = ["lib/*/*.so"]
184.        }
185.
186.        res {
187.            /**
188.              * optional,default '[]'
189.              * what resource in apk are expected to deal with tinkerPatch
190.              * it support * or ? pattern.
191.              * you must include all your resources in apk here,
192.              * otherwise, they won't repack in the new apk resources.
193.              */
194.            pattern = ["res/*", "assets/*", "resources.arsc", "l"]
195.
196.            /**
197.              * optional,default '[]'
198.              * the resource file exclude patterns, ignore add, delete or modify resource change
199.              * it support * or ? pattern.
滤菌器200.              * Warning, we can only use for files no relative with resources.arsc
201.              */
202.            ignoreChange = ["assets/"]
203.
204.            /**
205.              * default 100kb
206.              * for modify resource, if it is larger than 'largeModSize'
207.              * we would like to use bsdiff algorithm to reduce patch file size
208.              */
209.            largeModSize = 100
210.        }
210.        }
211.
212.        packageConfig {
213.            /**
214.              * optional,default 'TINKER_ID, TINKER_ID_VALUE' 'NEW_TINKER_ID, NEW_TINKER_ID_VALUE'
215.              * package meta file gen. path is assets/ in patch file
216.              * you can PackageProperties() in your ownPackageCheck method
217.              * PackageConfigByName
218.              * we will get the TINKER_ID from the old apk manifest for you automatic,
219.              * other config files (such as patchMessage below)is not necessary
220.              */
221.            configField("TINKER_ID", getSha())
222.            configField("app_name", "MyApp")
223.            configField("patchMessage", "这是⼀个测试");
224.            /**
225.              * just a sample case, you can use such as sdkVersion, brand,
226.              * you can parse it in the SamplePatchListener.
227.              * Then you can use patch conditional!
228.              */
229.            configField("platform", "all")
230.            /**
231.              * patch version via packageConfig
232.              */
233.            configField("patchVersion", "1.0")
234.        }
235.        //or you can add config filed outside, or get meta value from old apk
236.        //project.figField("test1", project.MetaDataFromOldApk("Test")) 237.        //project.figField("test2", "sample")
238.
239.        /**
240.          * if you don't use zipArtifact or path, we just use 7za to try
241.          */
242.        sevenZip {
243.            /**
244.              * optional,default '7za'
245.              * the 7zip artifact path, it will use the right 7za with your platform
246.              */
247.            zipArtifact = ":SevenZip:1.1.10"
248.            /**
249.              * optional,default '7za'
250.              * you can specify the 7za path yourself, it will overwrite the zipArtifact value
251.              */
252. //        path = "/usr/local/bin/7za"
253.        }
254.    }
255.
256.    List<String> flavors = new ArrayList<>();
257.    project.android.productFlavors.each {flavor ->
258.        flavors.add(flavor.name)
259.    }
260.    boolean hasFlavors = flavors.size() > 0
261.    def date = new Date().format("MMdd-HH-mm-ss")
262.
263.    /**
264.      * bak apk and mapping
265.      */
266.    android.applicationVariants.all { variant ->
267.        /**
268.          * task type, you want to bak
269.          */
270.        def taskName = variant.name
271.
272.        tasks.all {
273.            if ("assemble${taskName.capitalize()}".equalsIgnoreCase(it.name)) {
274.
275.                it.doLast {
276.                    copy {
277.                        def fileNamePrefix = "${project.name}-${variant.baseName}"
278.                        def newFileNamePrefix = hasFlavors ? "${fileNamePrefix}" : "${fileNamePrefix}-${date}"
279.
280.                        def destPath = hasFlavors ? file("${bakPath}/${project.name}-${date}/${variant.flavorName}") : bakPath
281.                        from variant.outputs.outputFile
282.                        into destPath
283.                        rename { String fileName ->
284.                            place("${fileNamePrefix}.apk", "${newFileNamePrefix}.apk")
285.                        }
286.
287.                        from "${buildDir}/outputs/mapping/${variant.dirName}/"
288.                        into destPath
289.                        rename { String fileName ->
290.                            place("", "${newFileNamePrefix}-")
291.                        }
292.
293.                        from "${buildDir}/intermediates/symbols/${variant.dirName}/R.txt"
294.                        into destPath
295.                        rename { String fileName ->
296.                            place("R.txt", "${newFileNamePrefix}-R.txt")
297.                        }
298.                    }
299.                }
300.            }
301.        }
302.    }
303.    project.afterEvaluate {
304.        //sample use for build all flavor for one time
305.        if (hasFlavors) {
306.            task(tinkerPatchAllFlavorRelease) {
307.                group = 'tinker'
308.                def originOldPath = getTinkerBuildFlavorDirectory()
309.                for (String flavor : flavors) {
310.                    def tinkerTask = ByName("tinkerPatch${flavor.capitalize()}Release")
311.                    dependsOn tinkerTask
312.                    def preAssembleTask = ByName("process${flavor.capitalize()}ReleaseManifest")
313.                    preAssembleTask.doFirst {
314.                        String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 15)
315.                        project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release.apk"4g手机电子围栏
316.                        project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-"
317.                        project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-"
318.                    }
319.                }
320.            }
321.
322.            task(tinkerPatchAllFlavorDebug) {
323.                group = 'tinker'
324.                def originOldPath = getTinkerBuildFlavorDirectory()
325.                for (String flavor : flavors) {
326.                    def tinkerTask = ByName("tinkerPatch${flavor.capitalize()}Debug")
327.                    dependsOn tinkerTask
328.                    def preAssembleTask = ByName("process${flavor.capitalize()}DebugManifest")
329.                    preAssembleTask.doFirst {
330.                        String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 13)
331.                        project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug.apk"
332.                        project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-"
333.                        project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-"
334.                    }
335.                }
336.            }
337.        }
338.    }
339. }
注意,minifyEnabled⼀定要设置为true,别忘了设置tinkerId,在getSha⽅法⾥可以先写死。
代码⾥我们要做点什么呢?我需要先⾃定义Application类,因为引⼊了Tinker,所以原来的MyApplication必须改造。
涂改液程序启动时会加载默认的Application类,这导致我们补丁包是⽆法对它做修改了。如何规避?在这⾥我们并没有使⽤类似InstantRun hook Application的⽅式,⽽是通过代码框架的⽅式来避免,这也是为了升框架的兼容性。这⾥我们要实现的是完全将原来的Application类隔离起来,即其他任何类都不能再引⽤我们⾃⼰的Application。我们需要做的其实是以下⼏个⼯作:
1. 将我们⾃⼰Application类以及它的继承类的所有代码拷贝到⾃⼰的ApplicationLike继承类中,例如MyApplicationLike。你也可以直接将⾃⼰的Application改为继承ApplicationLike;
2. Application的attachBaseContext⽅法实现要单独移动到onBaseContextAttached中;
3. 对ApplicationLike中,引⽤application的地⽅改成getApplication();
4. 对其他引⽤Application或者它的静态对象与⽅法的地⽅,改成引⽤ApplicationLike的静态对象与⽅法;
我的demo中原来是⾃定义的MyApplication,现在必须把之前的Application中定义的变量转移到⾃定义的ApplicationLike中去,然后⾃动⽣成MyApplication,这⾥使⽤Annotation⽣成Application类(推荐)。
MyApplicationLike.java
1. app;
2.
3. import android.app.Application;
4. t.Context;
5. t.Intent;
6.
7. app.db.dao.DaoMaster;
8. app.db.dao.DaoSession;
9. app.event.MyEventBusIndex;
10. import com.jan.lib.BusPoster;
11. t.tinker.anno.DefaultLifeCycle;
12. t.tinker.lib.tinker.TinkerInstaller;
13. t.tinker.loader.app.DefaultApplicationLike;
14. t.tinker.loader.shareutil.ShareConstants;
15.
16. dao.database.Database;
17.
18. /**
19.  * Created by Jan on 2017/5/25.
20.  */
21. @DefaultLifeCycle(
22.        application = ".MyApplication",
23.        flags = ShareConstants.TINKER_ENABLE_ALL, ////tinkerFlags, tinker⽀持的类型,dex,library,还是全部都⽀持!
24.        loaderClass = "t.tinker.loader.TinkerLoader",//loaderClassName, 我们这⾥使⽤默认即可!
25.        loadVerifyFlag = false)    //tinkerLoadVerifyFlag
26. public class MyApplicationLike extends DefaultApplicationLike {
27.
28.    public static final boolean ENCRYPTED = false;
29.    private DaoSession mDaoSession;
30.    public static Context mContext;wntc
31.
32.    public MyApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) {
33.        super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);
34.    }
35.

本文发布于:2023-07-27 08:37:38,感谢您对本站的认可!

本文链接:https://patent.en369.cn/patent/4/194278.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:问题   框架   继承   定义
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 369专利查询检索平台 豫ICP备2021025688号-20 网站地图