目錄
一、5.0新特性
二、6.0新特性
三、7.0新特性
四、8.0新特性
五、9.0新特性
六、 一步步跟着案例進行版本更新踩坑——DownloadManager
1、适配6.0 動态權限
2、适配Android 7.0 解析包時出現問題
3、适配Android 8.0:未知來源的應用權限
本案例下載下傳位址:
https://download.csdn.net/download/csdn_aiyang/10906816
https://github.com/aiyangtianci/DownloadManagerDemo
前言
Android 最近幾年發展非常快,自08年智能機時代開始,Android作業系統一路高歌,每年推出更新新的版本。然而,這一轉眼都10年過去了,10年的智能機發展之路,使Android系統占全球市場佔有率近7成(IOS 近3成)。有趣的是谷歌給自己的這個‘親兒子’起名字可謂絞盡了腦汁,Android1.1開始,就有過給起名Astro(小金剛)、Bender(機器人班亭)本是想從字母A到字母Z,每一代都用首字母排序,可是,由這樣的名字太二了,于是從Android1.5的時候,聰明的谷歌就想到了以甜點給‘親兒子’命名的文化傳統了,就在17年8月份谷歌為提高續航和安全釋出了Android 8.0,主題是“奧利奧餅幹”。但是,由于版本系統推出過快及相容過多舊版代碼,Android 版本碎片化越來越嚴重了。
谷歌從8.0後推出的一種新的技術架構“Project Treble”,可以改善安卓系統的碎片化問題。将作業系統架構代碼與“特定供應商”硬體代碼分開,允許手機廠商在無須晶片商參與的情況下推送更新,希望由此減少更新阻礙,加快新系統普及速度。在Android 9.0釋出後,谷歌加入了強制性的rollback protection功能。簡單來說,這項功能直接杜絕了使用者的系統降級操作,雖然早在Android 8.0中就已經加入了這項功能,但是不同的是,原本這項功能是處于使用者自選的狀态,在更新Android 9之後就變更為強制性了。這也就寓意着,從Android 9開始,之後的安卓系統終于要正式緻敬蘋果的iOS,系統隻提供更新,不允許降級。
其實,谷歌一直以來都在想盡辦法試圖解決安卓系統碎片化的問題,但是由于覆寫廣、适配裝置過多等因素,始終沒有收獲什麼成效。碎片化這一問題,也始終被使用者所诟病,不過谷歌這次真的下狠手了,通過在系統内置Android Verified Boot 2.0,來阻止使用者降級,一旦使用者強制輸入低于目前版本的安卓系統,被自動檢測到後會出現無限重新開機、無法開機,不得不佩服谷歌的這招真的是做絕了。
正文
在正式開篇之前,本人有些心路曆程想和大家分享一下,說白了,就是想吐吐這幾天苦水,哈哈哈。從收集文章素材、參考文章、動手寫demo、寫文章等,用了近一個星期。我個人覺得自己寫文章的效率很高,這篇文章中的案例在踩坑時出現過很多次奇怪的閃退,着實要費了些心思。例如,在一個手機上開發時遇見崩潰,換個手機就沒問題了,我實在搞不懂崩潰原因時候,也會像小白一樣重新建立新項目,把舊代碼一行行重新複制粘貼過去,重新運作項目去解決問題。然而,就這樣我盡然重新建立過四次。手動捂臉哭表情。說這些,是希望同學們能認真對待我的博文,謝謝,因為我很用心在分享技術!
一、5.0新特性
- Material Design
- 支援多種裝置
- 全新通知中心
- 支援 64 位 ART 虛拟機
- 電池續航改進
- 全新“最近應用程式”
- 安全性改進
- 不同資料獨立儲存
- 改進搜尋
- 支援藍牙 4.1、USB Audio、多人分享等
重點注意: 加了很多新控件,如抽屜布局,菜單布局,卡片布局,清單布局新增RecyclerView等。努力改善應用界面吧!
Android Material Design之CoordinatorLayout效果實作
Android ToolBar 基礎使用——進階封裝BaseActivity(附源碼)
Android 探究onCreateViewHolder和onBindViewHolder兩者關系和調用次數
Android 使用RecycleView實作吸附小标題的Demo(附源碼)
Android5.0Activity的轉場動畫、過渡動畫、過場動畫、跳轉動畫
Android共享元素轉場動畫Fragment to Fragment
二、6.0新特性
- 動态權限管理
- 系統層支援指紋識别
- APP 關聯
- Android Pay
- 電源管理
- TF 卡預設存儲
重點注意:動态的權限申請,6.0以下的版本可以直接申請權限直接使用了,以上的版本需要一些敏感權限時需要動态申請。可參考我的文章。
Android6.0版本以上危險權限動态申請及RxPermissions權限庫使用
三、7.0新特性
- 分屏多任務
- 下拉快捷開關
- 新通知消息
- 夜間模式
- 流量保護模式
- 全新設定樣式
- 改進 Doze 休眠機制
- 系統級電話黑名單
- 菜單鍵快速切換應用
重點注意: 7.0對于SDCard的檔案URI的通路做了限制,擷取檔案uri的方式也變了,開發時需要注意。 下面案例會講到。
鴻洋的:Android 7.0 行為變更 通過FileProvider在應用間共享檔案吧
四、8.0新特性
- 畫中畫
- 通知标志
- 自動填充架構
- 系統優化
- 背景限制
- 應用快捷鍵
- 語言區域和國際化
重點注意:
8.0限制了背景服務這些,啟動背景服務需要設定通知欄,使服務變成前台服務。
8.0對于安裝位置來源的應用做了更嚴格的限制,在app更新安裝時需要做些處理。 下面案例會講到。
Android 8.0 P适配詳細指南
五、9.0新特性
- 劉海設計
- 黑白模式切換
- 加入長截圖
- 加入護眼模式
- 通知欄的體驗優化
- Material Design功能更新等等
重點注意:Android 9.0強制使用https,會阻塞http請求,如果app使用的第三方sdk有http,将全部被阻塞。
六、10 Q 新特性
Q的最大更新就是使用者隐私權限變更。
- 存儲權限:判斷當應用運作在Q平台上時,通路自己檔案不需申請讀寫權限,通路音頻需要申請新的媒體特定權限。
- 背景定位權限:如果目标版本targetSDK <= P 請求了ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION權限,Q裝置會自動幫你申請ACCESS_BACKGROUND_LOCATION權限。
- 背景啟動 Activity:僅針對與使用者毫無互動就啟動一個Activity的情況,比如微信登陸授權。
- minSDK警告:谷歌要求運作在Q裝置上的應用targetSDK>=Android 6.0(API 級别 23),不然會向使用者發出警告。
- 裝置辨別符(DeviceId):TelephonyManager.getDeviceId()方法失效。新權限READ_PRIVILEGED_PHONE_STATE隻提供給系統app使用。下面是一個通過硬體資訊生産的UUID。裝置ID的擷取一個版本比一個版本艱難,如有好的方法請指出。
public static String getUUID() {
String serial = null;
String m_szDevIDShort = "35" +
Build.BOARD.length() % 10 + Build.BRAND.length() % 10 +
Build.CPU_ABI.length() % 10 + Build.DEVICE.length() % 10 +
Build.DISPLAY.length() % 10 + Build.HOST.length() % 10 +
Build.ID.length() % 10 + Build.MANUFACTURER.length() % 10 +
Build.MODEL.length() % 10 + Build.PRODUCT.length() % 10 +
Build.TAGS.length() % 10 + Build.TYPE.length() % 10 +
Build.USER.length() % 10; //13 位
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
serial = android.os.Build.getSerial();
} else {
serial = Build.SERIAL;
}
//API>=9 使用serial号
return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();
} catch (Exception exception) {
//serial需要一個初始化
serial = "serial"; // 随便一個初始化
}
//使用硬體資訊拼湊出來的15位号碼
return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();
}
- 無線和藍牙掃描權限:使用 WLAN API 和 Bluetooth API 時的權限,這兩種權限的變更影響較少。
最後,版本更新踩坑記——DownloadManager
DownloadManager使用介紹參考:https://blog.csdn.net/csdn_aiyang/article/details/64126379
MainActivity.class 代碼
public class MainActivity extends AppCompatActivity {
private TextView down;
private TextView progress;
private ProgressBar pb_update;
private DownloadManager downloadManager;
private DownloadManager.Request request;
public static String downloadUrl = "http://www.wanandroid.com/blogimgs/ecb4c318-42f3-454a-a6c4-615ad16f35bd.apk";
private DownloadReceiver completeReceiver;
private final Uri CONTENT_URI = Uri.parse("content://downloads/my_downloads");
private DownloadChangeObserver observer;
long id;
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Bundle bundle = msg.getData();
int pro = bundle.getInt("pro");
pb_update.setProgress(pro);
progress.setText(String.valueOf(pro) + "%");
}
};
class DownloadChangeObserver extends ContentObserver {
public DownloadChangeObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
updateView();
}
}
class DownloadReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, final Intent intent) {
Log.i("aaa", "廣播監聽");
long completeDownLoadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
Intent intentInstall = new Intent();
Uri uri = null;
if (completeDownLoadId == id) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // 相容6.0以下
uri = downloadManager.getUriForDownloadedFile(completeDownLoadId);
installPackge(context,intentInstall,uri);
}
}
}
}
/**
* 安裝APK
* @param context
* @param intentInstall
* @param uri
*/
private void installPackge(Context context,Intent intentInstall,Uri uri){
intentInstall.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intentInstall.setAction(Intent.ACTION_VIEW);
// 安裝應用
Log.i("aaa", "app下載下傳完成了,開始安裝。。。"+uri);
intentInstall.setDataAndType(uri, "application/vnd.android.package-archive");
context.startActivity(intentInstall);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
down = (TextView) findViewById(R.id.down);
progress = (TextView) findViewById(R.id.progress);
pb_update = (ProgressBar) findViewById(R.id.pb_update);
down.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
LoadApp();
//requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE,Code_PERMISSION);
}
});
}
private void LoadApp() {
//建立下載下傳對象
downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
request = new DownloadManager.Request(Uri.parse(downloadUrl));
request.setTitle("app-release.apk");
request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
request.setAllowedOverRoaming(false);
request.setMimeType("application/vnd.android.package-archive");
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
//設定檔案存放路徑
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "app-release.apk");
}
private void updateView() {
int[] bytesAndStatus = new int[]{0, 0, 0};
DownloadManager.Query query = new DownloadManager.Query().setFilterById(id);
Cursor c = null;
try {
c = downloadManager.query(query);
if (c != null && c.moveToFirst()) {
//已經下載下傳的位元組數
bytesAndStatus[0] = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
//總需下載下傳的位元組數
bytesAndStatus[1] = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
}
} finally {
if (c != null) {
c.close();
}
}
int pro = (bytesAndStatus[0] * 100) / bytesAndStatus[1];
Message msg = Message.obtain();
Bundle bundle = new Bundle();
bundle.putInt("pro", pro);
msg.setData(bundle);
handler.sendMessage(msg);
Log.i("aaa", "下載下傳進度:" + bytesAndStatus[0] + "/" + bytesAndStatus[1]);
}
//開始下載下傳
private void onDownBegin() {
try {
id = downloadManager.enqueue(request);
} catch (Exception e) {
e.printStackTrace();
} finally {
//更新UI
observer = new DownloadChangeObserver(handler);
getContentResolver().registerContentObserver(CONTENT_URI, true, observer);
//安裝
completeReceiver = new DownloadReceiver();
registerReceiver(completeReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
down.setText("正在下載下傳");
down.setClickable(false);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (observer != null) {
getContentResolver().unregisterContentObserver(observer);
unregisterReceiver(completeReceiver);
}
}
}
布局檔案:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:gravity="center"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:text="下載下傳進度"/>
<ProgressBar
android:id="@+id/pb_update"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="10dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="10dp"
android:max="100"
android:progress="10"
android:layout_marginBottom="20dp"
/>
<TextView
android:id="@+id/progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_marginBottom="20dp"
android:text="10%"/>
<TextView
android:id="@+id/down"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:paddingLeft="30dp"
android:paddingRight="30dp"
android:background="@color/colorAccent"
android:text="立即下載下傳"/>
</LinearLayout>
開始運作,進行版本适配。
1、适配6.0 動态權限
targetSdkVersion 23
此時,運作報錯:
java.lang.SecurityException:
No permission to write to /storage/emulated/0/Download/app-release.apk:
Neither user 10484 nor current process has android.permission.WRITE_EXTERNAL_STORAGE.
沒有讀寫權限。下面進行添加申請權限代碼:
AndroidManifest.xml 中:
<uses-permission android:name="android.permission.INTERNET" />;
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>;
MainActivity.class 中添加
/**
* 請求權限
*/
int Code_PERMISSION = 0;
/**
* 權限申請
* @param ManifestPermission
* @param CODE
* @return
*/
private boolean requestPermission(final String ManifestPermission, final int CODE) {
//1. 檢查是否已經有該權限
if (ContextCompat.checkSelfPermission(this,ManifestPermission) != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this,ManifestPermission)) {
new AlertDialog.Builder(this)
.setTitle("權限申請")
.setMessage("親,沒有權限我會崩潰,請把權限賜予我吧!")
.setPositiveButton("賞給你", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
// 使用者同意 ,再次申請
ActivityCompat.requestPermissions(MainActivity.this, new String[]{ManifestPermission}, CODE);
}
})
.setNegativeButton("就不給", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
// 使用者拒絕 ,如果APP必須有權限否則崩潰,那就繼續重複詢問彈框~~
}
}).show();
} else {
//2. 權限沒有開啟,請求權限
ActivityCompat.requestPermissions(this,
new String[]{ManifestPermission}, CODE);
}
} else {
//3. 權限已開,處理邏輯
return true;
}
return false;
}
//4. 接收申請成功或者失敗回調
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == Code_PERMISSION) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//權限被使用者同意,做相應的事情
onDownBegin();
} else {
//權限被使用者拒絕,做相應的事情
Toast.makeText(this,"拒絕了權限",Toast.LENGTH_SHORT);
}
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
在點選下載下傳時候進行權限申請。可以執行下載下傳了,但是最後安裝還是報錯了:
Caused by:
android.content.ActivityNotFoundException:
No Activity found to handle Intent {
act=android.intent.action.VIEW typ=application/vnd.android.package-archive flg=0x10000000 }
原因是在Android6.0以下和Android6.0上,通過DownloadManager 擷取到的Uri不一樣。差別如下:
- Android 6.0以下版本:getUriForDownloadedFile得到的為:file:///storage/emulated/0/Android/data/packgeName/files/Download/xxx.apk;
- Android 6.0時:getUriForDownloadedFile得到的值為: content://downloads/my_downloads/10 。
//通過downLoadId查詢下載下傳的apk,解決6.0以後安裝的問題
public static File queryDownloadedApk(Context context, long downloadId) {
File targetApkFile = null;
DownloadManager downloader = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
if (downloadId != -1) {
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(downloadId);
query.setFilterByStatus(DownloadManager.STATUS_SUCCESSFUL);
Cursor cur = downloader.query(query);
if (cur != null) {
if (cur.moveToFirst()) {
String uriString = cur.getString(cur.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
if (!TextUtils.isEmpty(uriString)) {
targetApkFile = new File(Uri.parse(uriString).getPath());
}
}
cur.close();
}
}
return targetApkFile;
}
在廣播監聽安裝app處添加6.0-7.0代碼:
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // 6.0以下
uri = downloadManager.getUriForDownloadedFile(completeDownLoadId);
installPackge(context,intentInstall,uri);
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // 6.0 - 7.0
File apkFile = queryDownloadedApk(context, completeDownLoadId);
uri = Uri.fromFile(apkFile);
installPackge(context,intentInstall,uri);
}
2、适配Android 7.0 解析包時出現問題
從Android 7.0開始,不再允許在app中把file:// Uri暴露給其他app,否則應用會抛出FileUriExposedException。異常如下:
E/AndroidRuntime: FATAL EXCEPTION: Timer-0
Process: com.demo.aiyang.downloadmanger, PID: 7985
android.os.FileUriExposedException: file:///storage/emulated/0/Download/app-release.apk exposed beyond app through Intent.getData()
原因在于,Google認為使用file:// Uri存在一定的風險。比如,檔案是私有的,其他app無法通路該檔案,或者其他app沒有申請READ_EXTERNAL_STORAGE運作時權限。使用FileProvider生成content:// Uri來替代file:// Uri,如下圖:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHLuVzVZVDbXllZ1ckW65kMMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL3cjN1IDNzETM5ATMwkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
使用FileProvider解決上述異常。
(1)聲明FileProvider
首先在Manifest.xml檔案中申明FileProvider。
//記得替換成你項目包名
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="你的包名.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
- android:name:固定寫法;
- android:authorities:可自定義,是用來辨別該provider的唯一辨別;
- android:exported:必須設定成 false,否則運作時報錯java.lang.SecurityException: Provider must not be exported ;
- android:grantUriPermissions:用來控制共享檔案的通路權限;
- <meta-data>節點中的android:resource指定了共享檔案的路徑。
(2) 添加file_paths.xml檔案到res的xml檔案下
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<root-path name="root" path="" />
<files-path name="files" path="" />
<cache-path name="cache" path="" />
<external-path name="external" path="" />
<external-files-path name="name" path="path" />
<external-cache-path name="name" path="path" />
</paths>
根元素<paths>是固定的,屬性path表示子目錄,内部元素節點如下:此處,我們apk下載下傳存放在Sdk外部存儲區目錄下 :
- <root-path/> 代表裝置的根目錄new File("/");
- <files-path/> 代表context.getFilesDir()
- <cache-path/> 代表context.getCacheDir()
- <external-path/> 代表Environment.getExternalStorageDirectory()
- <external-files-path>代表context.getExternalFilesDirs()
- <external-cache-path>代表getExternalCacheDirs()
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 該方式提供在應用的外部存儲區根目錄的下的檔案。
它對應Context#getExternalFilesDir(String) Context.getExternalFilesDir(null)傳回的路徑。
eg:”/storage/emulated/0/Android/data/com.jph.simple/files”
-->
<external-path name="download" path="" />
</paths>
上述代碼中path="",是有特殊意義的,它代碼根目錄。如果你将path設為path="pictures",那麼它代表着根目錄下的pictures目錄(eg:/storage/emulated/0/pictures),如果你向其它應用分享pictures目錄範圍之外的檔案是不行的。
(3)在廣播監聽APP安裝代碼中添加相容>7.0
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // 相容6.0以下
uri = downloadManager.getUriForDownloadedFile(completeDownLoadId);
installPackge(context,intentInstall,uri);
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // 相容6.0-70
File apkFile = queryDownloadedApk(context, completeDownLoadId);
uri = Uri.fromFile(apkFile);
installPackge(context,intentInstall,uri);
} else {//相容7.0
intentInstall.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 給目标應用一個臨時授權
File file= new File(Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_DOWNLOADS),"app-release.apk");
Uri uri = FileProvider.getUriForFile(context, getPackageName() + ".fileProvider",file);
installPackge(this,intentInstall,uri);
}
其中:
file路徑:/storage/emulated/0/Download/app-release.apk
使用FileProvide得到uri為:content://com.demo.aiyang.demo.fileProvider/download/Download/app-release.apk
這裡,講一個坑。
在下載下傳成功後,安裝時出錯,如下圖所示
錯誤擷取File路徑代碼如下:
//改正前寫法
File file = new File(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "app-release.apk");
//file路徑:/storage/emulated/0/Android/data/com.demo.aiyang.demo/files/Download/app-release.apk
//改正後寫法
File file= new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
"app-release.apk");
//file路徑:/storage/emulated/0/Download/app-release.apk
因為FileProvider不支援sdcard目錄下的檔案共享。Android 7.0 DownloadManager與FileProvider的坑
(一把辛酸淚!這個問題解決了很久,試了很多方法,比較坑的是将檔案下載下傳後再寫入到私有目錄!)
3、适配Android 8.0:未知來源的應用權限
Android 8.0的手機會出現線上更新不了新版本,華為榮耀V10手機測試apk下載下傳完成後直接白屏提示“解析包時出現問題”。原因是Android8.0以上未知來源的應用是不可以通過代碼來執行安裝的(允許手動安裝)。Google這麼做是為了防止不合法APK安裝侵犯了使用者權益。适配如下:
(1) 在清單檔案中申明權限
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
(2)監聽apk下載下傳狀态的廣播中添加代碼:
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // 相容6.0以下
uri = downloadManager.getUriForDownloadedFile(completeDownLoadId);
installPackge(context,intentInstall,uri);
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // 相容6.0-70
File apkFile = queryDownloadedApk(context, completeDownLoadId);
uri = Uri.fromFile(apkFile);
installPackge(context,intentInstall,uri);
} else {
Log.i("aaa", ">7.0");
InstallPackgeAPI28(context);// 相容Android 8.0
}
相容Android 8.0 是否有安裝權限
/**
* 相容 8.0 未知來源應用安裝
*/
int Code_INSTALLPACKAGES = 1;
@RequiresApi(api = Build.VERSION_CODES.O)
private void startInstallPermissionSettingActivity() {
Uri packageURI = Uri.parse("package:" + getPackageName());
Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, packageURI);
startActivityForResult(intent, Code_INSTALLPACKAGES);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == Code_INSTALLPACKAGES){
InstallPackgeAPI28(this);
}
}
private void InstallPackgeAPI28(Context context){
Intent intentInstall = new Intent();
intentInstall.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 給目标應用一個臨時授權
File file= new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),"app-release.apk");
Uri uri = FileProvider.getUriForFile(context, getPackageName() + ".fileProvider", file);
boolean isInstallPermission = false;//是否有8.0安裝權限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
isInstallPermission = getPackageManager().canRequestPackageInstalls();
if (isInstallPermission) {
installPackge(this,intentInstall,uri);
} else {
new AlertDialog.Builder(this)
.setTitle("權限申請")
.setMessage("親,沒有權限我會崩潰,請把權限賜予我吧!")
.setPositiveButton("賞給你", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startInstallPermissionSettingActivity();
}
}
}).setNegativeButton("取消",null ).show();
}
}else{
installPackge(this,intentInstall,uri);
}
}
運作結果:
4、适配Android 9.0:Https網絡請求
前面代碼基本上都已經算非常完整了,可以在大部分手機上都可以正常安裝和使用。但是,我發現依然有很多網友留言說讓我相容9.0。于是乎,我自己也用9.0的測試手機運作了一下,可以下載下傳安裝啊!沒問題啊!
呵呵。。。終究是我太大意了。 demo 裡的 targetSdkVersion 并沒有大于 API 28 Android 9.0 。是以,也就不會出現什麼問題。審視一下一邊代碼就明白了,無非就是無法下載下傳apk的問題。
Android P 9.0 的系統上面預設所有Http的請求都被阻止了,就沒辦法通路了網絡了。解決這個問題最好當然是把Http換成Https了。然鵝,并不是每個公司都做到了。還有一個辦法,就是通過在AnroidManifest.xml中的application标簽下設定如下屬性即可。
android:usesCleartextTraffic="true"
完。2019年12月24日16:55:03
歡迎訂閱公衆号:資料結構、算法、面試經驗、每日新聞、閑聊趣文等。歡迎一起學習!
歡迎加入Android QQ交流學習群:
參考連結:
https://www.jianshu.com/p/c58d17073e65
https://blog.csdn.net/yq6073025/article/details/52934326
https://www.jianshu.com/p/121bbb07cb07