更換頭像的功能基本在每個app都有的,而且是最基本的,我前段時間做了下項目中的更換頭像功能,記錄下遇到的坑
- android 7.0之後需要動態申請權限
- Android 7.0 file://開頭的被認爲是不合法的uri,需要轉成content://形式的uri
第一個坑,權限問題
如果您的應用在其清單中列出正常權限(即,不會對用戶隱私或設備操作造成很大風險的權限),系統會自動授予這些權限。如果您的應用在其清單中列出危險權限(即,可能影響用戶隱私或設備正常操作的權限),系統會要求用戶明確授予這些權限。Android 發出請求的方式取決於系統版本,而系統版本是應用的目標:
- 如果設備運行的是 Android 6.0(API 級別 23)或更高版本,並且應用的 targetSdkVersion 是 23 或更高版本,則應用在運行時向用戶請求權限。用戶可隨時調用權限,因此應用在每次運行時均需檢查自身是否具備所需的權限。
- 如果設備運行的是 Android 5.1(API 級別 22)或更低版本,並且應用的 targetSdkVersion 是 22 或更低版本,則系統會在用戶安裝應用時要求用戶授予權限。如果將新權限添加到更新的應用版本,系統會在用戶更新應用時要求授予該權限。用戶一旦安裝應用,他們撤銷權限的唯一方式是卸載應用。
系統權限分類:
- 正常權限涵蓋應用需要訪問其沙盒外部數據或資源,但對用戶隱私或其他應用操作風險很小的區域。例如,設置時區的權限就是正常權限。如果應用聲明其需要正常權限,系統會自動向應用授予該權限。
- 危險權限涵蓋應用需要涉及用戶隱私信息的數據或資源,或者可能對用戶存儲的數據或其他應用的操作產生影響的區域。例如,能夠讀取用戶的聯繫人屬於危險權限。如果應用聲明其需要危險權限,則用戶必須明確嚮應用授予該權限。
當你進行拍照和從相冊中選圖片的時候,都需要一個讀和寫的權限即Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE,
申請權限邏輯:
判斷用戶是否進行過授權
1.沒有授權
1.授權被拒絕過,則彈出彈窗引導用戶進行授權
2.授權沒有被拒絕過,則彈出系統請求權限的彈窗即可
2.已經授權,則按照業務流程繼續往下走
具體代碼如下:
/**
* 檢查並且獲取權限
*
* @param context 上下文
* @param permissionParms 申請權限參數
* @param requestCode 回調code
* @param needShowCustomDialog 是否需要彈自定義對話框
* @return
*/
fun checkAndRequestPermission(context : Context, permissionParms : String ,requestCode : Int, needShowCustomDialog : Boolean) : Boolean {
//用來記錄是否向系統申請過權限
val isInvokedSysMethod = SPUtil.getInstance().getBoolean(PERMISSION_PREFIX + permissionParms, false)
try {
val permission = ContextCompat.checkSelfPermission(context, permissionParms)
if (hasPermission(permission, permissionParms)) {
return true
} else {
/** 是否拒絕過授權 */
val isRejected =
ActivityCompat.shouldShowRequestPermissionRationale(context as Activity, permissionParms)
if ((isRejected || permission == PackageManager.PERMISSION_GRANTED || isInvokedSysMethod) && needShowCustomDialog) {
/**
* 1. 用戶拒絕過授權
* 2. 系統接口判斷爲已經授權,但實際上沒有授權的
*/
//TODO 彈出彈窗引導用戶
} else {
SPUtil.getInstance().putBoolean(PERMISSION_PREFIX + permissionParms, true)
ActivityCompat.requestPermissions(context as Activity, arrayOf(permissionParms), requestCode)
}
return false
}
} catch (e: Exception) {
LogUtil.printeException(e)
}
return false
}
當你在檢測用戶是否授權的時候,會碰到一個機型適配的問題
/**
* 是否真的獲取了權限
*
* @param apiPermissionResult 系統接口返回的權限結果
* @param permissonParams 權限參數
* @return
*/
fun hasPermission(apiPermissionResult: Int, permissonParams: String): Boolean {
/**
* 沒有授權,直接返回false
*/
if (apiPermissionResult != PackageManager.PERMISSION_GRANTED) {
return false
}
/**
* EUI系統,返回接口值
*/
if (mIsEui) {
return true
}
/**
* 小米手機(以及zuk等其他機型),即使接口返回授權,也未必真的授權,需要通過AppOpsManager再次判斷
*/
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
val appOpsManager = CommonLib.getApp().getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
var opStr = AppOpsManager.OPSTR_READ_EXTERNAL_STORAGE
if (TextUtils.isEmpty(opStr)) {
throw RuntimeException("不在監控權限池,請先添加!!!")
}
try {
val checkOp = appOpsManager.checkOp(opStr, Process.myUid(), CommonLib.getApp().packageName)
return checkOp == AppOpsManager.MODE_ALLOWED
} catch (e: Exception) {
e.printStackTrace()
}
}
/**
* 未判斷到小米是否真的授權,返回接口值,出問題小米自己背鍋
*/
return true
}
到目前爲止動態申請權限問題解決了
第二個坑就是Android 7.0,file://開頭的被認爲是不合法的uri,需要轉成content://形式的uri,否則會報錯FileUriExposedException
1.第一步
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
2.第二 步
3.第三步
USER_HEAD_PHOTO_FILE_NAME = System.currentTimeMillis().toString() + USER_HEAD_PHOTO_FILE_NAME
var headFile = File(Environment.getExternalStorageDirectory(), USER_HEAD_PHOTO_FILE_NAME)
var contentUri: Uri
var intentCamera = Intent()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
if (PermissionUtil.checkAndRequestPermission(this, Manifest.permission.CAMERA, REQUEST_CAMERA, true)) {
//畫重點
intentCamera.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
contentUri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".fileprovider", headFile)
} else {
return
}
} else {
contentUri = Uri.fromFile(headFile)
}
intentCamera.action = MediaStore.ACTION_IMAGE_CAPTURE
intentCamera.putExtra(MediaStore.EXTRA_OUTPUT, contentUri)
startActivityForResult(intentCamera, REQUESTCODE_TAKE)
到此結束