Android实战——Tinker的集成和使用
2017-11-19 21:10
330 查看
前言
对于热修复我相信很多小伙伴都已经知道它们普遍的操作套路,Tinker主要是依赖自己的gradlePlugin生成拆分包,所以其拆分包的生成就由Gradle来完成,当然也可以通过命令行的方式,这里就不对命令行做讲解,Tinker接入指南项目结构
Tinker介绍
来自Tinker官方1、优点
2、缺点
Tinker不支持修改AndroidManifest.xml,Tinker不支持新增四大组件(1.9.0支持新增非export的Activity);
由于Google Play的开发者条款限制,不建议在GP渠道动态更新代码;
在Android N上,补丁对应用启动时间有轻微的影响;
不支持部分三星android-21机型,加载补丁时会主动抛出”TinkerRuntimeException:checkDexInstall failed”;
对于资源替换,不支持修改remoteView。例如transition动画,notification icon以及桌面图标。
Tinker集成
1、在项目的build.gradle中,添加依赖buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.3.3' // Tinker classpath ("com.tencent.tinker:tinker-patch-gradle-plugin:${TINKER_VERSION}") } }
这里的TINKER_VERSION写在项目gradle.properties中
TINKER_VERSION=1.7.7
2、在app的build.gradle文件,添加依赖
provided("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") compile("com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}") compile "com.android.support:multidex:1.0.1"
添加依赖以后,我们在gradle文件中做以下配置
开启Multidex、配置Java编译的版本
配置签名文件,为了后面打包方便调试
引入另一个gradle文件专门来对Tinker生成拆分包的配置(由于多渠道要用到gradle的参数,所以将引入放在末尾)
apply plugin: 'com.android.application' android { compileSdkVersion 25 buildToolsVersion "25.0.3" defaultConfig { applicationId "com.handsome.thinker" minSdkVersion 16 targetSdkVersion 25 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" multiDexEnabled true } compileOptions { sourceCompatibility JavaVersion.VERSION_1_7 targetCompatibility JavaVersion.VERSION_1_7 } dexOptions { jumboMode = true } signingConfigs { debug { keyAlias 'hensen' keyPassword '123456' storeFile file("../Hensen.jks") storePassword '123456' } release { keyAlias 'hensen' keyPassword '123456' storeFile file("../Hensen.jks") storePassword '123456' } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' signingConfig signingConfigs.release } debug { minifyEnabled false signingConfig signingConfigs.debug } } } // 加入Tinker生成补丁包的gradle apply from: 'buildTinker.gradle'
3、buildTinker.gradle是专门为Tinker配置和生成拆分包而写的,具体可以参考官方gradle
//指定生成apk文件的存放位置 def bakPath = file("${buildDir}/bakApk/") //参数配置 ext { //开启Tinker tinkerEnable = true //旧的apk位置,需要我们手动指定 tinkerOldApkPath = "${bakPath}/" //旧的混淆映射位置,如果开启了混淆,则需要我们手动指定 tinkerApplyMappingPath = "${bakPath}/" //旧的resource位置,需要我们手动指定 tinkerApplyResourcePath = "${bakPath}/" tinkerID = "1.0" } def buildWithTinker() { return ext.tinkerEnable } def getOldApkPath() { return ext.tinkerOldApkPath } def getApplyMappingPath() { return ext.tinkerApplyMappingPath } def getApplyResourceMappingPath(){ return ext.tinkerApplyResourcePath } def getTinkerIdValue(){ return ext.tinkerID } if (buildWithTinker()) { apply plugin: 'com.tencent.tinker.patch' tinkerPatch { oldApk = getOldApkPath() //指定old apk文件路径 ignoreWarning = false //不忽略tinker警告,出现警告则中止patch文件生成 useSign = true //patch文件必须是签名后的 tinkerEnable = buildWithTinker() //指定是否启用tinker buildConfig { applyMapping = getApplyMappingPath() //指定old apk打包时所使用的混淆文件 applyResourceMapping = getApplyResourceMappingPath() //指定old apk的资源文件 tinkerId = getTinkerIdValue() //指定TinkerID keepDexApply = false } dex { dexMode = "jar" //jar、raw pattern = ["classes*.dex", "assets/secondary-dex-?.jar"] //指定dex文件目录 loader = ["com.handsome.thinker.AppLike.MyTinkerApplication"] //指定加载patch文件时用到的类 } lib { pattern = ["libs/*/*.so"] //指定so文件目录 } res { pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"] //指定资源文件目录 ignoreChange = ["assets/sample_meta.txt"] //指定不受影响的资源路径 largeModSize = 100 //资源修改大小默认值 } packageConfig { configField("patchMessage", "fix the 1.0 version's bugs") configField("patchVersion", "1.0") } } /** * 是否配置了多渠道 */ List<String> flavors = new ArrayList<>(); project.android.productFlavors.each { flavor -> flavors.add(flavor.name) } boolean hasFlavors = flavors.size() > 0 /** * 复制apk包和其它必须文件到指定目录 */ android.applicationVariants.all { variant -> /** * task type, you want to bak */ def taskName = variant.name def date = new Date().format("yyyy-MM-dd-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") } } } } } } }
4、记得开启Manifest权限,否则生成拆分包的时候有奇怪错误
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
Tinker封装
我们提供两个方法来初始化Tinker默认的方式
自定义模块的方式
public class TinkerManager { private static boolean isInstalled = false; // 这里的ApplicationLike可以理解为Application的载体 private static ApplicationLike mAppLike; private static CustomPatchListener mPatchListener; /** * 默认初始化Tinker * * @param applicationLike */ public static void installTinker(ApplicationLike applicationLike) { mAppLike = applicationLike; if (isInstalled) { return; } TinkerInstaller.install(mAppLike); isInstalled = true; } /** * 初始化Tinker,带有自定义模块 * <p> * 1、CustomPatchListener * 2、CustomResultService * * @param applicationLike * @param md5Value 服务器下发的md5 */ public static void installTinker(ApplicationLike applicationLike, String md5Value) { mAppLike = applicationLike; if (isInstalled) { return; } mPatchListener = new CustomPatchListener(getApplicationContext()); mPatchListener.setCurrentMD5(md5Value); // Load补丁包时候的监听 LoadReporter loadReporter = new DefaultLoadReporter(getApplicationContext()); // 补丁包加载时候的监听 PatchReporter patchReporter = new DefaultPatchReporter(getApplicationContext()); AbstractPatch upgradePatchProcessor = new UpgradePatch(); TinkerInstaller.install(applicationLike, loadReporter, patchReporter, mPatchListener, CustomResultService.class, upgradePatchProcessor); isInstalled = true; } /** * 增加补丁包 * * @param path */ public static void addPatch(String path) { if (Tinker.isTinkerInstalled()) { TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), path); } } /** * 获取上下文 * * @return */ private static Context getApplicationContext() { if (mAppLike != null) { return mAppLike.getApplication().getApplicationContext(); } return null; } }
由于Tinker默认Patch检查是没有对文件做Md5校验,我们可以重写其检验的方法,加上我们自己的检验逻辑(需要自定义模块的方式初始化Tinker)
CustomPatchListener.java
public class CustomPatchListener extends DefaultPatchListener { private String currentMD5; public void setCurrentMD5(String md5Value) { this.currentMD5 = md5Value; } public CustomPatchListener(Context context) { super(context); } /** * patch的检测 * * @param path * @return */ @Override protected int patchCheck(String path) { //MD5校验的工具可以网上查找 //这里要求我们在初始化Tinker的时候加上MD5的参数 //增加patch文件的md5较验 if (!MD5Utils.isFileMD5Matched(path, currentMD5)) { return ShareConstants.ERROR_PATCH_DISABLE; } return super.patchCheck(path); } }
由于Tinker默认安装完补丁包之后是删除补丁包,然后杀掉进程的方式,我们可以修改杀掉进程的行为(需要自定义模块的方式初始化Tinker)
CustomResultService.java
public class CustomResultService extends DefaultTinkerResultService { private static final String TAG = "Tinker.SampleResultService"; /** * patch文件的最终安装结果,默认是安装完成后杀掉自己进程 * 此段代码主要是复制DefaultTinkerResultService的代码逻辑 */ @Override public void onPatchResult(PatchResult result) { if (result == null) { TinkerLog.e(TAG, "DefaultTinkerResultService received null result!!!!"); return; } TinkerLog.i(TAG, "DefaultTinkerResultService received a result:%s ", result.toString()); //first, we want to kill the recover process TinkerServiceInternals.killTinkerPatchServiceProcess(getApplicationContext()); // if success and newPatch, it is nice to delete the raw file, and restart at once // only main process can load an upgrade patch! if (result.isSuccess) { //删除patch包 deleteRawPatchFile(new File(result.rawPatchFilePath)); //杀掉自己进程,如果不需要则可以注释,在这里做自己的逻辑 if (checkIfNeedKill(result)) { android.os.Process.killProcess(android.os.Process.myPid()); } else { TinkerLog.i(TAG, "I have already install the newly patch version!"); } } } }
Tinker使用
1、Tinker的使用需要ApplicationLike来生成我们的Application,然后初始化Multidex和Tinker@DefaultLifeCycle(application = ".MyTinkerApplication", flags = ShareConstants.TINKER_ENABLE_ALL, loadVerifyFlag = false) public class CustomTinkerLike extends ApplicationLike { public CustomTinkerLike(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); MultiDex.install(base); TinkerManager.installTinker(this); } }
2、编译项目自动生成Application,然后在Manifest中指定我们的生成的Application
<application android:name=".AppLike.MyTinkerApplication"
3、在主页面按钮的点击事件,来加载放在缓存目录下的补丁包
public class MainActivity extends AppCompatActivity { private String mPath; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mPath = getExternalCacheDir().getAbsolutePath() + File.separatorChar; } /** * 加载Tinker补丁 * * @param view */ public void Fix(View view) { File patchFile = new File(mPath, "patch_signed.apk"); if (patchFile.exists()) { TinkerManager.addPatch(patchFile.getAbsolutePath()); Toast.makeText(this, "File Exists,Please wait a moment ", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "File No Exists", Toast.LENGTH_SHORT).show(); } } }
Tinker测试
完成Tinker的所有准备工作后,我们通过默认的初始化Tinker方式测试我们的补丁包1、找到gradle工具栏,点击生成Release包,作为1.0版本的程序
2、将生成的Release包Push到手机上,安装,运行程序
生成apk的目录在build的bakApk目录下
运行程序
3、在项目中,对主界面添加加载图片的按钮,同时添加一个drawable文件
public class MainActivity extends AppCompatActivity { private String mPath; private ImageView iv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); iv = (ImageView) findViewById(R.id.iv); mPath = getExternalCacheDir().getAbsolutePath() + File.separatorChar; } /** * 加载Tinker补丁 * * @param view */ public void Fix(View view) { File patchFile = new File(mPath, "patch_signed.apk"); if (patchFile.exists()) { TinkerManager.addPatch(patchFile.getAbsolutePath()); Toast.makeText(this, "File Exists,Please wait a moment ", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "File No Exists", Toast.LENGTH_SHORT).show(); } } /** * 新增的按钮点击事件 * * @param view */ public void Load(View view) { iv.setImageResource(R.drawable.bg_content_header); } }
4、同时记得修改buildTinker.gradle的old安装包的路径,Tinker需要比对前后安装包然后生成补丁包
//参数配置 ext { //开启Tinker tinkerEnable = true //旧的apk位置,需要我们手动指定 tinkerOldApkPath = "${bakPath}/app-release-2017-11-19-18-34-12.apk" //旧的混淆映射位置,如果开启了混淆,则需要我们手动指定 tinkerApplyMappingPath = "${bakPath}/" //旧的resource位置,需要我们手动指定 tinkerApplyResourcePath = "${bakPath}/app-release-2017-11-19-18-34-12-R.txt" tinkerID = "1.0" }
5、找到gradle工具栏,点击thinker生成Release补丁包,作为1.0版本的补丁
6、将生成的Release补丁包Push到手机的缓存目录上,运行程序点击修复补丁包,稍等数秒程序会被杀掉,重启点击加载图片按钮
生成的补丁包
记得将补丁放到缓存目录下,修复补丁后的程序
Tinker多渠道打包
1、Tinker支持多渠道打包,我们采用友盟的打包方式,下载友盟SDK,将jar包增加到项目上2、初始化友盟SDK(新版本的SDK似乎不用初始化了,找不到初始化入口)
3、Manifest增加友盟的AppKey配置和渠道配置
<meta-data android:name="UMENG_APPKEY" android:value="5a116bbea40fa33cf9000150" /> <meta-data android:name="UMENG_CHANNEL" android:value="${UMENG_CHANNEL_VALUE}" />
4、在app的build.gradle中增加多渠道打包信息
/** * 配置多渠道 */ productFlavors { googleplayer { manifestPlaceholders = [UMENG_CHANNEL_VALUE: "googleplayer"] } baidu { manifestPlaceholders = [UMENG_CHANNEL_VALUE: "baidu"] } wangdoujia { manifestPlaceholders = [UMENG_CHANNEL_VALUE: "wangdoujia"] } productFlavors.all { flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name] } }
5、在buildTinker.gradle增加配置多渠道补丁包的生成规则
/** * 生成多渠道补丁包 */ project.afterEvaluate { 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" } } } } }
6、找到gradle工具栏,点击生成Release包,作为1.0版本的程序
7、同时记得修改buildTinker.gradle的old安装包的路径,Tinker需要比对前后安装包然后生成补丁包
//参数配置 ext { //开启Tinker tinkerEnable = true //旧的apk位置,需要我们手动指定 tinkerOldApkPath = "${bakPath}/app-2017-11-19-20-35-23" //旧的混淆映射位置,如果开启了混淆,则需要我们手动指定 tinkerApplyMappingPath = "${bakPath}/app-2017-11-19-20-35-23" //旧的resource位置,需要我们手动指定 tinkerApplyResourcePath = "${bakPath}/app-2017-11-19-20-35-23" //旧的多渠道位置,需要我们手动指定 tinkerBuildFlavorDirectory = "${bakPath}/app-2017-11-19-20-35-23" tinkerID = "1.0" }
8、找到gradle工具栏,点击thinker生成Release补丁包,作为1.0版本的补丁
这里对程序的修改就省略了
后面的测试更上面一样,也就省略了
源码下载
源码下载结语
当野心大于现实的能力,只能默默的学习提升自己的能力,互相努力吧相关文章推荐
- Android集成微信Tinker热修复及使用
- 【Android】微信热修复 Tinker 的集成和使用
- Android集成腾讯bugly-tinker热更新使用步骤
- Android实战——第三方服务之Bmob后端云的推送服务的集成和使用(三)
- 微信 Tinker 在 Android 中集成以及使用
- 使用集成的ADT bundle来搭建android开发环境
- (android实战)Service 生命周期和使用注意项
- Android Fragment应用实战,使用碎片向ActivityGroup说再见
- Android项目实战--手机卫士33--ExpandableListView的使用
- Android Fragment应用实战,使用碎片向ActivityGroup说再见
- 使用Fragment完成Tab选项卡-Android Fragment应用实战
- Android Fragment应用实战,使用碎片向ActivityGroup说再见
- Android Fragment应用实战,使用碎片向ActivityGroup说再见
- Android Fragment应用实战,使用碎片向ActivityGroup说再见
- (android实战)Service 生命周期和使用注意项
- 《android自动化测试之NativerDriver 一 集成junit以及脚本的使用》
- Android Fragment应用实战,使用碎片向ActivityGroup说再见
- zdz工具箱v1.5 android版本发布了,集成各种个人生活中常用的工具,方便日常使用管理