摘要:最近想着Android有没有什么热补丁更新技术的实现,于是上网搜索。接着实现了这一功能后,记录一下,免得自己忘了,可能文章会有出错的地方,望指出,呵呵

搜索发现有这3种方式可以实现(至于其他的方式,暂不清楚)

1.dexposed github https://github.com/alibaba/dexposed

2.andfix github https://github.com/alibaba/AndFix

3.bsdiff http://blog.csdn.net/lazyer_dog/article/details/47173013

dexposed和andfix是alibaba的开源项目,都是apk增量更新的实现框架,目前dexposed的兼容性较差,只有2.3,4.0~4.4兼容,其他Android版本不兼容或未测试,详细可以去dexposed的github项目主页查看,而andfix则兼容2.3~6.0,所以就拿这个项目来实现增量更新吧。至于bsdiff,只是阅览了一下,还没研究过。

首先 Git clone github https://github.com/alibaba/AndFix,将andfix项目下载下来,Android studio可以在build.gradle里导入andfix,

compile ‘com.alipay.euler:andfix:0.3.1@aar’
但是我是使用module的方式添加andfix,这样可以直接查看编辑源码,而且直接gradle导入的话还有个问题,后面再说。

andfix项目里有sample,导入也行,自己新建也行,我是自己新建项目,接着导入andfix作为module,demo里就两个类,mainactivity和myapplication

andfix里有些文件夹不用导入的,例如tools,doc等,记得新建jniLibs文件夹,libs里的so文件移到jniLibs里。
~~~java
public class MainApplication extends Application {
private static final String TAG = “euler”;

private static final String APATCH_PATH = "/out.apatch";

private static final String DIR = "apatch";//补丁文件夹
/**
 * patch manager
 */
private PatchManager mPatchManager;

@Override
public void onCreate() {
    super.onCreate();
    // initialize
    mPatchManager = new PatchManager(this);
    mPatchManager.init("1.0");
    Log.d(TAG, "inited.");

    // load patch
    mPatchManager.loadPatch();

// Log.d(TAG, “apatch loaded.”);

    // add patch at runtime
    try {
        // .apatch file path
        String patchFileString = Environment.getExternalStorageDirectory()
                .getAbsolutePath() + APATCH_PATH;
        mPatchManager.addPatch(patchFileString);
        Log.d(TAG, "apatch:" + patchFileString + " added.");

        //这里我加了个方法,复制加载补丁成功后,删除sdcard的补丁,避免每次进入程序都重新加载一次
        File f = new File(this.getFilesDir(), DIR + APATCH_PATH);
        if (f.exists()) {
            boolean result = new File(patchFileString).delete();
            if (!result)
                Log.e(TAG, patchFileString + " delete fail");
        }
    } catch (IOException e) {
        Log.e(TAG, "", e);
    }

}

public class MainActivity extends Activity {
private static final String TAG = “euler”;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    toast();
}

@Override
protected void onDestroy() {
    super.onDestroy();
    android.os.Process.killProcess(android.os.Process.myPid());
}

//旧方法,1.apk
private void toast() {        Toast.makeText(this, "old", Toast.LENGTH_SHORT).show();    }}

刚刚说的直接在gradle里导入andfix会有个问题,是在原来的项目中,加载一次补丁后,out.apatch文件会copy到getFilesDir目录下的/apatch文件夹中,在下次补丁更新时,会检测补丁是否已经添加在apatch文件夹下,已存在就不会复制加载sdcard的out.apatch。

原来的addpath方法

public void addPatch(String path) throws IOException {
File src = new File(path);
File dest = new File(mPatchDir, src.getName());
if(!src.exists()){
throw new FileNotFoundException(path);
}
if (dest.exists()) {
Log.d(TAG, “patch [“ + path + “] has be loaded.”);
return;
}
FileUtil.copyFile(src, dest);// copy to patch’s directory
Patch patch = addPatch(dest);
if (patch != null) {
loadPatch(patch);
}
}

修改后,判断apatch下的out.apatch存在即删除掉,重新复制加载sdcard下的out.apatch

public void addPatch(String path) throws IOException {
File src = new File(path);
File dest = new File(mPatchDir, src.getName());
if (!src.exists()) {
throw new FileNotFoundException(path);
}
if (dest.exists()) {
Log.d(TAG, “patch [“ + src.getName() + “] has be loaded.”);
boolean deleteResult = dest.delete();
if (deleteResult)
Log.e(TAG, “patch [“ + dest.getPath() + “] has be delete.”);
else {
Log.e(TAG, “patch [“ + dest.getPath() + “] delete error”);
return;
}
}
FileUtil.copyFile(src, dest);// copy to patch’s directory
Patch patch = addPatch(dest);
if (patch != null) {
loadPatch(patch);
}
}

还有源码混淆

-optimizationpasses 5 # 指定代码的压缩级别
-dontusemixedcaseclassnames # 是否使用大小写混合
-dontskipnonpubliclibraryclasses # 是否混淆第三方jar
-dontpreverify # 混淆时是否做预校验
-verbose # 混淆时是否记录日志
-optimizations !code/simplification/arithmetic,!field/,!class/merging/ # 混淆时所采用的算法

重要,別忘了這些,不混淆andfix包,不混淆native方法

-dontwarn android.annotation
-dontwarn com.alipay.euler.
-keep class com.alipay.euler.
{;}
-keep class
extends java.lang.annotation.Annotation
-keepclasseswithmembernames class * {
native ;
}

接着打包1.apk,

然后修改mainactivity的toast(),打包2.apk

//修改Toast內容,2.apk
private void toast() {
Toast.makeText(this, “new”, Toast.LENGTH_SHORT).show();
}

cmd输入命令,具体参数看usage

如无错误,编译后会生成一个apatch文件,改名成out.apatch

安装打开1.apk

关闭app,将out.apatch放sdcard根目录后,重新打开app,toast方法改变了

ps:

  1. 这里只是简单的测试了一下,没有复杂的功能,而且andfix不支持布局资源等的修改。

  2. github主页的issues反应说Android4.0.4,以及5.0以上版本会crash,不过我使用了两个虚拟机,genymotion的4.2和5.0虚拟机,没有错误,其他请自测

  3. 使用了apk加固时(360加固,百度加固等等),发现在加固前要先apkpatch制作补丁,不能使用加固后的apk制作,否则补丁无法使用,但是在加固前制作的补丁可以很容易的被反编译出源码

demo下载 http://download.csdn.net/detail/ityangjun/9177377