Android APK 更新之路(android studio模拟器运行不了)

网友投稿 301 2022-06-15


一、前言

提到 APK 更新,大家可能会想到友盟(umeng)更新,市场上已有数万款应用在使用友盟自动更新的服务。但友盟于 2016 年 10 月 15 日起停止了更新服务。那么我们需要自己处理 APK 更新的业务。

本篇主要讲解以下知识点:

使用 DownloadManager 更新

基于 RxJava 和 retrofit 扩展的 Android 线程安全 http 请求库下载 APK 更新

热更新(AndFix)

我们来啾啾第一个知识点。

DownloadManager 更新

Android 2.3(API level 9)开始 Android 用系统服务(Service)的方式提供了DownloadManager 来优化处理长时间的下载操作。DownloadManager 对后台下载,下载状态回调,断点续传,下载环境设置,下载文件的操作等都有很好的支持。

本篇基于 Android 4.0 ~7.0 (SDK 14~24) 开发,众所周知 Android 6.0 的 Runtime Permissions (运行时权限)。

下面具体来看看 DownloadManager 更新的具体流程。

AndroidManifest 清单文件配置权限

下载文件需要使用到网络权限,文件读写权限:

获取当前的版本号

getPackageManager().getPackageInfo(getPackageName(), 0).versionName;

后台需要提供查询最新版本号的接口,获取接口数据与当前版本号对比,判定是否需要更新。

获取 DownloadManager 实例

DownloadManager manager = (DownloadManager)

appContext.getSystemService(Context.DOWNLOAD_SERVICE);

下面来看看 DownloadManager 提供哪些接口:

public long enqueue(Request request) 执行下载,返回 downloadId,downloadId 可用于后面查询下载信息。若网络不满足条件、Sdcard 挂载中、超过最大并发数等异常则会等待下载,正常则直接下载。

int remove(long… ids) 删除下载,若下载中取消下载。会同时删除下载文件和记录。参数 ids 为 enqueue 返回的 downloadId 集合。

Cursor query(Query query) 查询下载信息。

getMaxBytesOverMobile(Context context) 返回移动网络下载的最大值

rename(Context context, long id, String displayName) 重命名已下载项的名字

getRecommendedMaxBytesOverMobile(Context context) 获取建议的移动网络下载的大小

其它:通过查看代码我们可以发现还有个 CursorTranslator 私有静态内部类。这个类主要对 Query 做了一层代理。将 DownloadProvider 和 DownloadManager之间做了个映射。

接着来看看 DownloadManager.Request 的请求参数。

组装 DownloadManager.Request 请求参数

//获取Request的实例对象

DownloadManager.Request request = new DownloadManager.Request(Uri.parse(appUrl));

显示信息:

//设置一些基本显示信息

request.setTitle(name); //通知栏标题

request.setDescription(description);//通知栏内容

request.setMimeType("application/vnd.android.package-archive");//文件的类型

网络类型:

//NETWORK_MOBILE移动网络

//NETWORK_WIFI wifi网络

//NETWORK_BLUETOOTH 蓝牙

req.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);

通知栏显示类型:

request.setNotificationVisibility(DownloadManager.Request

.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);

VISIBILITY_HIDDEN 下载UI不会显示,也不会显示在通知中,如果设置该值, 需要声明android.permission.DOWNLOAD_WITHOUT_NOTIFICATION

VISIBILITY_VISIBLE 当处于下载中状态时,可以在通知栏中显示;当下载完成后,通知栏中不显示

VISIBILITY_VISIBLE_NOTIFY_COMPLETED 当处于下载中状态和下载完成时状态,均在通知栏中显示

VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION 只在下载完成时显示在通知栏中。

文件的保存位置:

保存到外部环境的私有目录:file:///storage/emulated/0/Android/data/your-package/files/Download/app.apk

request.setDestinationInExternalFilesDir(context, Environment.DIRECTORY_DOWNLOADS, "app.apk");

保存到外部环境的共有目录: file:///storage/emulated/0/Download/app.apk

request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "app.apk");

自定义文件路径

setDestinationUri(Uri uri)

添加请求下载的网络链接的http头,比如User-Agent,gzip压缩等:

request.addRequestHeader(String header, String value)

漫游:

//true 允许

//false 不允许

request.setAllowedOverRoaming(false);

其他:

setAllowedOverMetered(boolean allow) //是否允许计量

setRequiresCharging(boolean requiresCharging)//是否在充电环境下

setVisibleInDownloadsUi(boolean isVisible)//是否显示下载界面

...

下面是本文创建Request的示例代码:

request.setTitle(name);

request.setDescription(description);

//在通知栏显示下载进度

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {

request.allowScanningByMediaScanner();

request.setNotificationVisibility(DownloadManager.Request

.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);

}

request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);

request.setDestinationInExternalPublicDir(SAVE_APP_LOCATION, SAVE_APP_NAME);

加入下载队列

DownloadManager manager = (DownloadManager) appContext.getSystemService(Context.DOWNLOAD_SERVICE);

manager.enqueue(request);

下载信息查询

DownloadManager 下载工具并没有提供相应的回调接口用于返回实时的下载进度状态。可以通过 DownloadManager.query 方法进行查询,该方法返回一个 Cursor 对象,具体看以下代码:

private void queryDownloadManager(long id) {

DownloadManager mDownloadManager = (DownloadManager)

this.getSystemService(Context.DOWNLOAD_SERVICE);

DownloadManager.Query query = new DownloadManager.Query().setFilterById(id);

//可以对query设置一些过滤条件

//setFilterById(long… ids)根据下载id进行过滤

//setFilterByStatus(int flags)根据下载状态进行过滤

Cursor cursor = mDownloadManager.query(query);

if (cursor != null) {

while (cursor.moveToNext()) {

String bytesDownload = cursor.getString(cursor.getColumnIndex(DownloadManager

.COLUMN_BYTES_DOWNLOADED_SO_FAR));

String description = cursor.getString(cursor.getColumnIndex(DownloadManager

.COLUMN_DESCRIPTION));

String cid = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_ID));

String localUri = cursor.getString(cursor.getColumnIndex(DownloadManager

.COLUMN_LOCAL_URI));

String mimeType = cursor.getString(cursor.getColumnIndex(DownloadManager

.COLUMN_MEDIA_TYPE));

String title = cursor.getString(cursor.getColumnIndex(DownloadManager

.COLUMN_TITLE));

String status = cursor.getString(cursor.getColumnIndex(DownloadManager

.COLUMN_STATUS));

String totalSize = cursor.getString(cursor.getColumnIndex(DownloadManager

.COLUMN_TOTAL_SIZE_BYTES));

Log.i("MainActivity", "bytesDownload:" + bytesDownload);

Log.i("MainActivity", "description:" + description);

Log.i("MainActivity", "cid:" + cid);

Log.i("MainActivity", "localUri:" + localUri);

Log.i("MainActivity", "mimeType:" + mimeType);

Log.i("MainActivity", "title:" + title);

Log.i("MainActivity", "status:" + status);

Log.i("MainActivity", "totalSize:" + totalSize);

}

}

}

本篇示例的打印结果如下:

man

注册广播监听通知栏点击事件和下载完成事件

当用户点击通知栏中的下载列表时,系统会发出 ACTION_NOTIFICATION_CLICKED 事件广播;下载完成时会发出 ACTION_DOWNLOAD_COMPLETE 事件广播,那么我们就可以实现一个广播接收器处理点击和完成时的状态。请看下面代码:

public void onReceive(Context context, Intent intent) {

if (intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) {

installApk(context);

} else if (intent.getAction().equals(DownloadManager.ACTION_NOTIFICATION_CLICKED)) {

//Toast.makeText(context, "Clicked", Toast.LENGTH_SHORT).show();

}

}

如文本下载 apk 文件,下载完成时就自动安装,使用意图进行 apk 安装:

// 安装Apk

private void installApk(Context context) {

try {

Intent i = new Intent(Intent.ACTION_VIEW);

String filePath = DownloadManagerUtils.APP_FILE_NAME;

i.setDataAndType(Uri.parse("file://" + filePath), "application/vnd.android" +

".package-archive");

i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

context.startActivity(i);

} catch (Exception e) {

Log.e(TAG, "安装失败");

e.printStackTrace();

}

}

DownloadManager 更新就讲到这里了,源码在文章的后面会附上。

基于 RxJava 和 retrofit 扩展的 Android 线程安全 http 请求库下载 APK 更新

针对 DownloadManager 更新,我们还可以通过 http 请求库下载 apk 文件进行更新。

提到 http 请求库,就不得不提到 Novate 库,功能非常强大,使用便利,看看它有哪些功能:

加入基础API,减少Api冗余

支持离线缓存

支持多种方式访问网络(get,put,post ,delete)

支持Json字符串,表单提交

支持文件下载和上传

支持请求头统一加入

支持对返回结果的统一处理

支持自定义的扩展API

支持统一请求访问网络的流程控制

我下载了源码,并修改了进度条的接口。下载文件相信大家都比较熟悉了,我这里就不再细讲了。如果有什么疑问请链接上面地址查看。

新建通知

以下给出本篇用到的消息代码:

private NotificationCompat.Builder buildNotification() {

final Resources res = mContext.getResources();

// This image is used as the notification's large icon (thumbnail).

// TODO: Remove this if your notification has no relevant thumbnail.

final Bitmap picture = BitmapFactory.decodeResource(res, R.mipmap.ic_launcher);

return new NotificationCompat.Builder(mContext).

setContentTitle("更新包下载中...")

.setTicker("准备下载...")

.setProgress(100, 0, false)

.setContentText(String.format(mContext.getResources()

.getString(R.string.apk_progress), 0) + "%")

.setLargeIcon(picture)

.setPriority(NotificationCompat.PRIORITY_DEFAULT)

.setWhen(System.currentTimeMillis())

.setSmallIcon(R.mipmap.ic_launcher)

.setAutoCancel(false);

}

//更新消息进度

public void showProgressNotification(int progress) {

if (mBuilder == null) {

mBuilder = buildNotification();

}

Notification notification = mBuilder.setProgress(100, progress, false)

.setContentText(String.format(mContext.getResources().getString(R.string

.apk_progress), progress) + "%")

.build();

notify(mContext, notification);

}

apk下载

private void downloadApk() {

RetrofitClient.getInstance(this).createBaseApi()

.download(DOWN_URL, new CallBack() {

@Override

public void onError(Throwable e) {

Log.e("HttpActivity", "onError--------2222" + e.getMessage());

mHttpNotification.removeProgressNotification();

}

@Override

public void onStart() {

super.onStart();

mHttpNotification.showProgressNotification(0);

}

@Override

public void onSucess(String path, String name, long fileSize) {

mHttpNotification.removeProgressNotification();

installApk(HttpActivity.this);

}

@Override

public void onProgress(int progress) {

super.onProgress(progress);

mCircleProgressView.setProgress(progress);

mHttpNotification.showProgressNotification(progress);

}

});

}

如果你还有疑问,在文章结尾处下载源码进行查看。

更新全过程效果图:

热更新(AndFix)

热更新技术近段时间非常火爆,各个大公司都相继开发自己的热更新框架。由于公司主要项目基于电商商城,所以我选择了阿里巴巴的 AndFix 热更新的实现,使用起来也比较简单。至少在我的测试下修改一些小的 BUG 是没有问题的。

我的开发工具是 Android Studio ,第一步导包:

app 的 dependencies 的节点下:

compile 'com.alipay.euler:andfix:0.3.1@aar'

第二步配置 MyApplication 类:

@TargetApi(Build.VERSION_CODES.KITKAT)

@Override

public void onCreate() {

super.onCreate();

String version = "";

try {

version = getPackageManager().getPackageInfo(getPackageName(), 0).versionName;

} catch (PackageManager.NameNotFoundException e) {

e.printStackTrace();

}

mPatchManager = new PatchManager(getApplicationContext());

mPatchManager.init(version);

mPatchManager.loadPatch();

try {

String patchFileString = "/sdcard" + APATCH_PATH;

mPatchManager.addPatch(patchFileString);

} catch (IOException e) {

e.printStackTrace();

}

}

首先获取到版本号,系统会判断版本号,只有相同的版本号的时候会执行热更新。其中 String patchFileString = "/sdcard" + APATCH_PATH; 是我测试的补丁存放路径。你需要替换成你自己的存放路径。

注意:文件的权限。

然后在 MainActivity 中写一个打印吐司的方法:

private void showToast() {

Toast.makeText(this, "你好啊", Toast.LENGTH_LONG).show();

}

然后打包,重命名为 old.apk

接着修改吐司的内容:

private void showToast() {

Toast.makeText(this, "你好啊,世界", Toast.LENGTH_LONG).show();

}

重新打包,命名为 new.apk

下载apkpatch工具

下面是我的目录结构:

用红线框框住的是签名文件,补丁包,旧包。

打开 cmd ->cd 到 apkpatch 的目录,如我 F:\AndroidTools\apkpatch 目录下,下图我已用红框圈住:

然后输入:

apkpatch.bat -f new.apk -t old.apk -o output -k demo.jks -p 123456 -a boby -e 123456

其中:

-f 是新apk的名字

-t 是旧apk的名字

-o 是输出补丁的文件夹位置

-k 是 keystore(jks)文件的名称

-p 是keystore文件的密码

-a 是项目的别名

-e 别名的密码

回车,不出现错误,补丁打包成功。

打开 output 目录,则可以看到 out.apatch 文件。

补丁文件上传到后台,然后通过接口下载到 /sdcard/out.apatch 目录下。

注意 /sdcard/out.apatch 路径,跟 MyApplication 中的一致。

看看效果:

安装 old.apk 包:

安装补丁,接着运行:

来自:http://jianshu.com/p/61336c6f750a


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:在线看Android系统源码,相见恨晚的几种方案(android源码在线查看)
下一篇:Android 反编译初探 应用是如何被注入广告的(android13)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~