目录
前言
建议先看看鸿洋大神的博客再回来看这里
新建项目
android_studio_new_native_project.png这里我的项目就叫
BadPatchDemo
同时勾选Include C++ support
android_studio_install_cmake.png如果没有安装
cmake
(一个跨平台的C/C++项目编译配置工具), 那么AS会提示安装cmake
, 按照提示完成安装就可以了.
NDK开发
我们发现
app/src/main/
多了一个cpp
的文件夹, 这里就是存放我们源文件的目录.
添加第三方代码
前提是你按照鸿洋大神的博客里面那样, 安装了
bsdiff
. 因为生成差分包的工作应该是在服务器, 所以我们就只集成bspatch
来对差分包跟旧安装包合并.
大家可以下载代码, 这里是筛选了某些源代码的
链接: 密码: abnf
解压后我们直接把bspatch.c
和bzip
拷贝进去cpp
文件夹, 然后切换到project视图
编辑app/CMakeLists.txt
然后加入需要编译的代码
src/main/cpp/bzip2/blocksort.csrc/main/cpp/bzip2/bzip2.csrc/main/cpp/bzip2/bzip2recover.csrc/main/cpp/bzip2/bzlib.csrc/main/cpp/bzip2/compress.csrc/main/cpp/bzip2/crctable.csrc/main/cpp/bzip2/decompress.csrc/main/cpp/bzip2/huffman.csrc/main/cpp/bzip2/randtable.csrc/main/cpp/bspatch.c
android_studio_add_sourcefiles.png
最后点击同步按钮
暴露出接口
我们在cpp
目录邮件下新建C/C++ Header File
->bspatch.h
, 加入下面代码
#ifndef BSDPATCHDEMO_BSPATCH_H
#define BSDPATCHDEMO_BSPATCH_H
#endif
//BSDPATCHDEMO_BSPATCH_Hint
doPatch(int argc, char *argv[]);
其中doPatch是bspatch.c
的main函数改名而来, 因为我们在命令行下面执行命令的时候, 其实就是调用命令行工具的main函数. 后面的参数会作为字符串的形式传入到main函数中. 其中argc表示参数的个数
,argv存着参数的具体内容
例如:
mv test /root/
argc = 3
argv[0] = mv
argv[1] = test
argv[2] = /root/
编写native代码
我们新建BsdUtil
package com.example.bsdpatchdemo;
import android.content.Context;
import android.content.Intent;
import
import java.io.File;
/**
* Created by August on 2017/3/18.
*/
public class BsdUtil {
static {
System.loadLibrary("native-lib");
}
/**
* 原生打补丁方法
*
* @param oldApk
* @param patchFile
* @param newApk
*/
public static native void patch(String oldApk, String patchFile, String newApk);
/**
* 获取本身APK的路径
*
* @param context
* @return
*/
public static String getOriginalApk(Context context) {
return context.getApplicationInfo().sourceDir;
}
/**
* 安装指定apk
*
* @param context
* @param apk
*/
public static void install(Context context, String apk) {
Intent intent = new Intent();
intent.setDataAndType(Uri.fromFile(new File(apk)),
"application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
}
我们看到patch爆红, 这时候我们点一下提示, 然后让as帮我们创建方法. 然后我们就直接调到对应的C/C++文件里面了
现在我们需要引入的刚刚暴露方法的头文件bspatch.h
#include <jni.h>
#include <string>
#include "bspatch.h"
JNIEXPORT void JNICALL
Java_com_example_bsdpatchdemo_BsdUtil_patch(JNIEnv *env, jclass type, jstring oldApk_,
jstring patchFile_, jstring newApk_) {
const char *oldApk = env->GetStringUTFChars(oldApk_, 0);
const char *patchFile = env->GetStringUTFChars(patchFile_, 0);
const char *newApk = env->GetStringUTFChars(newApk_, 0);
// TODO
char *argv[] = {(char *) "bspatch", (char *) oldApk, (char *) newApk, (char *) patchFile};
doPatch(4, argv);
env->ReleaseStringUTFChars(oldApk_, oldApk);
env->ReleaseStringUTFChars(patchFile_, patchFile);
env->ReleaseStringUTFChars(newApk_, newApk);
}
编写调用代码
activity_main
<?xml version="1.0" encoding="utf-8"?>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:onClick="patch"
android:text="old app"
android:textSize="20sp" />
MainActivity
package com.example.bsdpatchdemo;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import java.io.File;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void patch(View v) {
String oldApk = BsdUtil.getOriginalApk(this);
String newApk = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "newApk.apk";
String patchFile = "/storage/sdcard0/p.patch";
BsdUtil.patch(oldApk, patchFile, newApk);
BsdUtil.install(this, newApk);
}
}
涉及到读写权限的, 不要忘了在AndroidManifest中加入
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
编译
Build
->Build APK
,编译的时候我这边爆了一个链接错误, 估计是C++跟C语言混编配置不当造成的.
解决方法
-
把
native-lib.cpp
改成native-lib.c
-
同时把
CMakeLists.txt
里面的native-lib.cpp
改成native-lib.c
-
然后把
native-lib.c
中面向对象的写法改回C语言面向过程的写法
#include <jni.h>
#include "bspatch.h"
JNIEXPORT void JNICALL
Java_com_example_bsdpatchdemo_BsdUtil_patch(JNIEnv *env, jclass type, jstring oldApk_,
jstring patchFile_, jstring newApk_) {
const char *oldApk = (*env)->GetStringUTFChars(env, oldApk_, 0);
const char *patchFile = (*env)->GetStringUTFChars(env, patchFile_, 0);
const char *newApk = (*env)->GetStringUTFChars(env, newApk_, 0);
char *argv[] = {(char *) "bspatch", (char *) oldApk, (char *) newApk, (char *) patchFile};
doPatch(4, argv);
(*env)->ReleaseStringUTFChars(env, oldApk_, oldApk);
(*env)->ReleaseStringUTFChars(env, patchFile_, patchFile);
(*env)->ReleaseStringUTFChars(env, newApk_, newApk);
}
再次编译APK
测试
-
现在我们得到的是旧的APK改名为
old.apk
, 给手机安装上 -
然后我们把activity_main中的
old app
改成new app
, 然后再build出新的apk, 改名为new.apk
. -
有了两个apk我们可以生成差分包
yubindeMacBook-Pro:Desktop August$ bsdiff old.apk new.apk p.patch
yubindeMacBook-Pro:Desktop August$ adb push p.patch /storage/sdcard0/p.patch
[100%] /storage/sdcard0/p.patch
android_bspatch.gif
使用热修复虽然不需要安装, 但几乎市面上的热修复框架都是基于运行时修复的, 所以存在很大兼容性问题. 但是增量更新是通过合并差分包来更新的, 只要差分包是对的, 那么几乎不会存在兼容性问题. 很多时候应用只需要修改代码, 但是资源文件是不变的. 这时候用增量更新, 就能减去资源文件的大小, 从而达到省流量更新.
说在最后
其实本博文主要目的是说明新版的Android Studio的NDK开发方式, 增量更新只是作为其中一个应用分析. 其实这个过程也成为
移植
. 大家也可以去找一些C库, 图片压缩 声音视频变换之类的, 然后移植到Android上, 也是挺有趣的.