Android微信Tinker热更新详细使用

Android微信Tinker热更新详细使用,第1张

概述先看一下效果图Tinker已知问题由于原理与系统限制,Tinker有以下已知问题:Tinker不支持修改AndroidManifest.xml,Tinker不支持新增四大组件;

先看一下效果图

Tinker已知问题

由于原理与系统限制,Tinker有以下已知问题:

Tinker不支持修改AndroIDManifest.xml,Tinker不支持新增四大组件; 由于Google Play的开发者条款限制,不建议在GP渠道动态更新代码; 在AndroID N上,补丁对应用启动时间有轻微的影响; 不支持部分三星androID-21机型,加载补丁时会主动抛出”TinkerRuntimeException:checkDexInstall Failed”; 由于各个厂商的加固实现并不一致,在1.7.6以及之后的版本,tinker不再支持加固的动态更新; 对于资源替换,不支持修改remoteVIEw。例如Transition动画,notification icon以及桌面图标。

1.首先在项目的build中,集成tinker插件 ,如下所示(目前最新版是1.7.6)

先看结构图,只有几个类而已:

项目中的build集成

buildscript { repositorIEs { jcenter() } dependencIEs { classpath 'com.androID.tools.build:gradle:2.2.3' classpath ('com.tencent.tinker:tinker-patch-gradle-plugin:1.7.6') // NOTE: Do not place your application dependencIEs here; they belong // in the indivIDual module build.gradle files }}allprojects { repositorIEs { jcenter() }}task clean(type: Delete) { delete rootProject.buildDir}

1.再将app的build中的关联属性添加进去,这些属性都是经过测试过的,都有注释显示,如果自己需要其他属性,可以自己去github上查看并集成,文章末尾会送上地址,ps:官方的集成特别麻烦,有时候一整天都有可能搞不定,根据自己的需求和情况来添加,末尾会送上demo

apply plugin: 'com.androID.application'def javaVersion = JavaVersion.VERSION_1_7androID { compileSdkVersion 23 buildToolsversion "23.0.2" compileOptions { sourceCompatibility javaVersion targetCompatibility javaVersion } //recommend dexOptions { jumboMode = true } defaultConfig { applicationID "com.tinker.demo.tinkerdemo" minSdkVersion 15 targetSdkVersion 22 versionCode 1 versionname "1.0" testInstrumentationRunner "androID.support.test.runner.AndroIDJUnitRunner" buildConfigFIEld "String","MESSAGE","\"I am the base apk\"" buildConfigFIEld "String","TINKER_ID","\"${getTinkerIDValue()}\"" buildConfigFIEld "String","PLATFORM","\"all\"" } signingConfigs { release {  try {  storefile file("./keystore/release.keystore")  storePassword "testres"  keyAlias "testres"  keyPassword "testres"  } catch (ex) {  throw new InvalIDUserDataException(ex.toString())  } } deBUG {  storefile file("./keystore/deBUG.keystore") } } buildTypes { release {  MinifyEnabled true  signingConfig signingConfigs.release  proguardfiles getDefaultProguardfile('proguard-androID.txt'),'proguard-rules.pro' } deBUG {  deBUGgable true  MinifyEnabled false  signingConfig signingConfigs.deBUG } } sourceSets { main {  jnilibs.srcDirs = ['libs'] } }}dependencIEs { compile filetree(dir: 'libs',include: ['*.jar']) androIDTestCompile('com.androID.support.test.espresso:espresso-core:2.2.2',{ exclude group: 'com.androID.support',module: 'support-annotations' }) compile "com.androID.support:appcompat-v7:23.1.1" testCompile 'junit:junit:4.12' compile("com.tencent.tinker:tinker-androID-lib:${TINKER_VERSION}") { changing = true } provIDed("com.tencent.tinker:tinker-androID-anno:${TINKER_VERSION}") { changing = true } compile "com.androID.support:multIDex:1.0.1"}def gitSha() { try { // String gitRev = 'git rev-parse --short head'.execute(null,project.rootDir).text.trim() String gitRev = "1008611" if (gitRev == null) {  throw new GradleException("can't get git rev,you should add git to system path or just input test value,such as 'testTinkerID'") } return gitRev } catch (Exception e) { throw new GradleException("can't get git rev,such as 'testTinkerID'") }}def bakPath = file("${buildDir}/bakApk/")ext { //for some reason,you may want to ignore tinkerBuild,such as instant run deBUG build? tinkerEnabled = true //for normal build //old apk file to build patch apk tinkeroldApkPath = "${bakPath}/app-deBUG-0113-14-01-29.apk" //proguard mapPing file to build patch apk tinkerApplyMapPingPath = "${bakPath}/app-deBUG-1018-17-32-47-mapPing.txt" //resource R.txt to build patch apk,must input if there is resource changed tinkerApplyResourcePath = "${bakPath}/app-deBUG-0113-14-01-29-R.txt" //only use for build all flavor,if not,just ignore this fIEld tinkerBuildFlavorDirectory = "${bakPath}/app-1018-17-32-47"}def getoldApkPath() { return hasProperty("olD_APK") ? olD_APK : ext.tinkeroldApkPath}def getApplyMapPingPath() { return hasProperty("APPLY_MAPPing") ? APPLY_MAPPing : ext.tinkerApplyMapPingPath}def getApplyResourceMapPingPath() { return hasProperty("APPLY_RESOURCE") ? APPLY_RESOURCE : ext.tinkerApplyResourcePath}def getTinkerIDValue() { return hasProperty("TINKER_ID") ? TINKER_ID : gitSha()}def builDWithTinker() { return hasProperty("TINKER_ENABLE") ? TINKER_ENABLE : ext.tinkerEnabled}def getTinkerBuildFlavorDirectory() { return ext.tinkerBuildFlavorDirectory}if (builDWithTinker()) { apply plugin: 'com.tencent.tinker.patch' tinkerPatch { /**  * 默认为null  * 将旧的apk和新的apk建立关联  * 从build / bakApk添加apk  */ oldApk = getoldApkPath() /**  * 可选,默认'false'  *有些情况下我们可能会收到一些警告  *如果ignoreWarning为true,我们只是断言补丁过程  * case 1:minSdkVersion低于14,但是你使用dexMode与raw。  * case 2:在AndroIDManifest.xml中新添加AndroID组件,  * case 3:装载器类在dex.loader {}不保留在主要的dex,  * 它必须让tinker不工作。  * case 4:在dex.loader {}中的loader类改变,  * 加载器类是加载补丁dex。改变它们是没有用的。  * 它不会崩溃,但这些更改不会影响。你可以忽略它  * case 5:resources.arsc已经改变,但是我们不使用applyResourceMapPing来构建  */ ignoreWarning = false /**  *可选,默认为“true”  * 是否签名补丁文件  * 如果没有,你必须自己做。否则在补丁加载过程中无法检查成功  * 我们将使用sign配置与您的构建类型  */ useSign = true /**  可选,默认为“true”  是否使用tinker构建  */ tinkerEnable = builDWithTinker() /**  * 警告,applyMapPing会影响正常的androID build!  */ buildConfig {  /**  *可选,默认为'null'  * 如果我们使用tinkerPatch构建补丁apk,你最好应用旧的  * apk映射文件如果MinifyEnabled是启用!  * 警告:你必须小心,它会影响正常的组装构建!  */  applyMapPing = getApplyMapPingPath()  /**  *可选,默认为'null'  * 很高兴保持资源ID从R.txt文件,以减少java更改  */  applyResourceMapPing = getApplyResourceMapPingPath()  /**  *必需,默认'null'  * 因为我们不想检查基地apk与md5在运行时(它是慢)  * tinkerID用于在试图应用补丁时标识唯一的基本apk。  * 我们可以使用git rev,svn rev或者简单的versionCode。  * 我们将在您的清单中自动生成tinkerID  */  tinkerID = getTinkerIDValue()  /**  *如果keepDexApply为true,则表示dex指向旧apk的类。  * 打开这可以减少dex diff文件大小。  */  keepDexApply = false } dex {  /**  *可选,默认'jar'  * 只能是'raw'或'jar'。对于原始,我们将保持其原始格式  * 对于jar,我们将使用zip格式重新包装dexes。  * 如果你想支持下面14,你必须使用jar  * 或者你想保存rom或检查更快,你也可以使用原始模式  */  dexMode = "jar"  /**  *必需,默认'[]'  * apk中的dexes应该处理tinkerPatch  * 它支持*或?模式。  */  pattern = ["classes*.dex","assets/secondary-dex-?.jar"]  /**  *必需,默认'[]'  * 警告,这是非常非常重要的,加载类不能随补丁改变。  * 因此,它们将从补丁程序中删除。  * 你必须把下面的类放到主要的dex。  * 简单地说,你应该添加自己的应用程序{@code tinker.sample.androID.SampleApplication}  * 自己的tinkerLoader,和你使用的类  *  */  loader = [   //use sample,let BaseBuildInfo unchangeable with tinker   "tinker.sample.androID.app.BaseBuildInfo"  ] } lib {  /**  可选,默认'[]'  apk中的图书馆应该处理tinkerPatch  它支持*或?模式。  对于资源库,我们只是在补丁目录中恢复它们  你可以得到他们在TinkerLoadResult与Tinker  */  pattern = ["lib/armeabi/*.so"] } res {  /**  *可选,默认'[]'  * apk中的什么资源应该处理tinkerPatch  * 它支持*或?模式。  * 你必须包括你在这里的所有资源,  * 否则,他们不会重新包装在新的apk资源。  */  pattern = ["res/*","assets/*","resources.arsc","AndroIDManifest.xml"]  /**  *可选,默认'[]'  *资源文件排除模式,忽略添加,删除或修改资源更改  * *它支持*或?模式。  * *警告,我们只能使用文件没有relative与resources.arsc  */  ignoreChange = ["assets/sample_Meta.txt"]  /**  *默认100kb  * *对于修改资源,如果它大于'largeModSize'  * *我们想使用bsdiff算法来减少补丁文件的大小  */  largeModSize = 100 } packageConfig {  /**  *可选,默认'TINKER_ID,TINKER_ID_VALUE','NEW_TINKER_ID,NEW_TINKER_ID_VALUE'  * 包元文件gen。路径是修补程序文件中的assets / package_Meta.txt  * 你可以在您自己的PackageCheck方法中使用securityCheck.getPackagePropertIEs()  * 或TinkerLoadResult.getPackageConfigByname  * 我们将从旧的apk清单为您自动获取TINKER_ID,  * 其他配置文件(如下面的patchMessage)不是必需的  */  configFIEld("patchMessage","tinker is sample to use")  /**  *只是一个例子,你可以使用如sdkVersion,品牌,渠道...  * 你可以在SamplePatchListener中解析它。  * 然后你可以使用补丁条件!  */  configFIEld("platform","all")  /**  * 补丁版本通过packageConfig  */  configFIEld("patchVersion","1.0") } //或者您可以添加外部的配置文件,或从旧apk获取元值 //project.tinkerPatch.packageConfig.configFIEld("test1",project.tinkerPatch.packageConfig.getMetaDataFromoldApk("Test")) //project.tinkerPatch.packageConfig.configFIEld("test2","sample") /**  * 如果你不使用zipArtifact或者path,我们只是使用7za来试试  */ sevenZip {  /**  * 可选,默认'7za'  * 7zip工件路径,它将使用正确的7za与您的平台  */  zipArtifact = "com.tencent.mm:SevenZip:1.1.10"  /**  * 可选,默认'7za'  * 你可以自己指定7za路径,它将覆盖zipArtifact值  */// path = "/usr/local/bin/7za" } } List<String> flavors = new ArrayList<>(); project.androID.productFlavors.each {flavor -> flavors.add(flavor.name) } boolean hasFlavors = flavors.size() > 0 /** * bak apk and mapPing */ androID.applicationVariants.all { variant -> /**  * task type,you want to bak  */ def taskname = variant.name def date = new Date().format("MMdd-HH-mm-ss") tasks.all {  if ("assemble${taskname.cAPItalize()}".equalsIgnoreCase(it.name)) {  it.dolast {   copy {   def filenamePrefix = "${project.name}-${variant.basename}"   def newfilenamePrefix = hasFlavors ? "${filenamePrefix}" : "${filenamePrefix}-${date}"   def destPath = hasFlavors ? file("${bakPath}/${project.name}-${date}/${variant.flavorname}") : bakPath   from variant.outputs.outputfile   into destPath   rename { String filename ->    filename.replace("${filenamePrefix}.apk","${newfilenamePrefix}.apk")   }   from "${buildDir}/outputs/mapPing/${variant.dirname}/mapPing.txt"   into destPath   rename { String filename ->    filename.replace("mapPing.txt","${newfilenamePrefix}-mapPing.txt")   }   from "${buildDir}/intermediates/symbols/${variant.dirname}/R.txt"   into destPath   rename { String filename ->    filename.replace("R.txt","${newfilenamePrefix}-R.txt")   }   }  }  } } } project.afterEvaluate { //sample use for build all flavor for one time if (hasFlavors) {  task(tinkerPatchAllFlavorRelease) {  group = 'tinker'  def originoldpath = getTinkerBuildFlavorDirectory()  for (String flavor : flavors) {   def tinkerTask = tasks.getByname("tinkerPatch${flavor.cAPItalize()}Release")   dependsOn tinkerTask   def preAssembleTask = tasks.getByname("process${flavor.cAPItalize()}ReleaseManifest")   preAssembleTask.doFirst {   String flavorname = preAssembleTask.name.substring(7,8).tolowerCase() + preAssembleTask.name.substring(8,preAssembleTask.name.length() - 15)   project.tinkerPatch.oldApk = "${originoldpath}/${flavorname}/${project.name}-${flavorname}-release.apk"   project.tinkerPatch.buildConfig.applyMapPing = "${originoldpath}/${flavorname}/${project.name}-${flavorname}-release-mapPing.txt"   project.tinkerPatch.buildConfig.applyResourceMapPing = "${originoldpath}/${flavorname}/${project.name}-${flavorname}-release-R.txt"   }  }  }  task(tinkerPatchAllFlavorDeBUG) {  group = 'tinker'  def originoldpath = getTinkerBuildFlavorDirectory()  for (String flavor : flavors) {   def tinkerTask = tasks.getByname("tinkerPatch${flavor.cAPItalize()}DeBUG")   dependsOn tinkerTask   def preAssembleTask = tasks.getByname("process${flavor.cAPItalize()}DeBUGManifest")   preAssembleTask.doFirst {   String flavorname = preAssembleTask.name.substring(7,preAssembleTask.name.length() - 13)   project.tinkerPatch.oldApk = "${originoldpath}/${flavorname}/${project.name}-${flavorname}-deBUG.apk"   project.tinkerPatch.buildConfig.applyMapPing = "${originoldpath}/${flavorname}/${project.name}-${flavorname}-deBUG-mapPing.txt"   project.tinkerPatch.buildConfig.applyResourceMapPing = "${originoldpath}/${flavorname}/${project.name}-${flavorname}-deBUG-R.txt"   }  }  } } }}

3.在清单文件中集成application和服务,name的application必须是.AMSKY,如果你添加不进去,或者是红色的话,请先build一下,如果你已经有了自己的application,后面我会说怎么来集成,Service中做的 *** 作是在你加载成功热更新插件后,会提示你更新成功,并且这里做了锁屏 *** 作就会加载热更新插件,继续往下看。

<?xml version="1.0" enCoding="utf-8"?><manifest xmlns:androID="http://schemas.androID.com/apk/res/androID" package="com.tinker.demo.tinkerdemo"> <uses-permission androID:name="androID.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission androID:name="androID.permission.READ_EXTERNAL_STORAGE"/> <application androID:allowBackup="true" androID:icon="@mipmap/ic_launcher" androID:label="@string/app_name" androID:supportsRtl="true" androID:name=".AMSKY" androID:theme="@style/Apptheme"> <service  androID:name=".service.SampleResultService"  androID:exported="false"/> <activity androID:name=".MainActivity">  <intent-filter>  <action androID:name="androID.intent.action.MAIN" />  <category androID:name="androID.intent.category.LAUNCHER" />  </intent-filter> </activity> </application></manifest>

4.到这里就已经基本集成的差不多了,剩下的就是代码里面的集成,首先是application,这里主要说如果是自已已经存在的application的时候改怎么 *** 作 ,这个applicaiton可以说就是自己的一个application,只不过写法,要这样去写,可以在onCreate中做自己的一些 *** 作,只不过清单文件中,要写AMSKY

@SuppressWarnings("unused")@DefaultlifeCycle(application = "com.tinker.demo.tinkerdemo.AMSKY",flags = ShareConstants.TINKER_ENABLE_ALL,loadVerifyFlag = false)public class SampleApplicationlike extends DefaultApplicationlike { private static final String TAG = "Tinker.SampleApplicationlike";public SampleApplicationlike(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); } /** * 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); //MultIDex必须在Tinker初始化之前 MultIDex.install(base); //这里就是初始化Tinker TinkerInstaller.install(this,new DefaultLoadReporter(getApplication()),new DefaultPatchReporter(getApplication()),new DefaultPatchListener(getApplication()),SampleResultService.class,new UpgradePatch()); Tinker tinker = Tinker.with(getApplication()); //这个只是一个Toast提示 Toast.makeText( getApplication(),"没鸟用,就是Toast提示而已",Toast.LENGTH_SHORT).show(); } @OverrIDe public voID onCreate() { super.onCreate(); //这里可以做自己的 *** 作 } @TargetAPI(Build.VERSION_CODES.ICE_CREAM_SANDWICH) public voID registeractivitylifecycleCallbacks(Application.ActivitylifecycleCallbacks callback) { getApplication().registeractivitylifecycleCallbacks(callback); }}

5.这里就是在MainActivity中来加载热更新文件,在点击加载的时候,就直接锁屏加载(不要删除service),当然退出app,下次进来也是可以加载的吗,这里加载补丁插件的话,路径可以自己设置,我是放在根目录的deBUG文件夹当中的,并且我的补丁插件名字叫patch,可以自行更改。

public class MainActivity extends AppCompatActivity { @OverrIDe protected voID onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentVIEw(R.layout.activity_main); } /** * 加载热补丁插件 * @param v */ public voID loadPatch(VIEw v) { TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(),"/sdcard/deBUG/patch.apk"); } /** * 杀死应用加载补丁 * @param v */ public voID killApp(VIEw v) { ShareTinkerInternals.killAllOtherProcess(getApplicationContext()); androID.os.Process.killProcess(androID.os.Process.myPID()); } @OverrIDe protected voID onResume() { super.onResume(); Utils.setBackground(false); } @OverrIDe protected voID onPause() { super.onPause(); Utils.setBackground(true); }}

6.Service文件

public class SampleResultService extends DefaultTinkerResultService { private static final String TAG = "Tinker.SampleResultService"; @OverrIDe public voID onPatchResult(final PatchResult result) { if (result == null) {  TinkerLog.e(TAG,"SampleResultService received null result!!!!");  return; } TinkerLog.i(TAG,"SampleResultService receive result: %s",result.toString()); //first,we want to kill the recover process TinkerServiceInternals.killTinkerPatchServiceProcess(getApplicationContext()); Handler handler = new Handler(Looper.getMainLooper()); handler.post(new Runnable() {  @OverrIDe  public voID run() {  if (result.isSuccess) {   Toast.makeText(getApplicationContext(),"patch success,please restart process",Toast.LENGTH_LONG).show();  } else {   Toast.makeText(getApplicationContext(),"patch fail,please check reason",Toast.LENGTH_LONG).show();  }  } }); // is success and newPatch,it is nice to delete the raw file,and restart at once // for old patch,you can't delete the patch file if (result.isSuccess) {  file rawfile = new file(result.rawPatchfilePath);  if (rawfile.exists()) {  TinkerLog.i(TAG,"save delete raw patch file");  SharePatchfileUtil.safeDeletefile(rawfile);  }  //not like TinkerResultService,I want to restart just when I am at background!  //if you have not install tinker this moment,you can use TinkerApplicationHelper API  if (checkIfNeedKill(result)) {  if (Utils.isBackground()) {   TinkerLog.i(TAG,"it is in background,just restart process");   restartProcess();  } else {   //we can wait process at background,such as onAppBackground   //or we can restart when the screen off   TinkerLog.i(TAG,"tinker wait screen to restart process");   new ScreenState(getApplicationContext(),new ScreenState.IOnScreenOff() {   @OverrIDe   public voID onScreenOff() {    restartProcess();   }   });  }  } else {  TinkerLog.i(TAG,"I have already install the newly patch version!");  } } } /** * you can restart your process through service or broadcast */ private voID restartProcess() { TinkerLog.i(TAG,"app is background Now,i can kill quIEtly"); //you can send service or broadcast intent to restart your process androID.os.Process.killProcess(androID.os.Process.myPID()); } static class ScreenState { interface IOnScreenOff {  voID onScreenOff(); } ScreenState(Context context,final IOnScreenOff onScreenOffInterface) {  IntentFilter filter = new IntentFilter();  filter.addAction(Intent.ACTION_SCREEN_OFF);  context.registerReceiver(new broadcastReceiver() {  @OverrIDe  public voID onReceive(Context context,Intent in) {   String action = in == null ? "" : in.getAction();   TinkerLog.i(TAG,"ScreenReceiver action [%s] ",action);   if (Intent.ACTION_SCREEN_OFF.equals(action)) {   context.unregisterReceiver(this);   if (onScreenOffInterface != null) {    onScreenOffInterface.onScreenOff();   }   }  }  },filter); } }}

7.Utils文件

public class Utils { /** * the error code define by myself * should after {@code ShareConstants.ERROR_PATCH_INSERVICE */ public static final int ERROR_PATCH_GooglePLAY_CHANNEL = -5; public static final int ERROR_PATCH_ROM_SPACE  = -6; public static final int ERROR_PATCH_MEMORY_liMIT  = -7; public static final int ERROR_PATCH_ALREADY_APPLY  = -8; public static final int ERROR_PATCH_CRASH_liMIT  = -9; public static final int ERROR_PATCH_RETRY_COUNT_liMIT = -10; public static final int ERROR_PATCH_CONDITION_NOT_SATISFIED = -11; public static final String PLATFORM = "platform"; public static final int MIN_MEMORY_HEAP_SIZE = 45; private static boolean background = false; public static boolean isGooglePlay() { return false; } public static boolean isBackground() { return background; } public static voID setBackground(boolean back) { background = back; } public static int checkForPatchRecover(long roomSize,int maxMemory) { if (Utils.isGooglePlay()) {  return Utils.ERROR_PATCH_GooglePLAY_CHANNEL; } if (maxMemory < MIN_MEMORY_HEAP_SIZE) {  return Utils.ERROR_PATCH_MEMORY_liMIT; } //or you can mention user to clean their rom space! if (!checkRomSpaceEnough(roomSize)) {  return Utils.ERROR_PATCH_ROM_SPACE; } return ShareConstants.ERROR_PATCH_OK; } public static boolean isXposedExists(Throwable thr) { StackTraceElement[] stackTraces = thr.getStackTrace(); for (StackTraceElement stackTrace : stackTraces) {  final String clazzname = stackTrace.getClassname();  if (clazzname != null && clazzname.contains("de.robv.androID.xposed.XposedBrIDge")) {  return true;  } } return false; } @Deprecated public static boolean checkRomSpaceEnough(long limitSize) { long allSize; long availableSize = 0; try {  file data = Environment.getDataDirectory();  StatFs sf = new StatFs(data.getPath());  availableSize = (long) sf.getAvailableBlocks() * (long) sf.getBlockSize();  allSize = (long) sf.getBlockCount() * (long) sf.getBlockSize(); } catch (Exception e) {  allSize = 0; } if (allSize != 0 && availableSize > limitSize) {  return true; } return false; } public static String getExceptionCauseString(final Throwable ex) { final ByteArrayOutputStream bos = new ByteArrayOutputStream(); final PrintStream ps = new PrintStream(bos); try {  // print directly  Throwable t = ex;  while (t.getCause() != null) {  t = t.getCause();  }  t.printstacktrace(ps);  return toVisualString(bos.toString()); } finally {  try {  bos.close();  } catch (IOException e) {  e.printstacktrace();  } } } private static String toVisualString(String src) { boolean cutFlg = false; if (null == src) {  return null; } char[] chr = src.tochararray(); if (null == chr) {  return null; } int i = 0; for (; i < chr.length; i++) {  if (chr[i] > 127) {  chr[i] = 0;  cutFlg = true;  break;  } } if (cutFlg) {  return new String(chr,i); } else {  return src; } }}

到这里就已经集成完毕,下面来说下使用的方法

这是有BUG的版本,我们测试就使用assembleDeBUG来测试 ,注意没点击assembleDeBUG之前,build文件夹里面是没有bakApk文件夹的

2.点击assembleDeBUG之后会出现bakApk这个文件夹,里面就有apk文件,如果失败,记得clean一下,然后build一下

3.接下来在build文件夹里面,更改ext中的属性,将bakApk中生成的apk文件和R文件复制到ext这里,如果你打的release包有mapPing的话同样复制到这里,我们这里是deBUG测试,所以没有mapPing文件

4.下面就修改我们需要更新,或者更改的BUG,我这里是添加一张图片,并且更改标题显示

这是有BUG的版本,我还没添加图片,更改标题

这里我添加了一张aa的图片,并且更改了标题

5.接下来我们运行tinker下面的tinkerPatchDeBUG,来生成补丁包,这个补丁包在outputs下面

点击完成后,就会生成tinkerPatch文件夹

将tinkerPatch文件夹下面的patch_signed_7zip.apk文件,粘贴出来,改成你的MainActivity中加载的文件名字,我这里叫patch,然后点击加载没加载之前

加载之后,锁频,解锁 ,补丁已经加载出来了,并且文件夹中的补丁已经不在了,因为它和老apk合并了

注意

签名文件的话 在build的signingConfigs中设置,以及左侧的kestore文件夹中设置 ,如下图


项目github地址:TinkerDemo

Tinker原项目地址:https://github.com/Tencent/tinker
Tinker使用指南:https://github.com/Tencent/tinker/wiki
Tinker一键集成(这个简单,但是不能从自己服务器上下载补丁,不需配置Tinker自己的后台,有部分局限性,自行选择):https://github.com/TinkerPatch/tinkerpatch-sdk/blob/master/docs/tinkerpatch-android-sdk.md
Tinker一键集成后台:http://www.tinkerpatch.com/

更多精彩内容请点击《Android微信开发教程汇总》,《java微信开发教程汇总》欢迎大家学习阅读。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程小技巧。

总结

以上是内存溢出为你收集整理的Android微信Tinker热更新详细使用全部内容,希望文章能够帮你解决Android微信Tinker热更新详细使用所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1147253.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-31
下一篇 2022-05-31

发表评论

登录后才能评论

评论列表(0条)

保存