官方Github:https://github.com/Meituan-Dianping/Robust
0、前言
网上关于robust使用的帖子很多,大部分帖子都会忽略某些细节,在没有搞明白robust原理的情况下,往往因为这些细节,导致热更新失败。我在用robust时也是各种碰壁,在此总结和重新梳理robust的应用流程。
1、总体流程
如果我们要写一个robust的demo,实现热更新功能,应该分下面几个步骤。
第一步:写一个有“bug”的demo
第二步:开启热更新,修改代码,
第三步:生成和部署jar包
第四步:测试热更新效果。
2、分步实现
这里分步实现各个流程。follow me!
2.1、写一个有“bug”的demo
(1)声明权限
首先的首先是建一个android工程,然后声明权限。
Robust需要在AndroidManifest中声明如下权限(如图1-0)
(2)app的gradle文件中引入依赖
apply plugin:'com.android.application'
//apply plugin: 'auto-patch-plugin'(热更新代码后要开启这行,并注释掉下面那行)
apply plugin:'robust'
compile'com.meituan.robust:robust:0.4.5'
(3)工程的gradle文件加入classpath
classpath 'com.meituan.robust:gradle-plugin:0.4.5'
classpath 'com.meituan.robust:auto-patch-plugin:0.4.5'
(4)手动增加和修改robust.xml文件
注意事项:
1、robust.xml文件和src是同级目录,如下图1-4
2、xml文件是从上面官方github的demo中拷贝过来的。这个xml是热更新的一个配置文件。
3、修改robust.xml文件
一般来说只需要修改三个地方,如下图1-5.
(5)写demo
用三个class文件(分别是:MainActivity.class, SecondActiviry.class, PatchManipulateImp.class)来写一个简单的demo。
【MainActivity.class】
package com.gzyct.myapplication;
public class MainActivity extends AppCompatActivity {
protected Button load;
protected Button skip;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.setContentView(R.layout.main);
initView();
}
/**
*初始化界面
*/
private void initView() {
// 加载patch
load= (Button) findViewById(R.id.load);
load.setOnClickListener(newView.OnClickListener() {
@Override
public voidonClick(View v) {
newPatchExecutor(getApplicationContext(),newPatchManipulateImp(),newcallBack()).start();
}
});
// 页面跳转
skip= (Button) findViewById(R.id.skip);
skip.setOnClickListener(newView.OnClickListener() {
@Override
public voidonClick(View v) {
Intent intent =newIntent(MainActivity.this, SecondActivity.class);
startActivity(intent);
}
});
}
private class callBack implements RobustCallBack {
@Override
public void onPatchListFetched(booleanresult,booleanisNet, List patches) {
// TODO
}
@Override
public void onPatchFetched(booleanresult,booleanisNet, Patch patch) {
// TODO
}
@Override
public void onPatchApplied(booleanresult, Patch patch) {
// TODO
}
@Override
public void logNotify(String log, String where) {
// TODO
}
@Override
public void exceptionNotify(Throwable throwable, String where) {
// TODO
}
}
}
[MainActivity的UI图]
【PatchManipulateImp.class文件代码】
这段代码中要重载三个函数:
protectedList fetchPatchList(Context context)
protected booleanverifyPatch(Context context, Patch patch)
protected booleanensurePatchExist(Patch patch)
.fetchPatchList是作用什么用?
1、保存原始jar包存储的路径以及设置运行的jar包
.verifyPatch做什么用?
you can verify your patches here。官方demo的解释
package com.gzyct.myapplication;
import android.content.Context;
import android.os.Environment;
import com.meituan.robust.Patch;
import com.meituan.robust.PatchManipulate;
importjava.io.File;
importjava.util.ArrayList;
importjava.util.List;
/**
* Created by gt on 2017/11/15.
*/
public class PatchManipulateImp extends PatchManipulate {
@Override
protected List fetchPatchList(Context context) {
//将app自己的robustApkHash上报给服务端,服务端根据robustApkHash来区分每一次apk build来给app下发补丁
//apkhash is the unique identifier for apk,so you cannnot patch wrong apk.
//String robustApkHash = RobustApkHashUtils.readRobustApkHash(context);
Patch patch =newPatch();
patch.setName("123");
//we recommend LocalPath store the origin patch.jar which may be encrypted,while TempPath is the true runnable jar
// 下面的robust是生成补丁后,存在在手机的robust文件夹在中。如果你改成qwe,生成的补丁就放在qwe文件夹中。所以部署jar包到手机的时候,相关adb命令的参数和这里要对应上。
patch.setLocalPath(Environment.getExternalStorageDirectory().getPath() + File.separator+"robust"+ File.separator+"patch.jar");
patch.setTempPath(Environment.getExternalStorageDirectory().getPath() + File.separator+"robust"+ File.separator+"patch");
//setPatchesInfoImplClassFullName的包名是图1-5第三个红色框中的包名+PatchesInfoImpl, 这样robust可以通过xml文件找到当前类
patch.setPatchesInfoImplClassFullName("com.gzyct.myapplication.PatchesInfoImpl");
List patches =newArrayList();
patches.add(patch);
returnpatches;
}
@Override
protected booleanverifyPatch(Context context, Patch patch) {
return true;
}
@Override
protected booleanensurePatchExist(Patch patch) {
return true;
}
}
【SecondActivity.class的代码】
public class SecondActivity extends Activity {
@Override
protected void onCreate(@NullableBundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.second);
title= (TextView) findViewById(R.id.title);
title.setText("hello , this is patched activity,and patch is success-13231313131123441412---");
title.setOnClickListener(newView.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(),"asfawdfawf", Toast.LENGTH_LONG).show();
}
});
init();
}
TextViewtitle;
private voidinitView() {
title= (TextView) findViewById(R.id.title);
title.setText("hello , this is patched activity");
}
【SecondActivity.classUI图】
至此一个有“bug”的demo就写完了。
写完代码后,开始打包,下面将介绍如何打包以及热更新的准备工作。
2.2 打包
.(1)开启混淆
在app的gradle文件中开发混淆
.(2)打包
可以通过命令方式或者传统方式打包。(我是用传统方式打包滴)
.通过命令打包
gradlew clean assembleRelease --stacktrace --no-daemon
.传统方式打包
.2.3、开始热更新和修改代码
开启热更新前,有些准备工作要做,如下
(1)准备工作
.新建robust文件夹
src的同级目录新建robust文件夹
.拷贝上面红线的文件到robust文件夹
在打包后(借用人家的图),在output下面将这几个文件夹拷贝到刚才我们新建的robust文件夹中。
(2)开启“热更新模式”
在app的gradle文件中,修改配置,入下图1-15,注释掉第三行,并开启第二行,和写bug代码时刚好相反。
(3)修改“bug”
在secondactivity.class中修改“bug”
2.4、生成和部署补丁包
(1)生成补丁包
补丁包可以通过命令生成,也可以用传统的签名打包方式生成。这里介绍用命令方式生成补丁,具体如下:
通过命令:gradlew clean assembleRelease --stacktrace --no-daemon生成补丁(如下图1-17):
执行结束后,出现BUILD FAILED,没有关系。只要patch end successfully就ok啦!~~(图下图1-18)
接下来,我们可以发现:在app->build->output->robust目录下生成patch.jar以及patch.dex两个文件(如下图1-19)。
(2)热更新部署
上面的patch.jar是我们需要的热更新文件。通过adb命令将jar包传到手机(如下图1-20)
用adb将jar传到手机时可能会遇到几个坑。
坑1
报错:Unable to create Debug Bridge: Unable to start adb server: error: could not install *smartsocket* listener: cannot bind to 127.0.0.1:5037: 通常每个套接字地址(协议/网络地址/端口)只允许使用一次。
原因:端口被占用
解决办法:
step1、通过netstat -aon|findstr “5037” 找出占用5037端口号的对应pid号。
step2、通过任务管理器kill进程
step3:重启adb
坑2
报错:no device *** command ***
没有安装手机驱动:(
4、测试
在没有load patch,直接skip new activity时的UI如下:
load patch之后
测试结论:
热更新成功