基于微信Tinker的热更新详细说明
2017-09-07 17:24
387 查看
先来吐槽一下,这个更新方法简直6的没话说,经我详细的测试,可以更新类及新增类,删除类什么的,以及对XML资源文件的更新,好像还能更新library,但是我还没测试过,总之可以可以很强势,但是在集成的过程中也很多坑,集了我一天多,报错太多了,网上资料还不怎么详细,找了好久,然后各种方法集入,终于OK了,相对 Andfix(只能对方法进行更新)和Nuwa(只对类更新不能新增类)及HotFix(这个我已经无力吐槽,一旦混淆就报未知的错,混淆文件我都有配置的还出错,我也很绝望就果断放弃了)来说就复杂了一点,在这期间遇到了非常多的bug,然后各种资料API的查阅,终于给搞定了!
注:演示的话我就不弄了,视频肯定会很大,弄成图片也传不上来,(到时我直接放DEMO)这里先说一个小提醒,在执行更新成功后,会自己杀死进程,然后我们要自己手动打开才能看到效果,到时发布给客户用了,建议大家弄个框提示一下,不然以为发生了啥,怎么闪退了。。
2、点击打开SDK接入指南
3、点击下载官方提供的DEMO
现在热修复已经很热门了,比较著名的有阿里巴巴的AndFix、Dexposed,腾讯QQ空间的超级补丁和微信最近开源的Tinker。
Tinker是一个android的热修复库,在不重新安装apk的情况就可以更新dex,library和resource。Tinker区别于AndFix和QQ空间超级补丁采用了更好的dexdiff算法。想要了解详细介绍参考下面微信负责人张绍文的博客链接。
参考博客:
微信 Tinker 负责人张绍文关于 Android 热修复直播分享记录
想要快速学习Tinker的使用,可以只查看Tinker GitHub和配置参考博客。这里我也会具体写一下配置步骤还有自己遇到的问题。Tinker在github上的接入指南(wiki)看起来确实有点难的啊,搞了半天都没搞明白为什么有两个Application,有明白了的给留个言啊。先不管这个问题了,说下具体配置。
这里添加javaVersion最好不要改成VERSION_1_8,改成8可能需要添加其他的支持。sigingConfig里面的debug的配置可以注释掉,否则会报关于debug找不到的错。
下面这个是签名和混淆的配置,这里比较重要,到时候配置Tinker中会有一个变量 useSign = true如果为ture就必须要有签名否则打不了release包,我这用了OkHttp,所以混淆文件也要注意一下,要放上去,不然会报非常多稀奇古怪的错
然后添加依赖,图例中显示的版本号我放置在了gradle.properties文件内
app Build
project Build
gradle.properties
根据官方提供的DEMO来进行下面图片中的配置,复制进去即可,然后在将里面的Application包名换成自己的即可,具体可以参考DEMO,比较多代码就不放截图了,我将比较重要的放上来,下面代码放在build.gradle(APP)的dependencies 下面就直接把剩下的配置原样拷贝过来就可以了。
先说一件事,如果加入下面这段代码,拷贝过来后,一但 Sync Now会立马报错,需要添加git仓库来获取获取git的提交版本号,不然会报错提示 tinkerId is not set
2、配置git跟github并上传一次代码,解决tinkerId is not set问题。
去官网下载git,并安装,给AndroidStudio设置git,点击test
配置github账号,点击test
为project设置git
上传一次项目到github就不会出tinkerId is not set问题了。
此时进行sync Now或者clean project
实现自己的Application类呢,主要还是参考Tinker推荐的方式,通过注解生成Application类。
BaseApplication(manifest中添加这个application)
BaseApplicationLike
然后在manifest中添加这个BaseApplication及加入对于sd卡的读写及网络权限
MainActivity
在As的terminal终端使用命令行生成基础APP信息文件
1、如果是debug就执行下面命令
gradlew assemblehDebug
2、如果是release则执行
gradlew assembleRelease
如果以前没有使用过命令行,可能会下载一些东西,不知道是不是我家里网络的原因,我开的翻墙才下载成功的。如果以前使用过命令就可以直接编译了,编译完成之后可以在你的目录下面看到新生成的apk。
截图示例:
注:这是旧的APP发布后生成的文件信息,不是新的APP生成的,在生成成功后,将目录中的文件名对应下面配置提取出来,放置所需代码块中,在不安装新APP的情况下,这个要备份出来,以后叠加修复需要
配置好后然后在命令行中执行
1、如果是debug就执行下面命令
gradlew tinkerPatchDebug
2、如果是release则执行
gradlew tinkerPatchRelease
编译完成之后可以在你的目录下面看到新生成的(打补丁的)apk。
将生成的patch_signed_7zip.apk下载放到代码中编写的路径下面,运行app点击更新按钮就可以了。
注:此时如果加载补丁成功,应用会闪退,Android Studio 日志控制台会打印出如下日志输出:
再次运行app,可以发现之前打开的注释代码已经被差异化修复到app中,并且打开的日志已经输出到控制台,
混淆文件我就不放上来了,大家看代码复制就行
以上就是我的分享,希望能对大家有帮助
注:演示的话我就不弄了,视频肯定会很大,弄成图片也传不上来,(到时我直接放DEMO)这里先说一个小提醒,在执行更新成功后,会自己杀死进程,然后我们要自己手动打开才能看到效果,到时发布给客户用了,建议大家弄个框提示一下,不然以为发生了啥,怎么闪退了。。
先来说一个官方集入指南点:
1、点击打开Tinker官方SDK文档2、点击打开SDK接入指南
3、点击下载官方提供的DEMO
现在热修复已经很热门了,比较著名的有阿里巴巴的AndFix、Dexposed,腾讯QQ空间的超级补丁和微信最近开源的Tinker。
Tinker是一个android的热修复库,在不重新安装apk的情况就可以更新dex,library和resource。Tinker区别于AndFix和QQ空间超级补丁采用了更好的dexdiff算法。想要了解详细介绍参考下面微信负责人张绍文的博客链接。
参考博客:
微信 Tinker 负责人张绍文关于 Android 热修复直播分享记录
想要快速学习Tinker的使用,可以只查看Tinker GitHub和配置参考博客。这里我也会具体写一下配置步骤还有自己遇到的问题。Tinker在github上的接入指南(wiki)看起来确实有点难的啊,搞了半天都没搞明白为什么有两个Application,有明白了的给留个言啊。先不管这个问题了,说下具体配置。
配置build.gradle
参考官方的build.gradle配置自己的build.gradle,顺序可以不按照官方的https://github.com/Tencent/tinker/blob/master/tinker-sample-android/app/build.gradle,注意compileSdkVersion跟v7最好都不要使用24的这里添加javaVersion最好不要改成VERSION_1_8,改成8可能需要添加其他的支持。sigingConfig里面的debug的配置可以注释掉,否则会报关于debug找不到的错。
下面这个是签名和混淆的配置,这里比较重要,到时候配置Tinker中会有一个变量 useSign = true如果为ture就必须要有签名否则打不了release包,我这用了OkHttp,所以混淆文件也要注意一下,要放上去,不然会报非常多稀奇古怪的错
然后添加依赖,图例中显示的版本号我放置在了gradle.properties文件内
app Build
project Build
gradle.properties
根据官方提供的DEMO来进行下面图片中的配置,复制进去即可,然后在将里面的Application包名换成自己的即可,具体可以参考DEMO,比较多代码就不放截图了,我将比较重要的放上来,下面代码放在build.gradle(APP)的dependencies 下面就直接把剩下的配置原样拷贝过来就可以了。
先说一件事,如果加入下面这段代码,拷贝过来后,一但 Sync Now会立马报错,需要添加git仓库来获取获取git的提交版本号,不然会报错提示 tinkerId is not set
解决方法:
1、其实可以简单粗暴的方式解决,那就是在app/build.gradle中更改以下代码:tinkerId = getTinkerIdValue() //更改为 tinkerId = "TinkerSample"//或者其他你想要的id
2、配置git跟github并上传一次代码,解决tinkerId is not set问题。
去官网下载git,并安装,给AndroidStudio设置git,点击test
配置github账号,点击test
为project设置git
上传一次项目到github就不会出tinkerId is not set问题了。
此时进行sync Now或者clean project
实现自己的Application类呢,主要还是参考Tinker推荐的方式,通过注解生成Application类。
BaseApplication(manifest中添加这个application)
package czb.com.tinker.application; import com.tencent.tinker.loader.app.TinkerApplication; import com.tencent.tinker.loader.shareutil.ShareConstants; /** * Created by AnmiLin on 2017/9/7. */ public class BaseApplication extends TinkerApplication { private static final String TAG=BaseApplication.class.getSimpleName(); public BaseApplication(){ super( //tinkerFlags, which types is supported //dex only, library only, all support ShareConstants.TINKER_ENABLE_ALL, // This is passed as a string so the shell application does not // have a binary dependency on your ApplicationLifeCycle class. "czb.com.tinker.application.BaseApplicationLike"); } }
BaseApplicationLike
package czb.com.tinker.application; import android.annotation.TargetApi; import android.app.Application; import android.content.Context; import android.content.Intent; import android.content.res.AssetManager; import android.content.res.Resources; import android.os.Build; import android.support.multidex.MultiDex; import com.tencent.tinker.lib.tinker.TinkerInstaller; import com.tencent.tinker.loader.app.DefaultApplicationLike; /** * Created by AnmiLin on 2017/9/7. */ public class BaseApplicationLike extends DefaultApplicationLike { public BaseApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) { super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent); } @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) @Override public void onBaseContextAttached(Context base) { super.onBaseContextAttached(base); MultiDex.install(base); TinkerInstaller.install(this); } @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) public void registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks callback) { getApplication().registerActivityLifecycleCallbacks(callback); } }
然后在manifest中添加这个BaseApplication及加入对于sd卡的读写及网络权限
MainActivity
package czb.com.tinker; import android.content.Intent; import android.os.Environment; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Toast; import com.tencent.tinker.lib.tinker.TinkerInstaller; import java.io.File; import java.io.IOException; import czb.com.tinker.util.BugClass2; import czb.com.tinker.util.HttpDownload; import czb.com.tinker.util.LoadBugClass; public class MainActivity extends AppCompatActivity { private static final String TAG = MainActivity.class.getSimpleName(); private String fileState = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.v(TAG, "onCreate"); Log.e(TAG, "i am on patch log"); Toast.makeText(MainActivity.this, new BugClass2().getStr()+"-"+new LoadBugClass().getStr(), Toast.LENGTH_SHORT).show(); findViewById(R.id.btn_load_patch).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { //准备补丁,从assert里拷贝到dex里 new Thread(new Runnable() { @Override public void run() { String url = "http://xxx.com/patch_signed_7zip.apk"; final HttpDownload httpDownload = new HttpDownload(); try { String path = Environment.getExternalStorageDirectory() + File.separator + "TinkerHotFix"; int flag = httpDownload.downToFile(url, path); if (flag == 1) { fileState = "下载完成"; final String pathUrl = path + "/" + HttpDownload.getFileName(url); runOnUiThread(new Runnable() { @Override public void run() { try{ TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), pathUrl); }catch (Exception e){ Log.e(TAG,e.getMessage()); } Log.e(TAG, "安装完成"); Toast.makeText(getApplicationContext(), "安装完成", Toast.LENGTH_LONG).show(); } }); } else if (flag == -1) { fileState = "下载错误"; } runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(MainActivity.this, fileState, Toast.LENGTH_SHORT).show(); } }); } catch (IOException e) { e.printStackTrace(); } } }).start(); } }); } public void onRestart(View view) { //重启应用 Intent i = getPackageManager() .getLaunchIntentForPackage(getPackageName()); i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(i); finish(); android.os.Process.killProcess(android.os.Process.myPid()); } }
运行代码并在终端运行命令行,编译apk
代码编写完成后,运行自己的代码,如果是release的,则会自动在app/build/bakApk目录下会有生成好的apk文件一份以及对应的资源文件txt文件一份比如app-release-0907-15-19-19.apk和app-release-0907-15-19-19-R.txt及app-release-0907-15-19-19-mapping.txt,如果是debug的,则会生成app-debug-0907-15-19-19.apk和app-debug-0907-15-19-19-R.txtdebug 此时app应该已经部署到手机或者模拟器上。在As的terminal终端使用命令行生成基础APP信息文件
1、如果是debug就执行下面命令
gradlew assemblehDebug
2、如果是release则执行
gradlew assembleRelease
如果以前没有使用过命令行,可能会下载一些东西,不知道是不是我家里网络的原因,我开的翻墙才下载成功的。如果以前使用过命令就可以直接编译了,编译完成之后可以在你的目录下面看到新生成的apk。
截图示例:
注:这是旧的APP发布后生成的文件信息,不是新的APP生成的,在生成成功后,将目录中的文件名对应下面配置提取出来,放置所需代码块中,在不安装新APP的情况下,这个要备份出来,以后叠加修复需要
//基准APP信息文件路径(用于存基准的应用(这个应用一定要保留下来,后期修复都需要这个里的文件)) def bakPath = file("${buildDir}/bakApk/") ext { //debug build?是否打开tinker的功能 tinkerEnabled = true //for normal build //打开上面bakApk文件夹,在提取出旧的APK文件名 tinkerOldApkPath = "${bakPath}/app-release-0907-15-19-19.apk" //proguard mapping file to build patch apk //打开上面bakApk文件夹,在提取出旧的mapping文件名,如果是debug就没有mapping文件,则路径可以这么写: // tinkerApplyMappingPath = "${bakPath}/" //relase下就要同上方法提取出来 tinkerApplyMappingPath = "${bakPath}/app-release-0907-15-19-19-mapping.txt" //resource R.txt to build patch apk, must input if there is resource changed //如果更新了资源文件就需要把生成好的放进来 tinkerApplyResourcePath = "${bakPath}/app-release-0907-15-19-19-R.txt" //only use for build all flavor, if not, just ignore this field //tinkerBuildFlavorDirectory = "${bakPath}/app-1018-17-32-47" }
配置好后然后在命令行中执行
1、如果是debug就执行下面命令
gradlew tinkerPatchDebug
2、如果是release则执行
gradlew tinkerPatchRelease
编译完成之后可以在你的目录下面看到新生成的(打补丁的)apk。
将生成的patch_signed_7zip.apk下载放到代码中编写的路径下面,运行app点击更新按钮就可以了。
注:此时如果加载补丁成功,应用会闪退,Android Studio 日志控制台会打印出如下日志输出:
再次运行app,可以发现之前打开的注释代码已经被差异化修复到app中,并且打开的日志已经输出到控制台,
注意:
如果apk运行之后点击按钮没有反应不能进行热修复的话注意检查自己的apk的版本号跟BaseApk是否一致(也就是你build.gradle中修改的版本号一致) configField(“patchVersion”, “1.1”)这个第一次更新都需要改变混淆文件我就不放上来了,大家看代码复制就行
以上就是我的分享,希望能对大家有帮助
代码地址:StudyTinker
相关文章推荐
- 微信Tinker热更新详细使用
- Android微信Tinker热更新详细使用
- 史上最详细Android集成QQ,微信,微博分享(不用第三方)持续更新中
- 腾讯Tinker热更新的详细集成与使用
- Webrtc入门——基于阿里云ubuntu 最新webrtc Android平台编译详细说明
- 微信Android热更新Tinker使用详解(by 星空武哥)
- 基于lucene的案例开发:更新说明
- 史上最详细Android集成QQ,微信,微博分享(不用第三方)持续更新中
- 正阅读微信小说分销系统-功能说明与近期更新2017918
- 基于windows mobile 5.0 开发环境配置说明(详细图文)
- 史上最详细Android集成QQ,微信,微博分享(不用第三方)持续更新中
- wow1.6版更新详细说明
- 微信tinker热更新
- string类详细说明(更新中)
- 史上最详细Android集成QQ,微信,微博分享(不用第三方)持续更新中
- Android热更新:微信Tinker框架的接入与测试
- 微信热修复 Tinker 和 flavor 更新
- 微信Tinker热修补的快速集成详细步骤
- 微信Android热更新Tinker使用详解(星空武哥)
- [持续更新]Android 细节,小技巧 详细说明收集