Tinker热修复之 —— Gradle接入
来源:互联网 发布:新路由器有信号没网络 编辑:程序博客网 时间:2024/04/30 02:08
10月17日,据路透社报道,中国智能手机制造商华为公司发布了一款新的旗舰智能手机Mate 10系列,其改进功能甚至可与苹果或三星的高端手机相媲美,而性价比更高。华为的目标是通过技术进步来凸显自己,这将使其在高端市场竞争中处于有利位置,进而提高盈利能力。
本篇来自 BigSweetee 的投稿,讲解了如何用 gradle接入热修复tinker,希望对大家有所帮助!
BigSweetee 的博客地址:
http://blog.csdn.net/qq_15527709/
今天研究了一天的热修复,热修复,简单的来讲就是在不需要发包的情况下,修改你线上应用的bug,接入使用后对于我这种小白来说还是很神奇的,同时也考虑了一下,要不要接入我们的项目中,这样就不用因为一个小BUG而去再次发包了,不过,就算要接入项目中,也还有很多坑需要踩,tinker有俩种接入方式,一种命令行接入,一种是gradle接入,本篇只讲 gradle接入,下篇我在补充命令行,主要用于自己做个记录,把踩得坑和感想写下来.
将这个 C/C++编译链接生成二进制文件的这个过程是谁做的?
首先,基本的配置
在 project 的 build中配置如下
dependencies { classpath 'com.android.tools.build:gradle:2.2.2' classpath "com.tencent.tinker:tinker-patch-gradle-plugin:${TINKER_VERSION}" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files }
TINKER_VERSION 需要在 gradle.properties 中进行配置
TINKER_VERSION=1.7.7
这里这样写的目的是把资源统一管理,接下来是配置app的build,官方文档推荐我们使用这个,地址为:
https://github.com/Tencent/tinker/blob/master/tinker-sample-android/app/build.gradle
于是我全部 copy 了过来,不过里面的几个地方还是需要修改的 。
这里因为源码太多,有兴趣的同学可以点击下方阅读全文,去原文中进行查看学习。
主要修改了下面几个地方
buildConfigField "String", "TINKER_ID", "\"${getTinkerIdValue()}\""
官方文档默认是将git提交的记录作为think_id记录下来,这里我不需要,所以我改成下图
buildConfigField "String", "TINKER_ID", "\"1.0\""
我直接写死,需要git提交记录作为tinker_id的也可以按照官方文档推荐的。同时,这些地方也可以相应的替换掉
//废弃 /*def getTinkerIdValue() { return hasProperty("TINKER_ID") ? TINKER_ID : gitSha() }*/ tinkerId = "1.0"/*getTinkerIdValue()*/
接下来修改自己的签名文件,我这里配置的文件路径是自己电脑上面的,这个位置大家需要修改为自己的签名文件路径,也可以按照我的去生成一个签名文件,我相信这个还是很简单的。
//配置自己的签名文件,签名文件的生成和导入可以去百度,本篇不讲解 signingConfigs { release { try { keyAlias 'china' keyPassword '123456' storeFile file('D:/work/release.jks') storePassword '123456' } catch (ex) { throw new InvalidUserDataException(ex.toString()) }
} debug { storeFile file('D:/work/debug.jks') keyAlias 'china' keyPassword '123456' storePassword '123456' } }
buildTypes { release { minifyEnabled true signingConfig signingConfigs.release proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } debug { debuggable true minifyEnabled false signingConfig signingConfigs.debug proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } }
官网文档没有设置 debug 的 proguardFiles getDefaultProguardFile(‘proguard-android.txt’), ‘proguard-rules.pro’ 也就是说没有应用混淆文件,这样是不会生成 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") } } } } }
这下面主要是设置生成的文件所在的目录是什么。配置完上面之后,build就配置好了,接下来配置 application, 先上代码:
package com.anlaiye.swt.gradletest; import android.app.Application; import android.content.Context; import android.content.Intent; import android.support.multidex.MultiDex; import com.tencent.tinker.anno.DefaultLifeCycle; import com.tencent.tinker.lib.tinker.TinkerInstaller; import com.tencent.tinker.loader.app.ApplicationLike; import com.tencent.tinker.loader.shareutil.ShareConstants; @DefaultLifeCycle(application = ".SimpleTinkerInApplication", flags = ShareConstants.TINKER_ENABLE_ALL, loadVerifyFlag =true) public class SimpleTinkerInApplicationLike extends ApplicationLike { public SimpleTinkerInApplicationLike(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); TinkerInstaller.install(this); } @Override public void onCreate() { super.onCreate(); } }
这个 application 官网有提供的,也可以copy我的,ApplicationLike 并不是一个application,真正的 application 是 @DefaultLifeCycle(application = “.SimpleTinkerInApplication” 这个,所以在 androidmanifest 中配置 applica的 name:
<application android:name=".SimpleTinkerInApplication" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity>
</application>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
同时配置读取sd卡的权限,接下来测试:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.anlaiye.swt.gradletest.MainActivity"> <TextView android:id="@+id/tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="111"/> <Button android:layout_below="@+id/tv" android:onClick="loadPath" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="更新"/>
</RelativeLayout>
第一次我设置 textview 的值是111,然后设置按钮调用 onclick:
package com.anlaiye.swt.gradletest; import android.os.Bundle; import android.os.Environment; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.Toast; import com.tencent.tinker.lib.tinker.TinkerInstaller; import java.io.File; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } //加载补丁 public void loadPath(View view) { String path = Environment.getExternalStorageDirectory().getAbsolutePath()+"/patch_signed_7zip.apk"; File file = new File(path); if (file.exists()){ Toast.makeText(this, "补丁已经存在", Toast.LENGTH_SHORT).show(); TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), path); }else { Toast.makeText(this, "补丁已经不存在", Toast.LENGTH_SHORT).show(); } } }
然后我运行APP,会生成如下的目录结构:
同时界面效果
接下来做如下修改:
//老版本的文件所在的位置,大家也可以动态配置,不用每次都在这里修改 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-0313-16-49-55.apk" //proguard mapping file to build patch apk tinkerApplyMappingPath = "${bakPath}/app-release-0313-16-49-55-mapping.txt" //resource R.txt to build patch apk, must input if there is resource changed tinkerApplyResourcePath = "${bakPath}/app-release-0313-16-49-55-R.txt" //only use for build all flavor, if not, just ignore this field tinkerBuildFlavorDirectory = "${bakPath}/app-1018-17-32-47" }
注意这里是 release 版本,不能 debug 版本,release 版本混着来,比如 oldapk 是 debug 版本,新的 apk 是 release版本,这样是不行的,统一用 release 版本。
这里我将对应路径改为 bakapk 中第一次运行生成的apk文件名字,比如第一次生成的 apk文件名为 app-release-0313-16-49-55.apk,所以我把 tinkerOldApkPath 的路径后面的名字修改为对应的 app-release-0313-16-49-55.apk,mapping 文件和 R 文件路径也要对应修改,最后一项 tinkerBuildFlavorDirectory 可以忽略,我一直觉得这里太死板,应该像设置tinker_id 那样对这里进行动态的配置,不用每次都来修改。
接下来我把 textview 改成2222,用来和第一次作区别。然后点击:
生成目录如下
注意箭头所指的文件 ,我们在 onclick 中执行的文件名就是这个,这个文件就是我们需要的最终的文件,然后将这个文件导入SD卡的最外层目录,点击更新按钮,点击后如下 :
因为模拟器不好导入patch的apk所以直接截图了,成功。
本篇只是简单的演示了整个流程,整理了一下容易踩得坑,可以优化的地方还有很多,大家可以在这个基础上自行发挥。
需要准备的工作:
总结一下自己碰到的坑,
首先没有在gradle.properties中配置tinker_id,会提示错误tinker_id not set!!!
debug 没有配置 proguardFiles getDefaultProguardFile(‘proguard-android.txt’), ‘proguard-rules.pro’
导入生成的apk一直没有mapping文件,找了半天才发现。
最后说下热修复的大概流程
最终我们需要的是一个patch.apk文件,这个文件是通过老apk和新apk对比生成的, 具体怎么对比和生成,是tinker控制的,所以我们需要导入他的依赖包,新APK和老APK的mapping 必须是一致的,所以我们需要把老APK的mapping保存起来,方便新APK与他对比。 这样就能得到最终的patch,然后调用 sdk 的TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), path),这样就实现了热修复的目的。
本篇下载地址
csdn下载地址为:
http://download.csdn.net/download/qq_15527709/10017530
考虑到现在csdn改版下载代码都需要分了,所以我重新上传了一份到了github,地址如下:
https://github.com/BigSweet/TinkerGradleDemo
欢迎长按下图 -> 识别图中二维码
或者 扫一扫 关注我的公众号
- Tinker热修复之 —— Gradle接入
- tinker热修复gradle接入
- tinker热修复gradle接入
- tinker热修复gradle接入
- Tinker热修复(gradle接入--成功)
- Android热修复之Tinker接入流程
- 初探Android热修复——tinker接入
- 接入热修复框架TinKer
- Tinker热修复简单接入
- 热修复,Tinker的接入
- 热修复Tinker简单接入
- Android 热修复 Tinker接入
- Tinker热修复接入心得
- Android 热修复 Tinker 接入
- Tinker热修复(命令行接入——未知BUG--.java文件修改不成功)
- 一步一步教你接入tinker 热修复
- 热修复 tinker接入及源码分析
- Android 热修复Tinker接入实战
- Android开发中pullToRefresh的配置
- 反编译:apktool、dex2jar、jd-gui的区别及详解
- win10 python3.5安装mysql的API
- JSP交互---分页查询(条件分页查询)
- 下拉刷新控件一个很好用的开源项目SmartRefreshLayout
- Tinker热修复之 —— Gradle接入
- 隐藏WebAppBuilder构建器中地图右下角esri的logo
- 大数据量下高并发同步
- nginx负载均衡简单配置
- Mysql中数据类型括号中的数字代表的含义
- 关于final关键字的用法
- jsp日期插件My97DatePicker 强大的日期控件 使用方便简单
- node.js升级后npm工作报错
- 调研文档格式