国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統 > Android > 正文

Android微信Tinker熱更新詳細使用

2019-12-12 04:00:49
字體:
來源:轉載
供稿:網友

先看一下效果圖

這里寫圖片描述

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, you should add git to system path or just input test value, 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, 8).toLowerCase() + preAssembleTask.name.substring(8, 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, 0, 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微信開發教程匯總》歡迎大家學習閱讀。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 西平县| 涞源县| 广州市| 襄樊市| 鄯善县| 东光县| 巴南区| 肥东县| 永昌县| 池州市| 当阳市| 屏山县| 六枝特区| 万州区| 利辛县| 湟中县| 同江市| 忻州市| 建瓯市| 光山县| 小金县| 彰化县| 扶余县| 彩票| 溧水县| 长汀县| 嘉义市| 鲁甸县| 乐至县| 通榆县| 丘北县| 中江县| 盐山县| 新津县| 马龙县| 贵阳市| 甘泉县| 姚安县| 临朐县| 铜山县| 乌兰浩特市|