Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android微信Tinker熱更新詳細使用

Android微信Tinker熱更新詳細使用

編輯:關於Android編程

先看一下效果圖

這裡寫圖片描述

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_7
android {
 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/

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved