当前位置:   article > 正文

Tinker 使用_tinker 重启intent

tinker 重启intent

       Tinker时微信推出的热修复框架,优点就是很稳定,可以gradle打包,缺点是这个修复不是实时的,需要重启。这是由于其实现的原理决定的,简单的说他是通过把生成的不定apk,加载进来,通过与基线apk的整合生成新的dex集合,最后生成oat文件。这只是整合成的文件而已,并没有影响ClassLoader的加载既存在的dex类。而重启之后Tinker会通过反射获取BaseDexClassloder对象,然后在获取里面的PathDexList对象中的Excument[]数据,这里面放的都是dex,这时候我们会把整合好的补丁dex插入到数据的前端,当ClassLoder进行加载使用的时候就会取到。也就是说重启是为了让整合的dex补丁插入进去。

   下面来简单说下步骤:

1.加依赖

  1. buildscript {
  2. repositories {
  3. google()
  4. jcenter()
  5. maven {
  6. url 'https://maven.google.com/'
  7. name 'Google'
  8. }
  9. }
  10. dependencies {
  11. classpath 'com.android.tools.build:gradle:3.1.2'
  12. classpath("com.tencent.tinker:tinker-patch-gradle-plugin:${TINKER_VERSION}") {
  13. changing = TINKER_VERSION?.endsWith("-SNAPSHOT")
  14. exclude group: 'com.android.tools.build', module: 'gradle'
  15. }
  16. // NOTE: Do not place your application dependencies here; they belong
  17. // in the individual module build.gradle files
  18. }
  19. }
  20. allprojects {
  21. repositories {
  22. google()
  23. jcenter()
  24. maven {
  25. url 'https://maven.google.com/'
  26. name 'Google'
  27. }
  28. }
  29. }
  30. task clean(type: Delete) {
  31. delete rootProject.buildDir
  32. }
  33. def is_gradle_3() {
  34. return hasProperty('GRADLE_3') && GRADLE_3.equalsIgnoreCase('TRUE')
  35. }

 

2.依赖

  1. apply plugin: 'com.android.application'
  2. def javaVersion = JavaVersion.VERSION_1_7
  3. android {
  4. compileSdkVersion 28
  5. defaultConfig {
  6. applicationId "com.example.liukang.mytinkerproject"
  7. minSdkVersion 19
  8. targetSdkVersion 28
  9. versionCode 1
  10. versionName "2.0"
  11. /**
  12. * you can use multiDex and install it in your ApplicationLifeCycle implement
  13. */
  14. // multiDexEnabled true
  15. }
  16. compileOptions {
  17. sourceCompatibility javaVersion
  18. targetCompatibility javaVersion
  19. }
  20. //recommend
  21. dexOptions {
  22. jumboMode = true
  23. }
  24. signingConfigs {
  25. release {
  26. storeFile file("sign/release.keystore")
  27. storePassword "123456"
  28. keyAlias "123456"
  29. keyPassword "123456"
  30. }
  31. debug {
  32. storeFile file("sign/release.keystore")
  33. storePassword "123456"
  34. keyAlias "123456"
  35. keyPassword "123456"
  36. }
  37. }
  38. buildTypes {
  39. release {
  40. minifyEnabled true
  41. signingConfig signingConfigs.release
  42. proguardFiles getDefaultProguardFile('proguard-android.txt'), project.file('proguard-rules.pro')
  43. }
  44. debug {
  45. debuggable true
  46. minifyEnabled true
  47. signingConfig signingConfigs.debug
  48. proguardFiles getDefaultProguardFile('proguard-android.txt'), project.file('proguard-rules.pro')
  49. }
  50. }
  51. sourceSets {
  52. main {
  53. jniLibs.srcDirs = ['libs']
  54. }
  55. }
  56. }
  57. def bakPath = file("${buildDir}/bakApk/")
  58. dependencies {
  59. implementation fileTree(dir: 'libs', include: ['*.jar'])
  60. testImplementation 'junit:junit:4.12'
  61. implementation "com.android.support:appcompat-v7:28.0.0+"
  62. api("com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}") { changing = true }
  63. // Maven local cannot handle transist dependencies.
  64. implementation("com.tencent.tinker:tinker-android-loader:${TINKER_VERSION}") { changing = true }
  65. annotationProcessor("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") {
  66. changing = true
  67. }
  68. compileOnly("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") { changing = true }
  69. implementation "com.android.support:multidex:1.0.1"
  70. implementation 'com.orhanobut:hawk:2.0.1'
  71. }
  72. /**
  73. * 使用Tinker的一些常量配置
  74. */
  75. ext {
  76. //for some reason, you may want to ignore tinkerBuild, such as instant run debug build?
  77. tinkerEnabled = true
  78. //for normal build
  79. //old apk file to build patch apk
  80. tinkerOldApkPath = "${bakPath}/app-release-0317-18-54-47.apk"
  81. //proguard mapping file to build patch apk混淆文件
  82. tinkerApplyMappingPath = "${bakPath}/app-release-0317-18-54-47-mapping.txt"
  83. //resource R.txt to build patch apk, must input if there is resource changed资源路径
  84. tinkerApplyResourcePath = "${bakPath}/app-release-0317-18-54-47-R.txt"
  85. //only use for build all flavor, if not, just ignore this field
  86. tinkerBuildFlavorDirectory = "${bakPath}/"
  87. }
  88. /**
  89. * Tinker的全剧配置
  90. */
  91. if (buildWithTinker()) {
  92. apply plugin: 'com.tencent.tinker.patch'
  93. tinkerPatch {
  94. /**
  95. * 第一次生成的基线包
  96. */
  97. oldApk = getOldApkPath()
  98. /**
  99. * default 'false'
  100. * 如果出现以下的情况,并且ignoreWarning为false,我们将中断编译。因为这些情况可能会导致编译出来的patch包带来风险:
  101. 1. minSdkVersion小于14,但是dexMode的值为"raw";
  102. 2. 新编译的安装包出现新增的四大组件(Activity, BroadcastReceiver...);
  103. 3. 定义在dex.loader用于加载补丁的类不在main dex中;
  104. 4. 定义在dex.loader用于加载补丁的类出现修改;
  105. 5. resources.arsc改变,但没有使用applyResourceMapping编译。
  106. */
  107. ignoreWarning = false
  108. /**
  109. * 在运行过程中,我们需要验证基准apk包与补丁包的签名是否一致,我们是否需要为你签名。
  110. */
  111. useSign = true
  112. /**
  113. * optional,default 'true'
  114. * 是否打开Tinker功能
  115. */
  116. tinkerEnable = buildWithTinker()
  117. /**
  118. * 编译相关的配置项
  119. */
  120. buildConfig {
  121. /**
  122. * optional,default 'null'
  123. * 在编译新的apk时候,我们希望通过保持旧apk的proguard混淆方式,
  124. * 从而减少补丁包的大小。这个只是推荐设置,
  125. * 不设置applyMapping也不会影响任何的assemble编译。
  126. */
  127. // applyMapping = getApplyMappingPath()
  128. /**
  129. * optional,default 'null'
  130. * 可选参数;在编译新的apk时候,我们希望通过旧apk的R.txt文件保持ResId的分配
  131. * ,这样不仅可以减少补丁包的大小,同时也避免由于ResId改变导致remote view异常
  132. */
  133. applyResourceMapping = getApplyResourceMappingPath()
  134. /**
  135. * necessary,default 'null'
  136. * 在运行过程中,我们需要验证基准apk包的tinkerId是否等于补丁包的tinkerId。
  137. * 这个是决定补丁包能运行在哪些基准包上面,一般来说我们可以使用git版本号
  138. * 、versionName等等。
  139. */
  140. tinkerId = getTinkerIdValue()
  141. /**
  142. * 如果我们有多个dex,编译补丁时可能会由于类的移动导致变更增多。
  143. * 若打开keepDexApply模式,补丁包将根据基准包的类分布来编译
  144. */
  145. keepDexApply = true
  146. /**
  147. * optional, default 'false'
  148. * 是否使用加固模式,仅仅将变更的类合成补丁。注意,这种模式仅仅可以用于加固应用中。
  149. */
  150. isProtectedApp = false
  151. /**
  152. * optional, default 'false'
  153. * 是否支持新增非export的Activity
  154. * <b>Notice that currently this feature is incubating and only support NON-EXPORTED Activity</b>
  155. */
  156. supportHotplugComponent = false
  157. }
  158. dex {
  159. /**
  160. * optional,default 'jar'
  161. * 只能是'raw'或者'jar'。
  162. 对于'raw'模式,我们将会保持输入dex的格式。
  163. 对于'jar'模式,我们将会把输入dex重新压缩封装到jar。
  164. 如果你的minSdkVersion小于14,你必须选择‘jar’模式,而且它更省存储空间,
  165. 但是验证md5时比'raw'模式耗时。默认我们并不会去校验md5,一般情况下选择jar模式即可。
  166. */
  167. dexMode = "jar"
  168. /**
  169. * necessary,default '[]'
  170. * what dexes in apk are expected to deal with tinkerPatch
  171. * it support * or ? pattern.
  172. */
  173. pattern = ["classes*.dex",
  174. "assets/secondary-dex-?.jar"]
  175. /**
  176. * necessary,default '[]'
  177. * Warning, it is very very important, loader classes can't change with patch.
  178. * thus, they will be removed from patch dexes.
  179. * you must put the following class into main dex.
  180. * Simply, you should add your own application {@code tinker.sample.android.SampleApplication}
  181. * own tinkerLoader, and the classes you use in them
  182. *
  183. */
  184. loader = [
  185. //use sample, let BaseBuildInfo unchangeable with tinker
  186. "tinker.sample.android.app.BaseBuildInfo"
  187. ]
  188. }
  189. lib {
  190. /**
  191. * optional,default '[]'
  192. * 需要处理lib路径,
  193. * 支持*、?通配符,必须使用'/'分割。与dex.pattern一致,
  194. * 路径是相对安装包的,例如assets/...
  195. */
  196. pattern = ["lib/*/*.so"]
  197. }
  198. res {
  199. /**
  200. * optional,default '[]'
  201. * 需要处理res路径,支持*、?通配符,必须使用'/'分割。
  202. * 与dex.pattern一致, 路径是相对安装包的,例如assets/...,务必注意的是,
  203. * 只有满足pattern的资源才会放到合成后的资源包
  204. */
  205. pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
  206. /**
  207. * optional,default '[]'
  208. * 支持*、?通配符,必须使用'/'分割。若满足ignoreChange的pattern,
  209. * 在编译时会忽略该文件的新增、删除与修改。 最极端的情况,
  210. * ignoreChange与上面的pattern一致,即会完全忽略所有资源的修改。
  211. ignoreChange = ["assets/sample_meta.txt"]
  212. /**
  213. * default 100kb
  214. * 如果大于largeModSize,我们将使用bsdiff算法。
  215. * 这可以降低补丁包的大小,但是会增加合成时的复杂度。默认大小为100kb
  216. */
  217. largeModSize = 100
  218. }
  219. packageConfig {
  220. /**
  221. * optional,default 'TINKER_ID, TINKER_ID_VALUE' 'NEW_TINKER_ID, NEW_TINKER_ID_VALUE'
  222. * package meta file gen. path is assets/package_meta.txt in patch file
  223. * you can use securityCheck.getPackageProperties() in your ownPackageCheck method
  224. * or TinkerLoadResult.getPackageConfigByName
  225. * we will get the TINKER_ID from the old apk manifest for you automatic,
  226. * other config files (such as patchMessage below)is not necessary
  227. */
  228. configField("patchMessage", "tinker is sample to use")
  229. /**
  230. * just a sample case, you can use such as sdkVersion, brand, channel...
  231. * you can parse it in the SamplePatchListener.
  232. * Then you can use patch conditional!
  233. */
  234. configField("platform", "all")
  235. /**
  236. * patch version via packageConfig
  237. */
  238. configField("patchVersion", "1.0")
  239. }
  240. //or you can add config filed outside, or get meta value from old apk
  241. //project.tinkerPatch.packageConfig.configField("test1", project.tinkerPatch.packageConfig.getMetaDataFromOldApk("Test"))
  242. //project.tinkerPatch.packageConfig.configField("test2", "sample")
  243. /**
  244. * if you don't use zipArtifact or path, we just use 7za to try
  245. */
  246. sevenZip {
  247. /**
  248. * optional,default '7za'
  249. * the 7zip artifact path, it will use the right 7za with your platform
  250. */
  251. zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
  252. /**
  253. * optional,default '7za'
  254. * you can specify the 7za path yourself, it will overwrite the zipArtifact value
  255. */
  256. // path = "/usr/local/bin/7za"
  257. }
  258. }
  259. List<String> flavors = new ArrayList<>();
  260. project.android.productFlavors.each { flavor ->
  261. flavors.add(flavor.name)
  262. }
  263. boolean hasFlavors = flavors.size() > 0
  264. def date = new Date().format("MMdd-HH-mm-ss")
  265. /**
  266. * bak apk and mapping
  267. */
  268. android.applicationVariants.all { variant ->
  269. /**
  270. * task type, you want to bak
  271. */
  272. def taskName = variant.name
  273. tasks.all {
  274. if ("assemble${taskName.capitalize()}".equalsIgnoreCase(it.name)) {
  275. it.doLast {
  276. copy {
  277. def fileNamePrefix = "${project.name}-${variant.baseName}"
  278. def newFileNamePrefix = hasFlavors ? "${fileNamePrefix}" : "${fileNamePrefix}-${date}"
  279. def destPath = hasFlavors ? file("${bakPath}/${project.name}-${date}/${variant.flavorName}") : bakPath
  280. from variant.outputs.first().outputFile
  281. into destPath
  282. rename { String fileName ->
  283. fileName.replace("${fileNamePrefix}.apk", "${newFileNamePrefix}.apk")
  284. }
  285. from "${buildDir}/outputs/mapping/${variant.dirName}/mapping.txt"
  286. into destPath
  287. rename { String fileName ->
  288. fileName.replace("mapping.txt", "${newFileNamePrefix}-mapping.txt")
  289. }
  290. from "${buildDir}/intermediates/symbols/${variant.dirName}/R.txt"
  291. into destPath
  292. rename { String fileName ->
  293. fileName.replace("R.txt", "${newFileNamePrefix}-R.txt")
  294. }
  295. }
  296. }
  297. }
  298. }
  299. }
  300. }
  301. def getOldApkPath() {
  302. return hasProperty("OLD_APK") ? OLD_APK : ext.tinkerOldApkPath
  303. }
  304. def getApplyMappingPath() {
  305. return hasProperty("APPLY_MAPPING") ? APPLY_MAPPING : ext.tinkerApplyMappingPath
  306. }
  307. def getApplyResourceMappingPath() {
  308. return hasProperty("APPLY_RESOURCE") ? APPLY_RESOURCE : ext.tinkerApplyResourcePath
  309. }
  310. def getTinkerIdValue() {
  311. return hasProperty("TINKER_ID") ? TINKER_ID : android.defaultConfig.versionName
  312. }
  313. def buildWithTinker() {
  314. return hasProperty("TINKER_ENABLE") ? Boolean.parseBoolean(TINKER_ENABLE) : ext.tinkerEnabled
  315. }
  316. def getTinkerBuildFlavorDirectory() {
  317. return ext.tinkerBuildFlavorDirectory
  318. }

3.创建代理的MyTinkerApplication

  1. package com.example.liukang.mytinkerproject;
  2. import android.app.Application;
  3. import android.content.Context;
  4. import android.content.Intent;
  5. import android.support.multidex.MultiDex;
  6. import com.orhanobut.hawk.Hawk;
  7. import com.tencent.tinker.anno.DefaultLifeCycle;
  8. import com.tencent.tinker.entry.ApplicationLike;
  9. import com.tencent.tinker.entry.DefaultApplicationLike;
  10. import com.tencent.tinker.loader.shareutil.ShareConstants;
  11. @SuppressWarnings("unused")
  12. @DefaultLifeCycle(application = ".MyTinkerApplication" ,
  13. flags = ShareConstants.TINKER_ENABLE_ALL,
  14. loadVerifyFlag = false)//都是官方要求这么写的
  15. public class SampleApplicationLike extends ApplicationLike {
  16. public SampleApplicationLike(Application application,
  17. int tinkerFlags,
  18. boolean tinkerLoadVerifyFlag,
  19. long applicationStartElapsedTime,
  20. long applicationStartMillisTime,
  21. Intent tinkerResultIntent) {
  22. super(application,
  23. tinkerFlags,
  24. tinkerLoadVerifyFlag,
  25. applicationStartElapsedTime,
  26. applicationStartMillisTime,
  27. tinkerResultIntent);
  28. }
  29. @Override
  30. public void onBaseContextAttached(Context base) {
  31. super.onBaseContextAttached(base);
  32. TinkerManager.installedTinker(this);
  33. }
  34. @Override
  35. public void onCreate() {
  36. super.onCreate();
  37. Hawk.init(getApplication()).build();
  38. }
  39. }

4.TinkerManager对Tinker进行管理,这里面可以将重写的如DefaultResultService等一些自定义处理的东西,在这里面通过Tinker 进行初始化

  1. package com.example.liukang.mytinkerproject;
  2. import android.content.Context;
  3. import com.tencent.tinker.entry.ApplicationLike;
  4. import com.tencent.tinker.lib.patch.UpgradePatch;
  5. import com.tencent.tinker.lib.service.DefaultTinkerResultService;
  6. import com.tencent.tinker.lib.tinker.Tinker;
  7. import com.tencent.tinker.lib.tinker.TinkerInstaller;
  8. public class TinkerManager {
  9. private static boolean isInstalled = false;//是否已经初始化标志位
  10. private static ApplicationLike mApplicationLike;
  11. /**
  12. * 完成Tinker初始化
  13. *
  14. * @param applicationLike
  15. */
  16. public static void installedTinker(ApplicationLike applicationLike) {
  17. mApplicationLike = applicationLike;
  18. if (isInstalled) {
  19. return;
  20. }
  21. Tinker tinker = new Tinker.Builder(applicationLike.getApplication()).build();
  22. Tinker.create(tinker);
  23. tinker.install(applicationLike.getTinkerResultIntent(),MyTinkerService.class, new UpgradePatch());
  24. isInstalled = true;
  25. }
  26. /**
  27. * 完成patch文件的加载
  28. *
  29. * @param path 补丁文件路径
  30. */
  31. public static void loadPatch(String path) {
  32. if (Tinker.isTinkerInstalled()) {//是否已经安装过
  33. TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), path);
  34. }
  35. }
  36. /**
  37. * 利用Tinker代理Application 获取应用全局的上下文
  38. * @return 全局的上下文
  39. */
  40. private static Context getApplicationContext() {
  41. if (mApplicationLike != null)
  42. return mApplicationLike.getApplication().getApplicationContext();
  43. return null;
  44. }
  45. }

5.清单配置 需要修改application  的name

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3. package="com.example.liukang.mytinkerproject">
  4. <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
  5. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
  6. <application
  7. android:name=".MyTinkerApplication"
  8. android:allowBackup="true"
  9. android:icon="@mipmap/ic_launcher"
  10. android:label="@string/app_name"
  11. android:roundIcon="@mipmap/ic_launcher_round"
  12. android:supportsRtl="true"
  13. android:theme="@style/AppTheme">
  14. <service android:name=".MyTinkerService"
  15. android:exported="false"/>
  16. <activity android:name=".MainActivity">
  17. <intent-filter>
  18. <action android:name="android.intent.action.MAIN" />
  19. <category android:name="android.intent.category.LAUNCHER" />
  20. </intent-filter>
  21. </activity></application>
  22. </manifest>

6.生成基线

--》--》对应替换7.修改一下代码之后,在生成补丁包

===》这样便得到了

8.使用实例:

  1. public class MainActivity extends AppCompatActivity {
  2. private static final String TAG = "Tinker.MainActivity";
  3. private static final String FILE_END = ".apk";//文件后缀
  4. private String FILEDIR;//文件路径
  5. Activity activity;
  6. TextView showInfo;
  7. @Override
  8. protected void onCreate(Bundle savedInstanceState) {
  9. super.onCreate(savedInstanceState);
  10. setContentView(R.layout.activity_main);
  11. activity=this;
  12. createDir();
  13. Button loadPatchButton = findViewById(R.id.loadPatch);
  14. loadPatchButton.setOnClickListener(new View.OnClickListener() {
  15. @Override
  16. public void onClick(View v) {
  17. new PermissionHelper(activity).requestPermissions("请同意读取权限"
  18. , new PermissionHelper.PermissionListener() {
  19. @Override
  20. public void doAfterGrand(String... permission) {
  21. TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), getPatchName());
  22. }
  23. @Override
  24. public void doAfterDenied(String... permission) {
  25. Toast.makeText(activity, "没有权限", Toast.LENGTH_LONG).show();
  26. }
  27. }, Manifest.permission.READ_EXTERNAL_STORAGE
  28. , Manifest.permission.WRITE_EXTERNAL_STORAGE);
  29. }
  30. });
  31. Button cleanPatchButton = (Button) findViewById(R.id.cleanPatch);
  32. cleanPatchButton.setOnClickListener(new View.OnClickListener() {
  33. @Override
  34. public void onClick(View v) {
  35. Tinker.with(getApplicationContext()).cleanPatch();
  36. }
  37. });
  38. Button killSelfButton = (Button) findViewById(R.id.killSelf);
  39. killSelfButton.setOnClickListener(new View.OnClickListener() {
  40. @Override
  41. public void onClick(View v) {
  42. ShareTinkerInternals.killAllOtherProcess(getApplicationContext());
  43. android.os.Process.killProcess(android.os.Process.myPid());
  44. }
  45. });
  46. showInfo=findViewById(R.id.showInfo);
  47. showInfo.setText("哈哈哈哈哈!");
  48. }
  49. @Override
  50. protected void onResume() {
  51. Log.e(TAG, "i am on onResume");
  52. // Log.e(TAG, "i am on patch onResume");
  53. super.onResume();
  54. Utils.setBackground(false);
  55. }
  56. @Override
  57. protected void onPause() {
  58. super.onPause();
  59. Utils.setBackground(true);
  60. }
  61. public void createDir() {
  62. FILEDIR = Environment.getExternalStorageDirectory().getPath() + "/tpatch/";
  63. //创建路径对应的文件夹
  64. File file = new File(FILEDIR);
  65. if (!file.exists())
  66. file.mkdir();
  67. }
  68. public String getPatchName() {
  69. String s = FILEDIR.concat("tinker").concat(FILE_END);
  70. Log.e("TAG", s);
  71. return s;
  72. }
  73. }

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/701493
推荐阅读
相关标签
  

闽ICP备14008679号