您的位置:首页 > 移动开发 > 微信开发

基于微信Tinker的热更新详细说明

2017-09-07 17:24 387 查看
  先来吐槽一下,这个更新方法简直6的没话说,经我详细的测试,可以更新类及新增类,删除类什么的,以及对XML资源文件的更新,好像还能更新library,但是我还没测试过,总之可以可以很强势,但是在集成的过程中也很多坑,集了我一天多,报错太多了,网上资料还不怎么详细,找了好久,然后各种方法集入,终于OK了,相对 Andfix(只能对方法进行更新)和Nuwa(只对类更新不能新增类)及HotFix(这个我已经无力吐槽,一旦混淆就报未知的错,混淆文件我都有配置的还出错,我也很绝望就果断放弃了)来说就复杂了一点,在这期间遇到了非常多的bug,然后各种资料API的查阅,终于给搞定了!

  注:演示的话我就不弄了,视频肯定会很大,弄成图片也传不上来,(到时我直接放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

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: