1.简介
- 发布新版本时,用户已经安装的旧版本提示安装更新
- 版本更新分为选择更新和强制更新,区别就是弹窗提示是否可以取消,页面是否可以跳转问题
2.特点
1.Android7.0版本以下
这里不做解释,后面看代码。
2.Android7.0版本的FileProvider
主要就是在代码中和AndroidManifest.xml中使用FileProvider类
2.Android8.0及以上版本
版本更新后不能够自动打开安装页面,无法自动安装问题。主要是REQUEST_INSTALL_PACKAGES权限相关的问题
3.代码
- 主要类DownloadService.java
public class DownloadService extends Service {
public String DOWNLOAD_PATH = Environment.getExternalStorageDirectory() + "/download/AppUpdate.apk";
private String url;//下载链接
private long refernece;
private BroadcastReceiver receiver;
private IntentFilter filter;
public IBinder onBind(Intent arg0) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null) {
url = intent.getStringExtra("url");
if (url != null && !TextUtils.isEmpty(url)) {
download(url);
}
}
return super.onStartCommand(intent, flags, startId);
}
public void download(String url) {
if (new File(DOWNLOAD_PATH).exists()) {
new File(DOWNLOAD_PATH).delete();
}
receiver();
DownloadManager dManager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
Uri uri = Uri.parse(url);
DownloadManager.Request request = new DownloadManager.Request(uri);
//这里的"AppUpdate.apk"要对应DOWNLOAD_PATH的"AppUpdate.apk"
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "AppUpdate.apk");
request.setDescription("新版本下载中...");
request.setTitle("版本更新");
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setMimeType("application/vnd.android.package-archive");
// 设置为可被媒体扫描器找到
request.allowScanningByMediaScanner();
// 设置为可见和可管理
request.setVisibleInDownloadsUi(true);
try {
refernece = dManager.enqueue(request);
} catch (Exception e) {
}
}
public void receiver() {
filter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
receiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
long myDwonloadID = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
if (refernece == myDwonloadID) {
DownloadManager dManager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
setPermission(DOWNLOAD_PATH);
Intent install = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS, Uri.parse("package:" + getPackageName()));
install.setAction(Intent.ACTION_VIEW);
install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
install.addCategory(Intent.CATEGORY_DEFAULT);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {//大于Android版本7.0
//这里的"com.android.app.appupdate.provider"需要和AndroidManifest.xml的provider节点下的authorities属性保持一致
Uri contentUri = FileProvider.getUriForFile(context, "com.android.app.appupdate.provider", new File(DOWNLOAD_PATH));
install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);//这里不能是setFlags(),set会覆盖掉之前的flags
install.setDataAndType(contentUri, "application/vnd.android.package-archive");
context.startActivity(install);
} else {
installApk(context, refernece);
}
}
}
};
registerReceiver(receiver, filter);
}
private void installApk(Context context, long downloadApkId) {
DownloadManager dManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(downloadApkId);
Cursor c = dManager.query(query);
if (c != null) {
if (c.moveToFirst()) {
int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS);
if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)) {
String downloadFileUrl = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
startInstall(context, Uri.parse(downloadFileUrl));
}
}
c.close();
}
}
private boolean startInstall(Context context, Uri uri) {
if (!new File(uri.getPath()).exists()) {
System.out.println(" local file has been deleted! ");
return false;
}
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(uri, "application/vnd.android.package-archive");
context.startActivity(intent);
return true;
}
/**
* 提升读写权限
*
* @param filePath 文件路径
*/
public static void setPermission(String filePath) {
String command = "chmod " + "777" + " " + filePath;
Runtime runtime = Runtime.getRuntime();
try {
runtime.exec(command);
} catch (IOException e) {
e.printStackTrace();
}
}
public void onDestroy() {
unregisterReceiver(receiver);
}
}
- AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.android.app.appupdate">
<!-- 安装首先需要用户手动给予存储权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<!-- 安装应用的权限(8.0及以上会需要用户手动打开允许安装未知应用的权限,但在provider_paths的配置路径里,
可以配置不需要用户手动打开权限,也可跳转自动安装) -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".DownloadService" />
<!--注意FileProvider.getUriForFile()的第二个参数要和authorities一致-->
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.android.app.appupdate.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
</application>
</manifest>
- 在res目录下新建xml包,里面创建provider_paths.xml,内容如下:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android" >
<!--<external-path name="external" path="."/>-->
<!--<external-path path="." name="external_storage_root"/>-->
<!--<external-path name="beta_external_path" path="Download/"/>-->
<!--<external-path name="beta_external_files_path" path="Android/data/"/>-->
<!-- 这段代码很重要,这里的路径+android.permission.REQUEST_INSTALL_PACKAGES权限就可以解决,8.0及以上版本,
不能自动安装下载包的问题 -->
<root-path name="root" path="" />
<external-path name="external_storage_root" path="." />
<external-path name="external_storage_download" path="Download" />
<!--<files-path name="name" path="path" /> 对应getFilesDir()。
<cache-path name="name" path="path" /> 对应getCacheDir()。
<external-path name="name" path="path" /> 对应Environment.getExternalStorageDirectory()。
<external-files-path name="name" path="path" /> 对应getExternalFilesDir()。
<external-cache-path name="name" path="path" /> 对应getExternalCacheDir()。-->
<!--子节点 对应路径
root-path .
files-path Context.getFilesDir()
cache-path Context.getCacheDir()
external-path Environment.getExternalStorageDirectory()
external-files-path Context.getExternalFilesDir(null)
external-cache-path Context.getExternalCacheDir()-->
</paths >
activity做跳转
Intent intent = new Intent(mActivity, DownloadService.class);
intent.putExtra("url", "https://github.com/fanhenghao/AppUpdate/raw/master/AppUpdate_release1.0.1.apk");
mActivity.startService(intent);
4.参考
- 我个人GitHub的完整代码示例:AppUpdate
5.总结
- Android7.0需要FileProvider
- Android8.0需要一个允许安装未知应用的权限和修改provider_paths.xml的配置路径
- 可能会遇到下载安装时签名不一致的问题,可以在app下的build.gradle的android节点下添加
signingConfigs {
//这里是你自己的签名相关配置
release {
keyAlias 'paymentbox'
keyPassword 'android'
storeFile file('/Users/fanhenghao/Desktop/work/paymentbox/paymentbox.jks')
storePassword 'android'
}
debug {
keyAlias 'paymentbox'
keyPassword 'android'
storeFile file('/Users/fanhenghao/Desktop/work/paymentbox/paymentbox.jks')
storePassword 'android'
}
}
- 我遇到的问题:
1.强制或是选择更新都差不多,就是一个弹窗是否消失和页面是否跳转问题;另外,版本更新一定需要存储权限,如果app涉及到强制更新的话,我做的是不给存储权限直接退出app。
2.先前以为8.0系统的手机需要用户手动给予安装未知应用的权限,结果小米平台的审核不通过,所以做了不需要强制用户打开的安装未知应用的权限。
6.最后
开通了个公众号,扫码关注一下,可以获得超过1个G的免费PDF书籍学习资料,并且可以及时收到我分享的内容哦!