之前項目更新時都做了适配文檔,之前發了 Android O 适配 ,現在将 Android N 的文檔也分享一下
一、Uri适配
現在遇到的是調用系統照相機拍照攝像,照片裁剪,APK安裝等需要适配。有些Uri是不需要适配的,用了FileProvider反而有問題。比如照片裁剪中儲存裁剪照片的Uri詳細見下文,還有就是拍照後發送廣播将照片更新到圖庫的Uri也不能用FileProvider,如下。
File f = new File("");
Uri contentUri = Uri.fromFile(f);
mediaScanIntent.setData(contentUri);
context.sendBroadcast(mediaScanIntent);
下面介紹需要适配的Uri:
1、在Android7.0上調用系統相機
在Android7.0之前,如果你想調用系統相機拍照可以通過以下代碼來進行:
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
File file = new File(Environment.getExternalStorageDirectory(), "img.jpg");
Uri imageUri = Uri.fromFile(file);
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent, 1);
在Android7.0之後,需要使用FileProvider進行适配:
-
第一步:指定共享的目錄
在xml下建立file_paths檔案(名字任意)
<?xml version="1.0" encoding="utf-8"?>
<resources>
<paths>
<external-path path="" name="camera_photos" />
</paths>
</resources>
上述代碼中path="",是有特殊意義的,它代碼根目錄,也就是說你可以向其它的應用共享根目錄及其子目錄下任何一個檔案了,如果你将path設為path="pictures",
那麼它代表着根目錄下的pictures目錄(eg:/storage/emulated/0/pictures),如果你向其它應用分享pictures目錄範圍之外的檔案是不行的。
存在幾種類型:
<files-path>:内部存儲空間應用私有目錄下的 files/ 目錄,等同于 Context.getFilesDir() 所擷取的目錄路徑;
<cache-path>:内部存儲空間應用私有目錄下的 cache/ 目錄,等同于 Context.getCacheDir() 所擷取的目錄路徑;
<external-path>:外部存儲空間根目錄,等同于 Environment.getExternalStorageDirectory() 所擷取的目錄路徑;
<external-files-path>:外部存儲空間應用私有目錄下的 files/ 目錄,等同于 Context.getExternalFilesDir(null) 所擷取的目錄路徑;
<external-cache-path>:外部存儲空間應用私有目錄下的 cache/ 目錄,等同于 Context.getExternalCacheDir();
這五種子元素基本涵蓋内外存儲空間所有目錄路徑,包含應用私有目錄。同時,每個子元素都擁有 name 和 path 兩個屬性。
其中,path 屬性用于指定目前子元素所代表目錄下需要共享的子目錄名稱。注意:path 屬性值不能使用具體的獨立檔案名,隻能是目錄名
而 name 屬性用于給 path 屬性所指定的子目錄名稱取一個别名。後續生成 content:// URI 時,會使用這個别名代替真實目錄名。這樣做的目的,很顯然是為了提高安全性。
- 第二步:在manifest清單檔案中注冊provider
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="包名.provider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
注:
name:四大元件,必須唯一
authorities:唯一,一般用包名+字元串 來保證唯一
grantUriPermissions:true,表示授予 URI 臨時通路權限
exported:為false,為true則會報安全異常
meta-data下resource指定共享目錄檔案(第一步建立檔案)
- 第三步:Java中使用FileProvider
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
File file = new File(Environment.getExternalStorageDirectory(), "img.jpg");
Uri imageUri;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
imageUri = Uri.fromFile(file);
} else {
imageUri = FileProvider.getUriForFile(context, authority, file);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent, 1);
跟7.0前比較主要變化的就兩處:
- 将之前Uri的scheme類型為file的Uri改成了有FileProvider建立一個content類型的Uri
- 添加了intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);來對目标應用臨時授權該Uri所代表的檔案
上述代碼通過FileProvider的Uri getUriForFile (Context context, String authority, File file)靜态方法來擷取Uri.
該方法中authority參數就是manifest清單檔案中注冊provider的android:authorities="xxx"的值
将getUriForFile方法擷取的Uri列印出來如下:
content://xxx/camera_photos/img.jpg
2、裁剪照片
在Android7.0之前,可以通過以下代碼來進行:
Intent intent = new Intent("com.android.camera.action.CROP");
Uri imageUri = Uri.fromFile(imgFile); // 要裁剪的照片File
intent.setDataAndType(imageUri, "image/*");
File file = new File(Environment.getExternalStorageDirectory(), "crop.jpg");
Uri outputUri = Uri.fromFile(file); // 裁剪後的照片
intent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri);
...
startActivityForResult(intent, 1);
在Android7.0之後,需要使用FileProvider進行适配:
Intent intent = new Intent("com.android.camera.action.CROP");
Uri imageUri;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
imageUri = Uri.fromFile(imgFile);
} else {
imageUri = FileProvider.getUriForFile(context, authority, imgFile);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
intent.setDataAndType(imageUri, "image/*");
File file = new File(Environment.getExternalStorageDirectory(), "crop.jpg");
Uri outputUri = Uri.fromFile(file); // 裁剪後的照片
intent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri);
...
startActivityForResult(intent, 1);
跟7.0前比較主要變化的就兩處:
- 将之前需要裁剪照片Uri的scheme類型為file的Uri改成了有FileProvider建立一個content類型的Uri
- 添加了intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);來對目标應用臨時授權該Uri所代表的檔案
注:
照片裁剪中有兩個uri,需要将原始照片的uri通過FileProvider擷取,裁剪後的照片還通過Uri.fromFile擷取,否則會提示不能儲存照片。
3、安裝APK
同上,建立URI的方式不同
public static void installApk(Context context, String apkPath) {
File file = new File(apkPath);
if (file.exists()) {
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(Intent.ACTION_VIEW);
String type = "application/vnd.android.package-archive";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.setDataAndType(FileProvider.getUriForFile(context, authority, file), type);
} else {
intent.setDataAndType(Uri.fromFile(file), type);
}
context.startActivity(intent);
} else {
Toast.makeText(context, "檔案不存在", Toast.LENGTH_SHORT).show();
}
}
4、Android中引用第三方module Uri适配問題
當主app中按照上面方式适配後,在module中按上面三步适配後會報multiple error這樣的錯誤,原因是因為provider是四大元件,相同name隻能有一個,在打包合并manifest後出錯。解決方案兩種:
- 寫個類MyFileProvider繼承FileProvider,将manifest的provider的name改為MyFileProvider即可
public class MyFileProvider extends FileProvider {
}
- 不需要在module的manifest中寫provider,在代碼中适配時直接用主app的manifest中的provider的authorities的值即可
FileProvider.getUriForFile(context, authority, file);
即這裡authority直接用app的authority