Android 增量更新和升级详解手机开发

在年初的时候,尝试了一把热修复技术,当时选择的是阿里的andfix,使用起来也很简单,这里就不在多少,如果你对andfix有兴趣请链接:点击打开链接。虽然网上将热修复的文章很多,不过我还是想说原理,然后配合代码,我想这样大家理解更加深刻。

原理

其实就是用ClassLoader加载机制,覆盖掉有问题的方法。我们知道一个ClassLoader可以包含多个dex文件,每个dex文件是一个Element,多个dex文件排列成一个有序的数组dexElements,当找类的时候,会按顺序遍历dex文件,然后从当前遍历的dex文件中找类,如果找类则返回。那么我们热修复的原理就是用新的dex去替换有问题的dex,这里借用qq团队的一张图,可能更方便的说明热修复的原理。
如果有想对ClassLoader做深入了解的同学,可以去看我之前一篇对ClassLoader的分析:
点击打开链接
Android 增量更新和升级详解手机开发

热修复(打补丁)

打补丁:服务端通过新版本APK和旧版本APK生成patch补丁(也成为差分包),客户端更新的时候只需要下载差分包到本地,然后从system/app取出旧版本APK,通过差分包来合成新版本的APK,这个过程实际上就是打补丁。
打补丁的步骤:
拷贝资源 拷贝旧版本APK以及新版本APK到SD卡。为了后面进行生成差分包
安装旧版本APK 安装旧版本的APK
生成补丁 生成差分包。这个实际上应该是在服务端完成
打补丁 通过差分包及旧版本APK生成新版本APK
安装新版本APK 安装生成的新版本APK
获取某个应用的APK安装文件 在真正的增量更新过程中,旧版本Apk应该从/data/app底下获取,拷贝到SD卡,进行打补丁。当然,也可以不拷贝,直接使用该路径。

为了方便,我们就在本地放两个本地,为了方便讲解,我们先定义4个变量。

String srcDir = Environment.getExternalStorageDirectory().toString() + "/DaemonProcess-1.apk"; 
String destDir1 = Environment.getExternalStorageDirectory().toString() + "/DaemonProcess-2.apk"; 
String destDir2 = Environment.getExternalStorageDirectory().toString() + "/DaemonProcess-3.apk"; 
String patchDir = Environment.getExternalStorageDirectory().toString() + "/DaemonProcess.patch";

srcDir:旧版本apk路径。也就是已安装的旧版应用的APK地址。为了便于演示,这边直接写死路径。
实际开发中要想真正获取旧版apk地址,可通过如下代码获取:

String appDir = getPackageManager().getApplicationInfo(packageName, 0).sourceDir;

destDir1:新版本的apk路径。

destDir2:新版本的apk路径。通过差分包+旧版本APK合成新版本APK。

patchDir:差分包。通过旧版本APK+新版本APK生成差分包。

这里生成差分包和合成新apk,用的是jni做的,代码如下:
生成差分包Native方法
public class DiffUtils {  	static DiffUtils instance;  	public static DiffUtils getInstance() { 		if (instance == null) 			instance = new DiffUtils(); 		return instance; 	}  	static { 		System.loadLibrary("ApkPatchLibrary"); 	}  	/** 	 * native方法 比较路径为oldPath的apk与newPath的apk之间差异,并生成patch包,存储于patchPath 	 */ 	public native int genDiff(String oldApkPath, String newApkPath, String patchPath); }

合成新包Native方法:

public class PatchUtils { 
 
	static PatchUtils instance; 
 
	public static PatchUtils getInstance() { 
		if (instance == null) 
			instance = new PatchUtils(); 
		return instance; 
	} 
 
	static { 
		System.loadLibrary("ApkPatchLibrary"); 
	} 
 
	/** 
	 * native方法 使用路径为oldApkPath的apk与路径为patchPath的补丁包,合成新的apk,并存储于newApkPath 
	 */ 
	public native int patch(String oldApkPath, String newApkPath, String patchPath); 
}

这里用到了一个ndk,如果有需要了解如何生成动态库文件的可以访问下面的
点击打开链接

Android 增量更新和升级详解手机开发

热补丁修复步骤:
1,从服务端加载查分包文件(我们这里模拟下,将差分包放到assert文件下)
private class CopyTask extends AsyncTask<String, Void, Integer> { 
 
        @Override 
        protected Integer doInBackground(String... params) { 
 
            for (int i = 0; i < params.length; i += 2) { 
                try { 
                    File file = new File(params[i]); 
                    if (!file.exists()) 
                        FileUtils.createFile(file); 
 
                    InputStream is; 
                    OutputStream os = new FileOutputStream(params[i]); 
                    is = getAssets().open(params[i + 1]); 
                    byte[] buffer = new byte[1024]; 
                    int length = is.read(buffer); 
                    while (length > 0) { 
                        os.write(buffer, 0, length); 
                        length = is.read(buffer); 
                    } 
                    os.flush(); 
                    is.close(); 
                    os.close(); 
                } catch (Exception e) { 
                    handler.obtainMessage(1).sendToTarget(); 
                    return null; 
                } 
            } 
            handler.obtainMessage(0).sendToTarget(); 
            return null; 
        } 
 
        @Override 
        protected void onPostExecute(Integer integer) { 
            super.onPostExecute(integer); 
            loadding.setVisibility(View.GONE); 
        } 
    }

2,合成新的apk(jni会自动判断是否合成成功)

private class PatchTask extends AsyncTask<String, Void, Integer> { 
 
        @Override 
        protected Integer doInBackground(String... params) { 
 
            try { 
 
                int result = PatchUtils.getInstance().patch(srcDir, destDir2, patchDir); 
                if (result == 0) { 
                    handler.obtainMessage(4).sendToTarget(); 
                    return WHAT_SUCCESS; 
                } else { 
                    handler.obtainMessage(5).sendToTarget(); 
                    return WHAT_FAIL_PATCH; 
                } 
            } catch (Exception e) { 
                e.printStackTrace(); 
            } 
            return WHAT_FAIL_PATCH; 
        } 
 
        @Override 
        protected void onPostExecute(Integer integer) { 
            super.onPostExecute(integer); 
            loadding.setVisibility(View.GONE); 
        } 
    }

3,安装新的apk

  private void install(String dir) { 
        String command = "chmod 777 " + dir; 
        Runtime runtime = Runtime.getRuntime(); 
        try { 
            runtime.exec(command); // 可执行权限 
        } catch (IOException e) { 
            e.printStackTrace(); 
        } 
 
        Intent intent = new Intent(Intent.ACTION_VIEW); 
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 
        intent.setDataAndType(Uri.parse("file://" + dir), "application/vnd.android.package-archive"); 
        startActivity(intent); 
    }

服务端工具以及源码位于Server目录下。目前只在Linux64位的系统下编译,其他系统大家可自行编译。Linux下的可直接修改makefile,windows下可用VC编译。

Diff工具:生成差分包
<!--命令             oldApk              newApk              patch--> 
./linux-x86_64/Diff DaemonProcess-1.apk DaemonProcess-2.apk dp.patch

Patch工具:合并
<!--命令              oldApk              newApk              patch--> 
./linux-x86_64/Patch DaemonProcess-1.apk DaemonProcess-3.apk dp.patch

代码地址:
点击打开链接

原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/5981.html

(0)
上一篇 2021年7月17日
下一篇 2021年7月17日

相关推荐

发表回复

登录后才能评论