前言:
检测是否需要更新,这个很简单,这里就不叙述了。本文主要介绍如何下载和安装更新。下载新的安装包,大致有三种方式:自己写一个下载器、使用安卓自带的下载管理器、使用浏览器下载。其中,利用安卓自带下载管理器较为方便而不失优雅,本人的项目中使用较多。
下载器
自己写下载器
下载器的写法可以参照《第一行代码》中“服务的最佳实践”。郭神采用了前台服务,但是国内安卓手机使用前台服务容易出问题,需要稍微修改下,可以参照网上的方法。此外,也可以将其改为后台服务和普通通知。下载完成时,触发安装过程即可。
前台的修改可参考:Android startForeground 却无notification的黑科技原理分析 以及Android7.1的修复
使用下载管理器
调用DownloadManager和DownloadManager.Request,后者用来设置具体的参数,最后通过调用
downloadManager.enqueue(request)将其加入下载队列。具体参照一下代码(感谢:某位前辈)。
import android.app.DownloadManager;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;
import java.io.File;
public class DownloadAppUtils {
private static final String TAG = DownloadAppUtils.class.getSimpleName();
public static long downloadUpdateApkId = -1;//下载更新Apk 下载任务对应的Id
public static String downloadUpdateApkFilePath;//下载更新Apk 文件路径
/**
* 下载更新apk包
* 权限:1,<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
* @param context
* @param url
*/
public static void downloadForAutoInstall(Context context, String url, String fileName, String title) {
if (TextUtils.isEmpty(url)) {
return;
}
try {
Uri uri = Uri.parse(url);
DownloadManager downloadManager = (DownloadManager) context
.getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Request request = new DownloadManager.Request(uri);
//在通知栏中显示
request.setVisibleInDownloadsUi(true);
request.setTitle(title);
String filePath = null;
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { //外部存储卡
filePath = Environment.getExternalStorageDirectory().getAbsolutePath();
} else {
Log.i(TAG,"没有SD卡");
return;
}
downloadUpdateApkFilePath = filePath + File.separator + fileName;
// 若存在,则删除
deleteFile(downloadUpdateApkFilePath);
Uri fileUri = Uri.fromFile(new File(downloadUpdateApkFilePath));
request.setDestinationUri(fileUri);
downloadUpdateApkId = downloadManager.enqueue(request);
} catch (Exception e) {
e.printStackTrace();
}finally {
// registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
}
}
private static boolean deleteFile(String fileStr) {
File file = new File(fileStr);
return file.delete();
}
}
使用浏览器下载
创建一个URI的Intent,用来开启新的活动,由于该活动需要离开本程序,所以需标记Intent.FLAG_ACTIVITY_NEW_TASK。
/**
* 通过浏览器下载APK包
* @param context
* @param url
*/
public static void downloadForWebView(Context context, String url) {
Uri uri = Uri.parse(url);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
自动安装APK文件
这部分比较麻烦,6.0和7.0都有过些变更,所以网上的方法未必适用。本人在这里走了不少弯路,这里先贴出7.0以上版本的写法。(注意,这些安装过程需要在下载完后自动安装,因此可以设置一个广播监听或者下载监听)
File file = new File(DownloadAppUtils.downloadUpdateApkFilePath);
Intent install = new Intent();
install.setAction(Intent.ACTION_VIEW);
install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Uri uri = FileProvider.getUriForFile(MyApplication.getContext(), "com.fanjin.rctrandom.provider", file);
install.setDataAndType(uri, "application/vnd.android.package-archive");
context.startActivity(install);
此外还需要在AndroidManifest.xml中添加如下代码。
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.fanjin.rctrandom.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
以及新建一个xml/provider_paths.xml。
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<root-path name="root_path" path="." />
<external-path name="external_files" path="."/>
</paths>
以上部分是试错踩坑很久之后才成功的,出现过很多错误,比如
FileUriExposedException
和
ActivityNotFoundException: No Activity found to handle Intent
和
IllegalArgumentException: Failed to find configured root that contains
以及“解析软件包时出现问题”等。
其中解析包出现问题,调试很久之后发现是权限问题(不具备执行权限),试过很多方法,然而最终由一行代码解决:
install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
其中的linux命令法不起作用,大概也是命令权限不够吧。但是也算是学到了如何在安卓代码里执行linux命令,在此记一笔:
String[] command = {"chmod", "777", "/mnt/sdcard/rctrandom/rctrandom.apk" };
ProcessBuilder builder = new ProcessBuilder(command);
try {
builder.start();
} catch (IOException e) {
e.printStackTrace();
}
可参考:安卓程序中调用 linux 命令
以上只是针对7.0以上版本的更新方式,实际操作时需要考虑各种版本的兼容问题。
鸣谢
需要感谢许多博客资料:
android app 更新下载安装 适配android 7.0(非常棒的总结!)
Android:使用 DownloadManager 进行版本更新,出现 No Activity found to handle Intent 及解决办法
FileProvider相关 Failed to find configured root that contains
android 7.0 因为file://引起的FileUriExposedException异常
android.os.FileUriExposedException: file:///sdcard/ beyond app through
获取未安装的APK图标、版本号、包名、名称、是否安装、安装、打开
Android 7.0解析包时出现问题 的解决方案(应用内更新)
android 程序打开第三方程序(以后别的地方说不定有用)