目錄
Android NDK開發(fā)兩部曲(一)之初識篇(JNI通識與NDK配置)
Android NDK開發(fā)兩部曲(二)之應(yīng)用篇(增量更新也就那樣)
前言
之前發(fā)過一篇在
Eclipse上關(guān)于JNI和NDK的博客Android JNI與Android NDK掃盲, 里面只是籠統(tǒng)地介紹了一下, 文章本身并沒有深入學習的意思. 那么現(xiàn)在我們就開始在這篇文章的基礎(chǔ)上, 在Android Studio上面去詳細學習NDK開發(fā)吧.
本文參考鴻洋大神的Android 增量更新完全解析 是增量不是熱修復, 學習過程中. 發(fā)現(xiàn)Android Studio對NDK的支持越來越友好, 并不需要像以前那樣一個個地去新建文件.太多的原理就不重復了, 主要演示一下新版本下的操作變化
建議先看看鴻洋大神的博客再回來看這里
如果還沒有NDK環(huán)境, 那么請參考Android NDK開發(fā)三部曲(一)之配置篇(AS對NDK友好集成)
新建項目
這里我的項目就叫
BadPatchDemo

同時勾選Include C++ support
如果沒有安裝
cmake(一個跨平臺的C/C++項目編譯配置工具), 那么AS會提示安裝cmake, 按照提示完成安裝就可以了.

NDK開發(fā)
我們發(fā)現(xiàn)
app/src/main/多了一個cpp的文件夾, 這里就是存放我們源文件的目錄.
添加第三方代碼
前提是你按照鴻洋大神的博客里面那樣, 安裝了
bsdiff. 因為生成差分包的工作應(yīng)該是在服務(wù)器, 所以我們就只集成bspatch來對差分包跟舊安裝包合并.
大家可以下載代碼, 這里是篩選了某些源代碼的
鏈接: https://pan.baidu.com/s/1dEHNhx3 密碼: 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

最后點擊同步按鈕
暴露出接口
我們在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函數(shù)改名而來, 因為我們在命令行下面執(zhí)行命令的時候, 其實就是調(diào)用命令行工具的main函數(shù). 后面的參數(shù)會作為字符串的形式傳入到main函數(shù)中. 其中argc表示參數(shù)的個數(shù),argv存著參數(shù)的具體內(nèi)容
例如:
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 android.net.Uri;
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幫我們創(chuàng)建方法. 然后我們就直接調(diào)到對應(yīng)的C/C++文件里面了
現(xiàn)在我們需要引入的剛剛暴露方法的頭文件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);
}
編寫調(diào)用代碼
activity_main
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
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);
}
}
涉及到讀寫權(quán)限的, 不要忘了在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中面向?qū)ο蟮膶懛ǜ幕谻語言面向過程的寫法
#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
測試
現(xiàn)在我們得到的是舊的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

使用熱修復雖然不需要安裝, 但幾乎市面上的熱修復框架都是基于運行時修復的, 所以存在很大兼容性問題. 但是增量更新是通過合并差分包來更新的, 只要差分包是對的, 那么幾乎不會存在兼容性問題. 很多時候應(yīng)用只需要修改代碼, 但是資源文件是不變的. 這時候用增量更新, 就能減去資源文件的大小, 從而達到省流量更新.
說在最后
其實本博文主要目的是說明新版的Android Studio的NDK開發(fā)方式, 增量更新只是作為其中一個應(yīng)用分析. 其實這個過程也成為
移植. 大家也可以去找一些C庫, 圖片壓縮 聲音視頻變換之類的, 然后移植到Android上, 也是挺有趣的.