2023年12月7日发(作者:)

【AndroidOTA】应用的更新升级

App的更新方式主要有两种:

完全更新(Full updates)

增量更新(Incremental updates,也叫差分包升级)

应用更新前更新完成后

应用比较简单,关键是更新的流程,看背景色既可知更新是否成功。

App实现

文末附Demo完整源码,这里只大概介绍主要的步骤。

主要的界面就是一列表,列表有2个选项

activity_

xmlns:android="/apk/res/android"

xmlns:tools="/tools"

android:id="@+id/activity_main"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:paddingBottom="@dimen/activity_vertical_margin"

android:paddingLeft="@dimen/activity_horizontal_margin"

android:paddingRight="@dimen/activity_horizontal_margin"

android:paddingTop="@dimen/activity_vertical_margin"

tools:context="tivity">

android:id="@+id/list"

android:layout_width="match_parent"

android:layout_height="wrap_content">

列表项初始化,点击列表选项做对应的跳转

protected void onCreate(Bundle savedInstanceState) {

...

mListView = (ListView)findViewById();

mList = new ArrayList();

("版本信息");

("版本更新");

ArrayAdapter adapter = new ArrayAdapter(this, _list_item_1, mList);

pter(adapter);

temClickListener(new ClickListener() {

@Override

public void onItemClick(AdapterView parent, final View view, int position, long id) {

Intent intent = null;

switch (position) {

case 0:

intent = oIntent(getApplicationContext());

break;

case 1:

intent = ateIntent(getApplicationContext());

break;

default:

}

if (intent != null) {

startActivity(intent);

}

}

});

}

UpdateActivity 为更新升级界面,进入该界面时会自动向服务器进行更新信息的请求,并将请求的结果显示。

新建 AsyncTask 子类 UpdateCheckTask,用来做后台网络请求。

protected String voids) {

HttpURLConnection uRLConnection = null;

InputStream is = null;

BufferedReader buffer = null;

String result = null;

String urlStr = _REQUEST_URL + "?" + _VERSION_NAME + "=" + sionName(xt);

try {

URL url = new URL(urlStr);

uRLConnection = (HttpURLConnection) nnection();

uestMethod("GET");

is = utStream();

buffer = new BufferedReader(new InputStreamReader(is));

StringBuilder strBuilder = new StringBuilder();

String line;

while ((line = ne()) != null) {

(line);

}

result = ng();

} catch (Exception e) {

Log.e(TAG, "http post error");

} finally {

...

}

return result;

}

将获取到的服务器返回数据做json解析,并返回给UpdateActivity

protected void onPostExecute(String result) {

Log.i(TAG, "onPostExecute()");

UpdateInfo info = parseJson(result);

if (ner != null) {

ess(info);

}

}

parseJson

函数为UpdateCheckTask中实现的解析方法,具体见源码。

UpdateCheckTask在

onCreate

中调用更新请求

new UpdateCheckTask(, this).execute();

检测到更新后,界面出现下载按钮,点击按钮,下载指定url中的更新文件,显示下载进度条

新建 DownloadService 用来处理下载流程,DownloadService 继承自 IntentService 类

DownloadService 通过指定地址将文件下载到本地

@Override

protected void onHandleIntent(Intent intent) {

String urlStr = _SERVER_IP + ingExtra(_DOWNLOAD_URL);

String md5 = ingExtra(_MD5);

boolean isDiff = leanExtra(_DIFF_UPDATE, false);

InputStream in = null;

FileOutputStream out = null;

try {

URL url = new URL(urlStr);

HttpURLConnection urlConnection = (HttpURLConnection) nnection();

...

}

下载过程中将进度以消息的方式通知UpdateActivity以更新进度条

...

int oldProgress = 0;

Intent sendIntent = new Intent(E_RECEIVER);

while ((byteread = (buffer)) != -1) {

bytesum += byteread;

(buffer, 0, byteread);

int progress = (int) (bytesum * 100L / bytetotal);

// 如果进度与之前进度相等,则不更新,如果更新太频繁,否则会造成界面卡顿

if (progress != oldProgress) {

ra(_DOWNLOAD_PROGRESS, progress);

getApplicationContext().sendBroadcast(sendIntent);

}

oldProgress = progress;

}

...

UpdateActivity 监听进度消息

mReceiver = new ProgressReceiver();

IntentFilter intentFilter = new IntentFilter();

ion(SERVICE_RECEIVER);

registerReceiver(mReceiver, intentFilter);

UpdateActivity 获取到消息时更新进度条

public class ProgressReceiver extends BroadcastReceiver {

@Override

public void onReceive(Context context, Intent intent) {

int progress = Extra(_DOWNLOAD_PROGRESS, 0);

Log.i(TAG, "" + progress);

gress(progress);

if (ibility() != E) {

ibility(E);

ibility();

}

}

}

下载完成后安装apk

File apkFile = downloadFile;

installAPk(apkFile);

downloadFile

为下载完成保存到本地的apk文件,

installAPk

的具体实现

private void installAPk(File apkFile) {

Intent intent = new Intent(_VIEW);

//如果没有设置SDCard写权限,或者没有sdcard,apk文件保存在内存中,需要授予权限才能安装

try {

String[] command = {"chmod", "777", ng()};

ProcessBuilder builder = new ProcessBuilder(command);

();

} catch (IOException ignored) {

}

aAndType(le(apkFile), "application/e-archive");

gs(_ACTIVITY_NEW_TASK);

startActivity(intent);

}

Android本身自带接口,当打开.apk格式的文件时跳转到安装界面,主要实现为这行代码

aAndType(le(apkFile), "application/e-archive");

安装界面

若安装文件有问题,系统会提示解析错误

完全更新

为了区分完全更新和增量更新,服务器返回的json数据增加一个diffUpdate字段,用来区分文件是.apk文件还是差分包文件

修改服务器的info数据

var info = {

'url': '/ota_file/',

'updateMessage': 'Fix bugs.',

'versionName': 'v2',

'md5': '',

'diffUpdate': true

};

完全更新时,diffUpdate 值设为 true

url 设置为 /ota_file/

应用做些修改,修改主界面的背景色,增加一行代码

android:background="@color/colorAccent"

对应的项目版本信息修改

然后运行Android Studio Build Apk,生成的Apk文件在项目目录下

将apk文件拷贝到 ota服务器项目的 ota_file 文件夹下,并修改文件名为

然后将修改的代码回退,运行旧的版本,点击版本更新,下载更新文件完成后进行安装

安装完成后,重新打开应用,便可以看到主界面的背景颜色改变了。

增量更新

增量更新的原理,就是将手机上已安装apk与服务器端最新apk进行二进制对比,得到差分包,用户更新程序时,只需要下载差分包,并在本地使

用差分包与已安装apk,合成新版apk。

处理差分包的代码实现

项目中引入第三方动态库

引入对应的包 tils

DownloadService 下载完成时,根据服务器提供的 diffUpdate 字段来判断是否为差分包文件,若是,则将差分包文件与当前的apk合成新的

apk,并安装新的apk

安装前合成代码的实现

if (isDiff) {

// 增量式升级,先将patch合成新apk

String oldApkPath = eApkPath(getApplicationContext());

String newApkName = "";

String newApkPath = h() + "/" + newApkName;

String patchPath = h();

Log.i(TAG, "MD5:");

Log.i(TAG, "old apk md5: " + 5ByFile(new File(oldApkPath)));

Log.i(TAG, "new apk md5: " + 5ByFile(new File(newApkPath)));

Log.i(TAG, "patch md5: " + 5ByFile(new File(patchPath)));

Log.i(TAG, "");

int patchResult = (oldApkPath, newApkPath, patchPath);

if (patchResult == 0) {

apkFile = new File(newApkPath);

}

}

installAPk(apkFile);

应用当前的apk会保存在系统特定的位置,通过

getBaseApkPath

方法获取路径,方法的具体实现

public static String getBaseApkPath(Context context) {

String pkName = kageName();

try {

ApplicationInfo appInfo = kageManager().getApplicationInfo(pkName, 0);

String path = Dir;

return path;

} catch (tFoundException e) {

}

return null;

}

方法将旧apk文件与差分包文件合成新apk,并保存到newApkPath路径下。

然后和完全更新的方式一样,通过 installAPk(apkFile); 安装更新。

生成差分包文件

安装完成后,试着在命令行敲 bsdiff 命令

bsdiff

bsdiff: usage: bsdiff oldfile newfile patchfile

可看到该命令接收3个参数,依次为

旧的文件

新的文件

生成的差分包文件

和之前Build Apk方式一样,先将未修改前的应用编一个apk文件,命名为

然后修改应用的背景色和版本信息,重新编一个apk文件,命名为

将新旧两个apk文件拷贝到同一目录下,命令行cd到当前目录下,运行命令

完成后可看到在当前目录下生成了新文件 ,将拷贝到服务器的ota_file文件夹下

服务器返回的信息做对应修改

var info = {

'url': '/ota_file/',

'updateMessage': 'Fix bugs.',

'versionName': 'v2',

'md5': '',

'diffUpdate': true

};

diffUpdate 改为 true,url 改为差分包文件

运行

之前的运行都是通过Android Studio直接连手机真机跑起来的,但是调试差分包升级时需要注意,差分包必须保证本地应用的apk文件与生成差

分包时的文件完全一致,否则合成新的apk会失败。

而每次通过Android Studio连接手机编起来的应用所生成的apk文件都是不一样的,虽然代码一模一样。

为了测试差分包,手机必须通过旧的apk来安装并运行应用

用 adb 命令安装

adb install -r

结果打印

[100%] /data/local/tmp/

pkg: /data/local/tmp/

Success

安装完成后运行,和之前一样操作的流程。

end。

其他

实际生产中的升级过程还会涉及到很多逻辑处理细节,比如下载完成之后的MD5校验,下载前判断本地是否存在更新文件等,但基本的更新方式

和流程如上所示。

Demo附完整源码,源码里还附了一些工具类处理一些细节。

运行时只需按照文章之前所示,修改服务器信息即可进行相应的升级方式。