赞
踩
下面来简单说下步骤:
1.加依赖
buildscript { repositories { google() jcenter() maven { url 'https://maven.google.com/' name 'Google' } } dependencies { classpath 'com.android.tools.build:gradle:3.1.2' classpath("com.tencent.tinker:tinker-patch-gradle-plugin:${TINKER_VERSION}") { changing = TINKER_VERSION?.endsWith("-SNAPSHOT") exclude group: 'com.android.tools.build', module: 'gradle' } // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { google() jcenter() maven { url 'https://maven.google.com/' name 'Google' } } } task clean(type: Delete) { delete rootProject.buildDir } def is_gradle_3() { return hasProperty('GRADLE_3') && GRADLE_3.equalsIgnoreCase('TRUE') }
2.依赖
- apply plugin: 'com.android.application'
- def javaVersion = JavaVersion.VERSION_1_7
-
- android {
- compileSdkVersion 28
-
-
- defaultConfig {
- applicationId "com.example.liukang.mytinkerproject"
- minSdkVersion 19
- targetSdkVersion 28
- versionCode 1
- versionName "2.0"
- /**
- * you can use multiDex and install it in your ApplicationLifeCycle implement
- */
- // multiDexEnabled true
-
- }
- compileOptions {
- sourceCompatibility javaVersion
- targetCompatibility javaVersion
- }
- //recommend
- dexOptions {
- jumboMode = true
- }
-
- signingConfigs {
-
- release {
- storeFile file("sign/release.keystore")
- storePassword "123456"
- keyAlias "123456"
- keyPassword "123456"
- }
-
- debug {
- storeFile file("sign/release.keystore")
- storePassword "123456"
- keyAlias "123456"
- keyPassword "123456"
- }
- }
-
- buildTypes {
- release {
- minifyEnabled true
- signingConfig signingConfigs.release
- proguardFiles getDefaultProguardFile('proguard-android.txt'), project.file('proguard-rules.pro')
- }
- debug {
- debuggable true
- minifyEnabled true
- signingConfig signingConfigs.debug
- proguardFiles getDefaultProguardFile('proguard-android.txt'), project.file('proguard-rules.pro')
-
- }
- }
-
- sourceSets {
- main {
- jniLibs.srcDirs = ['libs']
- }
- }
- }
- def bakPath = file("${buildDir}/bakApk/")
-
- dependencies {
- implementation fileTree(dir: 'libs', include: ['*.jar'])
- testImplementation 'junit:junit:4.12'
- implementation "com.android.support:appcompat-v7:28.0.0+"
- api("com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}") { changing = true }
-
- // Maven local cannot handle transist dependencies.
- implementation("com.tencent.tinker:tinker-android-loader:${TINKER_VERSION}") { changing = true }
-
- annotationProcessor("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") {
- changing = true
- }
- compileOnly("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") { changing = true }
-
- implementation "com.android.support:multidex:1.0.1"
-
- implementation 'com.orhanobut:hawk:2.0.1'
- }
-
- /**
- * 使用Tinker的一些常量配置
- */
- 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-release-0317-18-54-47.apk"
- //proguard mapping file to build patch apk混淆文件
- tinkerApplyMappingPath = "${bakPath}/app-release-0317-18-54-47-mapping.txt"
- //resource R.txt to build patch apk, must input if there is resource changed资源路径
- tinkerApplyResourcePath = "${bakPath}/app-release-0317-18-54-47-R.txt"
-
- //only use for build all flavor, if not, just ignore this field
- tinkerBuildFlavorDirectory = "${bakPath}/"
-
-
-
-
- }
- /**
- * Tinker的全剧配置
- */
- if (buildWithTinker()) {
- apply plugin: 'com.tencent.tinker.patch'
-
- tinkerPatch {
- /**
- * 第一次生成的基线包
- */
- oldApk = getOldApkPath()
- /**
- * default 'false'
- * 如果出现以下的情况,并且ignoreWarning为false,我们将中断编译。因为这些情况可能会导致编译出来的patch包带来风险:
- 1. minSdkVersion小于14,但是dexMode的值为"raw";
- 2. 新编译的安装包出现新增的四大组件(Activity, BroadcastReceiver...);
- 3. 定义在dex.loader用于加载补丁的类不在main dex中;
- 4. 定义在dex.loader用于加载补丁的类出现修改;
- 5. resources.arsc改变,但没有使用applyResourceMapping编译。
- */
- ignoreWarning = false
-
- /**
- * 在运行过程中,我们需要验证基准apk包与补丁包的签名是否一致,我们是否需要为你签名。
- */
- useSign = true
-
- /**
- * optional,default 'true'
- * 是否打开Tinker功能
- */
- tinkerEnable = buildWithTinker()
-
- /**
- * 编译相关的配置项
- */
- buildConfig {
- /**
- * optional,default 'null'
- * 在编译新的apk时候,我们希望通过保持旧apk的proguard混淆方式,
- * 从而减少补丁包的大小。这个只是推荐设置,
- * 不设置applyMapping也不会影响任何的assemble编译。
- */
- // applyMapping = getApplyMappingPath()
- /**
- * optional,default 'null'
- * 可选参数;在编译新的apk时候,我们希望通过旧apk的R.txt文件保持ResId的分配
- * ,这样不仅可以减少补丁包的大小,同时也避免由于ResId改变导致remote view异常
- */
- applyResourceMapping = getApplyResourceMappingPath()
-
- /**
- * necessary,default 'null'
- * 在运行过程中,我们需要验证基准apk包的tinkerId是否等于补丁包的tinkerId。
- * 这个是决定补丁包能运行在哪些基准包上面,一般来说我们可以使用git版本号
- * 、versionName等等。
- */
- tinkerId = getTinkerIdValue()
-
- /**
- * 如果我们有多个dex,编译补丁时可能会由于类的移动导致变更增多。
- * 若打开keepDexApply模式,补丁包将根据基准包的类分布来编译
- */
- keepDexApply = true
-
- /**
- * optional, default 'false'
- * 是否使用加固模式,仅仅将变更的类合成补丁。注意,这种模式仅仅可以用于加固应用中。
- */
- isProtectedApp = false
-
- /**
- * optional, default 'false'
- * 是否支持新增非export的Activity
- * <b>Notice that currently this feature is incubating and only support NON-EXPORTED Activity</b>
- */
- supportHotplugComponent = false
- }
-
- dex {
- /**
- * optional,default 'jar'
- * 只能是'raw'或者'jar'。
- 对于'raw'模式,我们将会保持输入dex的格式。
- 对于'jar'模式,我们将会把输入dex重新压缩封装到jar。
- 如果你的minSdkVersion小于14,你必须选择‘jar’模式,而且它更省存储空间,
- 但是验证md5时比'raw'模式耗时。默认我们并不会去校验md5,一般情况下选择jar模式即可。
- */
- dexMode = "jar"
-
- /**
- * necessary,default '[]'
- * what dexes in apk are expected to deal with tinkerPatch
- * it support * or ? pattern.
- */
- pattern = ["classes*.dex",
- "assets/secondary-dex-?.jar"]
- /**
- * necessary,default '[]'
- * Warning, it is very very important, loader classes can't change with patch.
- * thus, they will be removed from patch dexes.
- * you must put the following class into main dex.
- * Simply, you should add your own application {@code tinker.sample.android.SampleApplication}
- * own tinkerLoader, and the classes you use in them
- *
- */
- loader = [
- //use sample, let BaseBuildInfo unchangeable with tinker
- "tinker.sample.android.app.BaseBuildInfo"
- ]
- }
-
- lib {
- /**
- * optional,default '[]'
- * 需要处理lib路径,
- * 支持*、?通配符,必须使用'/'分割。与dex.pattern一致,
- * 路径是相对安装包的,例如assets/...
- */
- pattern = ["lib/*/*.so"]
- }
-
- res {
- /**
- * optional,default '[]'
- * 需要处理res路径,支持*、?通配符,必须使用'/'分割。
- * 与dex.pattern一致, 路径是相对安装包的,例如assets/...,务必注意的是,
- * 只有满足pattern的资源才会放到合成后的资源包
- */
- pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
-
- /**
- * optional,default '[]'
- * 支持*、?通配符,必须使用'/'分割。若满足ignoreChange的pattern,
- * 在编译时会忽略该文件的新增、删除与修改。 最极端的情况,
- * ignoreChange与上面的pattern一致,即会完全忽略所有资源的修改。
- ignoreChange = ["assets/sample_meta.txt"]
-
- /**
- * default 100kb
- * 如果大于largeModSize,我们将使用bsdiff算法。
- * 这可以降低补丁包的大小,但是会增加合成时的复杂度。默认大小为100kb
- */
- largeModSize = 100
- }
-
- packageConfig {
- /**
- * optional,default 'TINKER_ID, TINKER_ID_VALUE' 'NEW_TINKER_ID, NEW_TINKER_ID_VALUE'
- * package meta file gen. path is assets/package_meta.txt in patch file
- * you can use securityCheck.getPackageProperties() in your ownPackageCheck method
- * or TinkerLoadResult.getPackageConfigByName
- * we will get the TINKER_ID from the old apk manifest for you automatic,
- * other config files (such as patchMessage below)is not necessary
- */
- configField("patchMessage", "tinker is sample to use")
- /**
- * just a sample case, you can use such as sdkVersion, brand, channel...
- * you can parse it in the SamplePatchListener.
- * Then you can use patch conditional!
- */
- configField("platform", "all")
- /**
- * patch version via packageConfig
- */
- configField("patchVersion", "1.0")
- }
- //or you can add config filed outside, or get meta value from old apk
- //project.tinkerPatch.packageConfig.configField("test1", project.tinkerPatch.packageConfig.getMetaDataFromOldApk("Test"))
- //project.tinkerPatch.packageConfig.configField("test2", "sample")
-
- /**
- * if you don't use zipArtifact or path, we just use 7za to try
- */
- sevenZip {
- /**
- * optional,default '7za'
- * the 7zip artifact path, it will use the right 7za with your platform
- */
- zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
- /**
- * optional,default '7za'
- * you can specify the 7za path yourself, it will overwrite the zipArtifact value
- */
- // path = "/usr/local/bin/7za"
- }
- }
-
- List<String> flavors = new ArrayList<>();
- project.android.productFlavors.each { flavor ->
- flavors.add(flavor.name)
- }
- boolean hasFlavors = flavors.size() > 0
- def date = new Date().format("MMdd-HH-mm-ss")
-
- /**
- * bak apk and mapping
- */
- android.applicationVariants.all { variant ->
- /**
- * task type, you want to bak
- */
- def taskName = variant.name
-
- 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.first().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")
- }
- }
- }
- }
- }
- }
-
- }
-
- 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 : android.defaultConfig.versionName
- }
-
- def buildWithTinker() {
- return hasProperty("TINKER_ENABLE") ? Boolean.parseBoolean(TINKER_ENABLE) : ext.tinkerEnabled
- }
-
- def getTinkerBuildFlavorDirectory() {
- return ext.tinkerBuildFlavorDirectory
- }
-
-
-
3.创建代理的MyTinkerApplication
package com.example.liukang.mytinkerproject; import android.app.Application; import android.content.Context; import android.content.Intent; import android.support.multidex.MultiDex; import com.orhanobut.hawk.Hawk; import com.tencent.tinker.anno.DefaultLifeCycle; import com.tencent.tinker.entry.ApplicationLike; import com.tencent.tinker.entry.DefaultApplicationLike; import com.tencent.tinker.loader.shareutil.ShareConstants; @SuppressWarnings("unused") @DefaultLifeCycle(application = ".MyTinkerApplication" , flags = ShareConstants.TINKER_ENABLE_ALL, loadVerifyFlag = false)//都是官方要求这么写的 public class SampleApplicationLike extends ApplicationLike { public SampleApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) { super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent); } @Override public void onBaseContextAttached(Context base) { super.onBaseContextAttached(base); TinkerManager.installedTinker(this); } @Override public void onCreate() { super.onCreate(); Hawk.init(getApplication()).build(); } }
4.TinkerManager对Tinker进行管理,这里面可以将重写的如DefaultResultService等一些自定义处理的东西,在这里面通过Tinker 进行初始化
package com.example.liukang.mytinkerproject; import android.content.Context; import com.tencent.tinker.entry.ApplicationLike; import com.tencent.tinker.lib.patch.UpgradePatch; import com.tencent.tinker.lib.service.DefaultTinkerResultService; import com.tencent.tinker.lib.tinker.Tinker; import com.tencent.tinker.lib.tinker.TinkerInstaller; public class TinkerManager { private static boolean isInstalled = false;//是否已经初始化标志位 private static ApplicationLike mApplicationLike; /** * 完成Tinker初始化 * * @param applicationLike */ public static void installedTinker(ApplicationLike applicationLike) { mApplicationLike = applicationLike; if (isInstalled) { return; } Tinker tinker = new Tinker.Builder(applicationLike.getApplication()).build(); Tinker.create(tinker); tinker.install(applicationLike.getTinkerResultIntent(),MyTinkerService.class, new UpgradePatch()); isInstalled = true; } /** * 完成patch文件的加载 * * @param path 补丁文件路径 */ public static void loadPatch(String path) { if (Tinker.isTinkerInstalled()) {//是否已经安装过 TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), path); } } /** * 利用Tinker代理Application 获取应用全局的上下文 * @return 全局的上下文 */ private static Context getApplicationContext() { if (mApplicationLike != null) return mApplicationLike.getApplication().getApplicationContext(); return null; } }
5.清单配置 需要修改application 的name
- <?xml version="1.0" encoding="utf-8"?>
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.liukang.mytinkerproject">
- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
- <application
- android:name=".MyTinkerApplication"
- android:allowBackup="true"
- android:icon="@mipmap/ic_launcher"
- android:label="@string/app_name"
- android:roundIcon="@mipmap/ic_launcher_round"
- android:supportsRtl="true"
- android:theme="@style/AppTheme">
- <service android:name=".MyTinkerService"
- 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>
6.生成基线
包
--》
--》对应替换
7.修改一下代码之后,在生成补丁包
===》这样便得到了
8.使用实例:
public class MainActivity extends AppCompatActivity { private static final String TAG = "Tinker.MainActivity"; private static final String FILE_END = ".apk";//文件后缀 private String FILEDIR;//文件路径 Activity activity; TextView showInfo; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); activity=this; createDir(); Button loadPatchButton = findViewById(R.id.loadPatch); loadPatchButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new PermissionHelper(activity).requestPermissions("请同意读取权限" , new PermissionHelper.PermissionListener() { @Override public void doAfterGrand(String... permission) { TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), getPatchName()); } @Override public void doAfterDenied(String... permission) { Toast.makeText(activity, "没有权限", Toast.LENGTH_LONG).show(); } }, Manifest.permission.READ_EXTERNAL_STORAGE , Manifest.permission.WRITE_EXTERNAL_STORAGE); } }); Button cleanPatchButton = (Button) findViewById(R.id.cleanPatch); cleanPatchButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Tinker.with(getApplicationContext()).cleanPatch(); } }); Button killSelfButton = (Button) findViewById(R.id.killSelf); killSelfButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ShareTinkerInternals.killAllOtherProcess(getApplicationContext()); android.os.Process.killProcess(android.os.Process.myPid()); } }); showInfo=findViewById(R.id.showInfo); showInfo.setText("哈哈哈哈哈!"); } @Override protected void onResume() { Log.e(TAG, "i am on onResume"); // Log.e(TAG, "i am on patch onResume"); super.onResume(); Utils.setBackground(false); } @Override protected void onPause() { super.onPause(); Utils.setBackground(true); } public void createDir() { FILEDIR = Environment.getExternalStorageDirectory().getPath() + "/tpatch/"; //创建路径对应的文件夹 File file = new File(FILEDIR); if (!file.exists()) file.mkdir(); } public String getPatchName() { String s = FILEDIR.concat("tinker").concat(FILE_END); Log.e("TAG", s); return s; } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。