前言:一個人至少擁有一個夢想,有一個理由去堅強。心若沒有棲息的地方,到哪里都是在流浪 ---三毛
現在市面上的app基本使用了更新功能,而且在android7.0以上的手機沒有做處理,故記錄之
一般寫法都差不多,比如在啟動app的時候,通過api接口獲得服務器最新的版本號,然后和本地的版本號比較,來判斷是否需要彈出提示框下載,當然也可以通過推送的自定義消息來實現。
這里主要討論的是應用程序下載,并在通知欄提醒下載完成。 實現過程大致分為三步:
1.創建一個service
2.在service啟動的時候創建一個廣播接受者,用于接受下載完成的廣播
3.當BroadcastReceiver接受到下載完成的廣播時,開始執行安裝。
主要通過系統提供的DownloadManager進行下載,DownloadManager下載完成會發送廣播,具體使用看下面完整的代碼。如果詳細了解可參考[android系統下載管理DownloadManager功能介紹及使用實例] (http://www.trinea.cn/android/android-downloadmanager)
public class DownLoadService extends Service {
/**廣播接受者*/
private BroadcastReceiver receiver;
/**系統下載管理器*/
private DownloadManager dm;
/**系統下載器分配的唯一下載任務id,可以通過這個id查詢或者處理下載任務*/
private long enqueue;
/**TODO下載地址 需要自己修改,這里隨便找了一個*/
private String downloadUrl="http://dakaapp.troila.com/download/daka.apk?v=3.0";
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
install(context);
//銷毀當前的Service
stopSelf();
}
};
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 granted) {
if(granted){
//請求成功
startDownload(downloadUrl);
}else{
// 請求失敗回收當前服務
stopSelf();
}
}
});
return Service.START_STICKY;
}
/**
* 通過隱式意圖調用系統安裝程序安裝APK
*/
public static void install(Context context) {
Intent intent = new Intent(Intent.ACTION_VIEW);
// 由于沒有在Activity環境下啟動Activity,設置下面的標簽
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(Uri.fromFile(
new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "myApp.apk")),
"application/vnd.android.package-archive");
context.startActivity(intent);
}
@Override
public void onDestroy() {
//服務銷毀的時候 反注冊廣播
unregisterReceiver(receiver);
super.onDestroy();
}
private void startDownload(String downUrl) {
//獲得系統下載器
dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
//設置下載地址
DownloadManager.Request request = new DownloadManager
.Request(Uri.parse(downUrl));
//設置下載文件的類型
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);
}
}
上面代碼使用了RxPermissions第三方庫動態申請權限,需要在app/build.gradle文件中進行配置
dependencies {
//...
compile 'com.tbruyelle.rxpermissions:rxpermissions:0.7.0@aar'
compile 'io.reactivex:rxjava:1.1.6' //需要引入RxJava
}
記得要配置服務
<application...>
...
<service android:name=".DownLoadService"/>
</application>
Caused by: android.os.FileUriExposedException:
file:///storage/emulated/0/Download/myApp.apk exposed beyond app through Intent.getData()
這是由于Android7.0執行了“StrictMode API 政策禁”的原因,不過小伙伴們不用擔心,可以用FileProvider來解決這一問題,
現在我們就來一步一步的解決這個問題。
Android 7.0以上版本更新錯誤原因
比如:Android6.0引入的動態權限控制(Runtime Permissions),Android7.0又引入“私有目錄被限制訪問”,“StrictMode API 政策”。
這些更改在為用戶帶來更加安全的操作系統的同時也為開發者帶來了一些新的任務。如何讓你的APP能夠適應這些改變而不是crash,是擺在每一位Android開發者身上的責任。
“私有目錄被限制訪問“ 是指在Android7.0中為了提高私有文件的安全性,面向 Android N 或更高版本的應用私有目錄將被限制訪問。這點類似iOS的沙盒機制。
” StrictMode API 政策” 是指禁止向你的應用外公開 file:// URI。 如果一項包含文件 file:// URI類型 的 Intent 離開你的應用,應用失敗,并出現 FileUriExposedException 異常。
上面用到的代碼中的Uri.fromFile 其實就是生成一個file://URL。
intent.setDataAndType(Uri.fromFile(
new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOWNLOADS),
"myApp.apk")),
"application/vnd.android.package-archive");
一旦我們通過這種辦法打開其它程序(這里打開系統包安裝器)就認為file:// URI類型的 Intent 離開你的應用。這樣程序就會發生異常。
接下來就用 FileProvider 來解決這一問題。
使用 FileProvider:
使用FileProvider的大致步驟如下:
第一步:
在AndroidManifest.xml清單文件中注冊provider,因為provider也是Android四大組件之一,可以簡單把它理解為向外提供數據的組件,這種組件在實際開發中用的頻率并不高,四大組件都可以在清單文件中進行配置。
<application
...>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.tyj.tourapp.fileprovider"
android:grantUriPermissions="true"
android:exported="false">
<!--元數據-->
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
注意:
?exported:要求必須為false,為true則會報安全異常。
?grantUriPermissions:true,表示授予 URI 臨時訪問權
限。
?authorities 組件標識,按照江湖規矩,都以包名開頭,避免和其它應用發生沖突。
第二步:指定共享的目錄
上面配置文件中 android:resource="@xml/file_paths" 指的是當前組件引用 res/xml/file_paths.xml 這個文件。
我們需要在資源(res)目錄下創建一個xml目錄,然后創建一個名為“file_paths”(名字可以隨便起,只要和在manifest注冊的provider所引用的resource保持一致即可)的資源文件,內容如下:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path path="Android/data/com.tyj.tourapp.fileprovider/" name="files_root" />
<external-path path="." name="external_storage_root" />
</paths>
?代表的根目錄: Context.getFilesDir()
?代表的根目錄: Environment.getExternalStorageDirectory()
?代表的根目錄: getCacheDir()
上述代碼中path=”“,是有特殊意義的,它代碼根目錄,也就是說你可以向其它的應用共享根目錄及其子目錄下任何一個文件了。
如果你將path設為path="pictures",那么它代表著根目錄下的pictures目錄(eg:/storage/emulated/0/pictures),如果你向其它應用分享pictures目錄范圍之外的文件是不行的。
第三步:使用FileProvider
上述準備工作做完之后,現在我們就可以使用FileProvider了。
我們需要將上述安裝APK代碼修改為如下
/**
* 通過隱式意圖調用系統安裝程序安裝APK
*/
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) { //判讀版本是否在7.0以上
//參數1 上下文, 參數2 Provider主機地址 和配置文件中保持一致 參數3 共享的文件
Uri apkUri =
FileProvider.getUriForFile(context, "com.a520wcf.chapter11.fileprovider", file);
//添加這一句表示對目標應用臨時授權該Uri所代表的文件
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);
}
這樣我們就能愉快的解決問題了,希望能幫助大家
參考文獻
[android應用開發app手動更新通知欄下載實踐]{http://www.lxweimin.com/p/e4f1dc089740}
[Android7.0適配教程心得]{http://www.lxweimin.com/p/56b9fb319310}