Skip to content

LoveqLRC/PatchUpdateDemo

Repository files navigation

增量更新

1.定义native增量更新方法

public class PatchUtils {
/**
 * @param oldApkPath 原来的本地安装的apk路径
 * @param newApkPath 与差分包合并后新的apk路径
 * @param patchPath  差分包路径,从服务器下载下来
 */
public static native void patch(String oldApkPath, String newApkPath,
                                String patchPath);
}

rebulid项目,进入项目\app\build\intermediates\classes\debug目录下,执行 javah (项目名).PatchUtils(例如rc.loveq.patchupdatedemo.utils.PatchUtils) 此时在app\build\intermediates\classes\debug生成.h头文件

2.将生成的.h头文件复制到jni目录下,将bsdiff和bzip里面的的C文件和.h文件复制到jni目录下

3.修改CMakeLists,修改生成的so的名字改为bspatch,修改编译的入口改为bspatch.c

4.这时运行是编译不通过的,需要修改bspatch.c,将#include <bzlib.h>改成#include "bzlib.h",另外要引入

#include "bzlib.c"
#include "crctable.c"
#include "compress.c"
#include "decompress.c"
#include "randtable.c"
#include "blocksort.c"
#include "huffman.c"

以上文件。

5.经过上面几步,运行项目可以正常运行了,另外可以在\app\build\intermediates\cmake\debug\obj目录下,看到生成了对应的libbspatch.so文件。

6.接下来实现patch的native方法,bspatch.c引入#include "rc_loveq_patchupdatedemo_utils_PatchUtils.h"头文件,将main方法改为patch,编写patch的native方法

JNIEXPORT void JNICALL Java_rc_loveq_patchupdatedemo_utils_PatchUtils_patch
  (JNIEnv *env, jclass jclz, jstring old_apk_path, jstring new_apk_path,
      jstring patch_path){
    // 1.封装参数
  	int argc = 4;
  	char * argv[4];
  	// 1.1 转换  jstring -> char*
  	char* old_pak_cstr = (char*)(*env)->GetStringUTFChars(env,old_apk_path,NULL);
  	char* new_apk_cstr = (char*)(*env)->GetStringUTFChars(env,new_apk_path,NULL);
  	char* patch_cstr = (char*)(*env)->GetStringUTFChars(env,patch_path,NULL);
  	// 第0的位置随便给
  	argv[0] = "combine";
  	argv[1] = old_pak_cstr;
  	argv[2] = new_apk_cstr;
  	argv[3] = patch_cstr;

  	// 2.调用上面的方法  int argc,char * argv[]
  	patch(argc,argv);

  	// 3.释放资源
  	(*env)->ReleaseStringUTFChars(env,old_apk_path,old_pak_cstr);
  	(*env)->ReleaseStringUTFChars(env,new_apk_path,new_apk_cstr);
  	(*env)->ReleaseStringUTFChars(env,patch_path,patch_cstr);

  }

7.java层调用patch方法

 public void patch(View view) {
    //1.访问后台,是否需要更新版本
    //2.需要更新版本,提示下载
    //3.下载完成差分之后,调用patch方法合成新的apk
    if (!new File(mPatchPath).exists()) {
        return;
    }
    //这是一个耗时操作应该新开一个线程处理
    PatchUtils.patch(getPackageResourcePath(), mNewApkPath, mPatchPath);

    //4.校验签名
    String v1Signature = SignatureUtils.getSignature(this);
    mTvV1Signature.setText(v1Signature);
    try {
        String v2signature = SignatureUtils.getSignature(mNewApkPath);
        mTvV2Signature.setText(v2signature);
        boolean isEqual = TextUtils.equals(v1Signature, v2signature);
        mTvV1V2IsEqual.setText("isEqual :" + isEqual);
    } catch (Exception e) {
        e.printStackTrace();
    }
    //5.安装最新版本
    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.setDataAndType(Uri.fromFile(new File(mNewApkPath)),
            "application/vnd.android.package-archive");
    startActivity(intent);

}

8.最后服务器生成差分包

bsdiff old.apk new.apk patch.patch

服务器合成差分包

bspatch old.apk new.apk patch.patch

9.签名校验

public class SignatureUtils {
private static final String TAG = "SignatureUtils";
/**
 * 获取当前apk的签名
 *
 * @param context
 * @return
 */
public static String getSignature(Context context) {
    // 通过Context获取当前包名
    String currentApkPackageName = context.getApplicationInfo().packageName;

    Log.e(TAG, "TAG -> " + currentApkPackageName);

    // 通过PackageManager获取所有应用的PackageInfo信息
    PackageManager packageManager = context.getPackageManager();
    List<PackageInfo> packageInfos = packageManager.getInstalledPackages(PackageManager.GET_SIGNATURES);
    for (PackageInfo packageInfo : packageInfos) {
        if (packageInfo.packageName.equals(currentApkPackageName)) {
            // 获取签名
            return packageInfo.signatures[0].toCharsString();
        }
    }
    return null;
}

/**
 * 获取该目录下的apk签名
 *
 * @param path 当前apk路径
 * @return
 */
public static String getSignature(String path) throws Exception {
    // 1.反射实例化PackageParser对象
    Object packageParser = getPackageParser(path);

    // 2.反射获取parsePackage方法
    Object packageObject = getPackageInfo(path, packageParser);

    // 3.调用collectCertificates方法
    Method collectCertificatesMethod = packageParser.getClass().
            getDeclaredMethod("collectCertificates", packageObject.getClass(), int.class);
    collectCertificatesMethod.invoke(packageParser, packageObject, 0);

    // 4.获取mSignatures属性
    Field signaturesField = packageObject.getClass().getDeclaredField("mSignatures");
    signaturesField.setAccessible(true);
    Signature[] mSignatures = (Signature[]) signaturesField.get(packageObject);
    return mSignatures[0].toCharsString();
}

/**
 * 创建PackageParser.Package类,兼容5.0
 *
 * @param path
 * @return
 * @throws Exception
 */
private static Object getPackageInfo(String path, Object packageParser) throws Exception {
    if (Build.VERSION.SDK_INT >= 21) {
        Class<?>[] paramClass = new Class[2];
        paramClass[0] = File.class;
        paramClass[1] = int.class;
        Method parsePackageMethod = packageParser.getClass().getDeclaredMethod("parsePackage", paramClass);

        // 3.反射执行parsePackage方法
        Object[] paramObject = new Object[2];
        paramObject[0] = new File(path);
        paramObject[1] = 0;
        parsePackageMethod.setAccessible(true);
        return parsePackageMethod.invoke(packageParser, paramObject);
    } else {
        Class<?>[] paramClass = new Class[4];
        paramClass[0] = File.class;
        paramClass[1] = String.class;
        paramClass[2] = DisplayMetrics.class;
        paramClass[3] = int.class;
        Method parsePackageMethod = packageParser.getClass().getDeclaredMethod("parsePackage", paramClass);

        // 3.反射执行parsePackage方法
        Object[] paramObject = new Object[4];
        paramObject[0] = new File(path);
        paramObject[1] = path;
        DisplayMetrics metrics = new DisplayMetrics();
        metrics.setToDefaults();
        paramObject[2] = metrics;
        paramObject[3] = 0;
        parsePackageMethod.setAccessible(true);
        return parsePackageMethod.invoke(packageParser, paramObject);
    }
}

/**
 * 创建PackageParser类
 *
 * @param path
 * @return
 * @throws Exception
 */
private static Object getPackageParser(String path) throws Exception {
    Class<?> packageParserClazz = Class.forName("android.content.pm.PackageParser");
    // 版本兼容
    if (Build.VERSION.SDK_INT >= 21) {
        Constructor<?> packageParserConstructor = packageParserClazz.getDeclaredConstructor();
        return packageParserConstructor.newInstance();
    } else {
        Constructor<?> packageParserConstructor = packageParserClazz.getDeclaredConstructor(String.class);
        return packageParserConstructor.newInstance(path);
    }
}


}

About

Android增量更新

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages