应用程序下载,并在通知栏提醒下载完成。过程大概分成三步:
- 创建一个service
- 在service启动的时候创建一个广播接收者
- 当BroadcastReceiver接收到下载完成的广播时,开始执行安装
此过程兼容了Android 7.0权限限制安装功能,Android 7.0 因为设置了“私有目录被限制访问”,“StricMode API”等安全机制,因此安装时会产生FileUriExposedException错误,此错误用FileProvider解决。
-
在AndroidMenifest.xml文件中注册provider,向外提供数据的组件。其中exported="false"必须设置成false,否则会报安全异常;grantUriPermissions="true"表示授予Uri临时访问权限;authorities组件标识,一般以包名开头。
<provider android:authorities="com.nxyuntui.testproject.fileprovider" android:name="android.support.v4.content.FileProvider" android:grantUriPermissions="true" android:exported="false"> <!--元数据--> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider>
-
指定共享的目录。在res文件下创建一个xml目录,再在xml目录下创建一个名为file_paths的资源文件。其中,file-path 表示Context.getFilesDir(),external-path表示Environment.getExternalStorageDirectory(),cache-path表示 getCacheDir()。
<?xml version="1.0" encoding="utf-8"?> <resources> <paths> <external-path path="" name="download"/> //path="" 表示共享根目录以及根目录下所有文件 </paths> </resources>
-
使用FileProvider ,主要代码片段如下
Uri apkUri = FileProvider.getUriForFile(context,"com.nxyuntui.testproject.fileprovider",file); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-
RxPermission 第三方动态库申请权限
首先gradle中添加动态库,接着申请权限。implementation 'com.tbruyelle.rxpermissions:rxpermissions:0.7.0@aar' implementation 'io.reactivex:rxjava:1.1.6' //申请sd卡权限,targetSdkVersion>=23,需要动态申请 RxPermissions.getInstance(this) //申请权限 .request(Manifest.permission.WRITE_EXTERNAL_STORAGE) .subscribe(new Action1<Boolean>() { @Override public void call(Boolean aBoolean) { if (aBoolean){ //请求成功 startDownload(downloadUrl); }else{ //请求失败回收当前服务 stopSelf(); } } });
整体示例如下 :
AndroidMenifest.xml
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/>
<uses-permission android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<provider
android:authorities="com.nxyuntui.testproject.fileprovider"
android:name="android.support.v4.content.FileProvider"
android:grantUriPermissions="true"
android:exported="false">
<!--元数据-->
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<service android:name=".Service.DownLoadService"/>
DownLoadService.java
public class DownLoadService extends Service {
private BroadcastReceiver receiver;
private DownloadManager dm;
private long enqueue;
private String downloadUrl = "http://downapp.baidu.com";
@Override
public void onCreate() {
super.onCreate();
Log.i("DownLoadService","--onCreate");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("DownLoadService","--onStartCommand");
receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
install(context);
stopSelf(); //销毁当前service
}
};
registerReceiver(receiver,new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
//申请sd卡权限,targetSdkVersion>=23,需要动态申请
RxPermissions.getInstance(this)
//申请权限
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.subscribe(new Action1<Boolean>() {
@Override
public void call(Boolean aBoolean) {
if (aBoolean){
//请求成功
startDownload(downloadUrl);
}else{
//请求失败回收当前服务
stopSelf();
}
}
});
return Service.START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i("DownLoadService","--onDestroy");
unregisterReceiver(receiver);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
public static void install(Context context){
File file = new File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
"myApp.apk");
Intent intent = new Intent(Intent.ACTION_VIEW);
//没有在Activity环境下启动activity,设置如下标签
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (Build.VERSION.SDK_INT >= 24){
Uri apkUri = FileProvider.getUriForFile(context,
"com.nxyuntui.testproject.fileprovider",file);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(apkUri,"application/vnd.android.package-archive");
}else{
intent.setDataAndType(Uri.fromFile(file),"application/vnd.android.package-archive");
}
context.startActivity(intent);
}
private void startDownload(String downloadUrl){
//获得系统下载器
dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
//设置下载地址
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(downloadUrl));
//设置下载文件的类型
request.setMimeType("application/vnd.android.package-archive");
//设置下载存放的文件夹和文件名称
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS,"myApp.apk");
//设置下载时或下载完成时,通知栏是否显示
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setTitle("下载新版本");
//执行下载,并返回任务唯一id
enqueue = dm.enqueue(request);
}
}
MainActivity.java
oncreat()里面调用此方法即可。
public void download(View view){
Log.i("MainActivity","--下载服务");
Intent intent = new Intent(this,DownLoadService.class);
startService(intent);
}
activity_main.xml
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="开始下载服务"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:onClick="start"/>