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

android集成热更新就是这么简单!

2017-06-26 18:46 549 查看
项目1.0开始发布,到项目稳定。这期间会有多次版本的迭代。如果每次都是发布版本,会耗费大量的时间成本,因为在版本迭代的过程中,旧的bug解决了,新的bug也可能会出现。即使是最牛逼的程序猿也不敢说自己的项目没有bug。还有就是有的时候项目中虽然存在bug,但是bug级别很低,也没有必要去发布版本。此时,为了满足这种需求。android的热更新就起了大作用了。

热更新原理:

android程序在打包之后,会将class文件打包成dex字节码文件,每个类都对应有自己的字节码文件,手机按照程序流程来读取字节码,这样程序响应的界面就展示在用户眼前了。微信的tinker实际上就是基于这个原理,将修改后的类生成对应的字节码文件,把修改的字节码文件放到服务器,app在运行中请求到服务器的数据(其实就是个文件下载)之后,替换掉原有的字节码文件,这样,程序就实现了bug的修改,也就是热更新。阿里的andfix核心技术是用c++实现的,它的本质上是方法的替换,这个更偏向底层一些。这里简单的介绍一下,java中方法的运行涉及到方法区、java堆、java栈这几个内存区域。堆用来存放对象实例,栈存放的是引用,方法区里面存放了类的字节码,每个类的字节码文件都对应一个方法表,这个方法表中对应的就是类中的方法,类中的方法压栈之后就是栈中的一个栈帧,这样一帧一帧的执行完毕之后,方法也就执行完了。andfix就是基于这个原理,替换方法表中的方法,从而完成更新。而java无法做到方法的替换,所以andfix是用c++来实现的。

热更新使用:

第一步:compile ‘com.alipay.euler:andfix:0.5.0@aar’ 添加aar依赖之后,不用在添加so等文件,因为本省aar中包含资源文件。

第二步:初始化PatchManager

public class App extends Application {

public static PatchManager mPatchManager;

@Override
public void onCreate() {
super.onCreate();
mPatchManager = new PatchManager(this);
mPatchManager.init(AppUtils.getVersionName(this));
mPatchManager.loadPatch();
}
}


第三步:生成apatch文件,使用阿里提供的工具

解压后在当前目录下执行以下命令apkpatch.bat -f 新apk -t 旧apk -o output -k 签名文件 -p 密码 -a 别名 -e 别名密码

第四部步:将apatch部署到服务器,app下载,这里我就直接放到sd卡下模拟一下,

private void update() {
String patchFileStr = Environment.getExternalStorageDirectory().getAbsolutePath() +"/fix.apatch" ;
try {
mPatchManager.addPatch(patchFileStr);
T.showShort(this,"修复成功!!!");

} catch (IOException e) {
e.printStackTrace();
}
}


调用addpatch方法就可以替换我们想要改变的方法了。这样bug就解决了,是不是很easy,确实很easy。andfix还没有很好的支持7.0的手机,亲测华为荣耀8运行失败。解决办法:可以把targetSdk版本改到22,不去兼容6.0以上的特性,这样热更新使用是没问题的。andfix也有一些不足,就是无法增加类和变量,部分方法无法替换(自己试试)。但是即使这样对与一般app的迭代作用也很大了。

Tinker的集成与常见问题的解决

第一步:

//可选,生成application
provided 'com.tencent.tinker:tinker-android-anno:1.7.11'
//核心jar包
compile 'com.tencent.tinker:tinker-android-lib:1.7.11'


第二步:

@DefaultLifeCycle(application = ".SimpleTinkerInApplication",
flags = ShareConstants.TINKER_ENABLE_ALL,
loadVerifyFlag = false)

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);

}

/**
* 绑定了application的生命周期,
* 在application中的操作移植到此方法
*/
@Override
public void onCreate() {
super.onCreate();
}

}


添加ApplicationLike的子类,这个类在作用就是绑定application的生命周期,看名字就能知道,是像application的一个类,但是真正的application在哪里呢?看@DefaultLifeCycle(…),通过注解生成了我们的application,这个application的名称叫SimpleTinkerInApplication,所以在xml文件就要相应的改变。

<application
android:name=".SimpleTinkerInApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<!-- 别忘了配置一下tinkerId-->
<meta-data
android:name="TINKER_ID"
android:value="100" />
</application>


第三步:

生成patch文件,patch.apk是新的apk和旧的apk做diff运算生成的,官方提供了命令行工具

java -jar tinker-patch-cli-1.7.11.jar -old 1.0.apk -new 1.0(新).apk -config tinker_config.xml -out output

tinker-patch-cli-1.7.11.jar的生成也很简单,在官方的demo里面找到一下gradle,然后运行即可。



运行命令行生成如下:



第四步:加载patch文件,和andfix文件是一样的,这个应该在服务器端下载到手机本地,然后在手机本地读取。

TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(),
Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed.apk");


这样我们就可以很愉快玩耍了。但是运行之后会发现app加载完patch文件后会重启,这个可定时不爽的吗,总不能用户在使用着app的时候突然就重启了吧,鬼知道是那里出了问题。好在官方也给出了解决方法,就是自定义SampleResultService。然后在SimpleTinkerInApplicationLike的onBaseContextAttached()方法中添加如下代码:

@Override
public void onBaseContextAttached(Context base) {

super.onBaseContextAttached(base);
TinkerManager.setTinkerApplicationLike(this);
//should set before tinker is installed
TinkerManager.setUpgradeRetryEnable(true);
TinkerManager.installTinker(this);

}


TinkerManager是官方demo中提供的一个tinker管理类,可以直接在拿出来用主要看

/**
* you can specify all class you want.
* sometimes, you can only install tinker in some process you want!
*
* @param appLike
*/
public static void installTinker(ApplicationLike appLike) {
if (isInstalled) {
TinkerLog.w(TAG, "install tinker, but has installed, ignore");
return;
}
//you can set your own upgrade patch if you need
AbstractPatch upgradePatchProcessor = new UpgradePatch();
TinkerInstaller.install(appLike,new DefaultLoadReporter(appLike.getApplication()),new DefaultPatchReporter(appLike.getApplication())
,new DefaultPatchListener(appLike.getApplication()),SampleResultService.class,upgradePatchProcessor);

isInstalled = true;
}


实际上我们使用了TinkerInstaller中install的多个参数的方法,这个方法就是微信提供给我们自定义的。这个方法传入了ApplicationLike,那么ApplicationLike 是如何起作用的呢? 我们简单看一下源码。

通过注解会生成

/**
*
* Generated application for tinker life cycle
*
*/
public class SimpleTinkerInApplication extends TinkerApplication {

public SimpleTinkerInApplication() {
super(7, "com.iwintrue.todoapplication.SimpleTinkerInApplicationLike", "com.tencent.tinker.loader.TinkerLoader", false);
}

}


进入TinkerApplication,其继承了Application。

/**
* Created by zhangshaowen on 16/3/8.
* <p/>
* A base class for implementing an Application that delegates to an {@link ApplicationLifeCycle}
* instance. This is used in conjunction with secondary dex files so that the logic that would
* normally live in the Application class is loaded after the secondary dexes are loaded.
*/
public abstract class TinkerApplication extends Application {

//oh, we can use ShareConstants, because they are loader class and static final!
private static final int    TINKER_DISABLE         = ShareConstants.TINKER_DISABLE;
private static final String INTENT_PATCH_EXCEPTION = ShareIntentUtil.INTENT_PATCH_EXCEPTION;
private static final String TINKER_LOADER_METHOD   = "tryLoad";
/**
* tinkerFlags, which types is supported
* dex only, library only, all support
* default: TINKER_ENABLE_ALL
*/
private final int     tinkerFlags;
/**
* whether verify md5 when we load dex or lib
* they store at data/data/package, and we had verity them at the :patch process.
* so we don't have to verity them every time for quicker!
* default:false
*/
private final boolean tinkerLoadVerifyFlag;
private final String  delegateClassName;
private final String  loaderClassName;

/**
* if we have load patch, we should use safe mode
*/
private       boolean useSafeMode;
private       Intent  tinkerResultIntent;

private ApplicationLike applicationLike = null;

private long applicationStartElapsedTime;
private long applicationStartMillisTime;

/**
* current build.
*/
protected TinkerApplication(int tinkerFlags) {
this(tinkerFlags, "com.tencent.tinker.loader.app.DefaultApplicationLike", TinkerLoader.class.getName(), false);
}

/**
* @param delegateClassName The fully-qualified name of the {@link ApplicationLifeCycle} class
*                          that will act as the delegate for application lifecycle callbacks.
*/
protected TinkerApplication(int tinkerFlags, String delegateClassName,
String loaderClassName, boolean tinkerLoadVerifyFlag) {
this.tinkerFlags = tinkerFlags;
this.delegateClassName = delegateClassName;
this.loaderClassName = loaderClassName;
this.tinkerLoadVerifyFlag = tinkerLoadVerifyFlag;
}

protected TinkerApplication(int tinkerFlags, String delegateClassName) {
this(tinkerFlags, delegateClassName, TinkerLoader.class.getName(), false);
}

private ApplicationLike createDelegate() {
try {
// Use reflection to create the delegate so it doesn't need to go into the primary dex.
// And we can also patch it
Class<?> delegateClass = Class.forName(delegateClassName, false, getClassLoader());
Constructor<?> constructor = delegateClass.getConstructor(Application.class, int.class, boolean.class,
long.class, long.class, Intent.class);
return (ApplicationLike) constructor.newInstance(this, tinkerFlags, tinkerLoadVerifyFlag,
applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);
} catch (Throwable e) {
throw new TinkerRuntimeException("createDelegate failed", e);
}
}

private synchronized void ensureDelegate() {
if (applicationLike == null) {
applicationLike = createDelegate();
}
}

/**
* Hook for sub-classes to run logic after the {@link Application#attachBaseContext} has been
* called but before the delegate is created. Implementors should be very careful what they do
* here since {@link android.app.Application#onCreate} will not have yet been called.
*/
private void onBaseContextAttached(Context base) {
applicationStartElapsedTime = SystemClock.elapsedRealtime();
applicationStartMillisTime = System.currentTimeMillis();
loadTinker();
ensureDelegate();
applicationLike.onBaseContextAttached(base);
//reset save mode
if (useSafeMode) {
String processName = ShareTinkerInternals.getProcessName(this);
String preferName = ShareConstants.TINKER_OWN_PREFERENCE_CONFIG + processName;
SharedPreferences sp = getSharedPreferences(preferName, Context.MODE_PRIVATE);
sp.edit().putInt(ShareConstants.TINKER_SAFE_MODE_COUNT, 0).commit();
}
}

@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
Thread.setDefaultUncaughtExceptionHandler(new TinkerUncaughtHandler(this));
onBaseContextAttached(base);
}

private void loadTinker() {
//disable tinker, not need to install
if (tinkerFlags == TINKER_DISABLE) {
return;
}
tinkerResultIntent = new Intent();
try {
//reflect tinker loader, because loaderClass may be define by user!
Class<?> tinkerLoadClass = Class.forName(loaderClassName, false, getClassLoader());

Method loadMethod = tinkerLoadClass.getMethod(TINKER_LOADER_METHOD, TinkerApplication.class);
Constructor<?> constructor = tinkerLoadClass.getConstructor();
tinkerResultIntent = (Intent) loadMethod.invoke(constructor.newInstance(), this);
} catch (Throwable e) {
//has exception, put exception error code
ShareIntentUtil.setIntentReturnCode(tinkerResultIntent, ShareConstants.ERROR_LOAD_PATCH_UNKNOWN_EXCEPTION);
tinkerResultIntent.putExtra(INTENT_PATCH_EXCEPTION, e);
}
}

@Override
public void onCreate() {
super.onCreate();
ensureDelegate();
applicationLike.onCreate();
}

@Override
public void onTerminate() {
super.onTerminate();
if (applicationLike != null) {
applicationLike.onTerminate();
}
}

@Override
public void onLowMemory() {
super.onLowMemory();
if (applicationLike != null) {
applicationLike.onLowMemory();
}
}

@TargetApi(14)
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
if (applicationLike != null) {
applicationLike.onTrimMemory(level);
}
}

@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (applicationLike != null) {
applicationLike.onConfigurationChanged(newConfig);
}
}

@Override
public Resources getResources() {
Resources resources = super.getResources();
if (applicationLike != null) {
return applicationLike.getResources(resources);
}
return resources;
}

@Override
public ClassLoader getClassLoader() {
ClassLoader classLoader = super.getClassLoader();
if (applicationLike != null) {
return applicationLike.getClassLoader(classLoader);
}
return classLoader;
}

@Override
public AssetManager getAssets() {
AssetManager assetManager = super.getAssets();
if (applicationLike != null) {
return applicationLike.getAssets(assetManager);
}
return assetManager;
}

@Override
public Object getSystemService(String name) {
Object service = super.getSystemService(name);
if (applicationLike != null) {
return applicationLike.getSystemService(name, service);
}
return service;
}

@Override
public Context getBaseContext() {
Context base = super.getBaseContext();
if (applicationLike != null) {
return applicationLike.getBaseContext(base);
}
return base;
}

public void setUseSafeMode(boolean useSafeMode) {
this.useSafeMode = useSafeMode;
}

public boolean isTinkerLoadVerifyFlag() {
return tinkerLoadVerifyFlag;
}

public int getTinkerFlags() {
return tinkerFlags;
}
}


最终通过createDelegate() ,反射出SimpleTinkerInApplicationLike这个类,这正是TinkerApplication构造函数的第二个参数。在TinkerApplication的oncreate方法中也调用了SimpleTinkerInApplicationLike的oncrate方法。明白其中的逻辑之后,我们就可以自定义自己的SampleResultService了,官方给出的是判断了只用手机在锁屏和后台运行的时候合并patch文件,重启app使增加的patch生效。这样还不错,代码可以自己去demo中抠出来。最后别忘了在xml文件中注册你的servise!!!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息