前言
適配前臺程式員必不可少的作業之一,且可能要花大量的時間精力,
何為前臺程式員,是面向用戶的一端,包括前端、移動端、PC等等,
何為適配,適配就是當我們的開發環境、運行環境等發生變化的時候,程式依然能穩健運行,
而適配中最難為程式員的就是Android了,除了開發環境、運行環境等因素之外,因為Android開源的原因,還要適配各大廠商,,
而適配條件之多,經常讓Android程式員為之頭疼,
來看看相機、相冊相關的適配歷程:
- Android 6 權限適配
- Android 7 檔案適配
- Android 10/11 存盤適配
ok,接下來以一個更換頭像的小例子來講解一下,
示例

點擊頭像,然后彈窗,給出不同的選項,執行不同的操作,
mBinding.llImg.setOnClickListener {
TakeImageDialog {
when (it) {
TakeImageDialog.ALBUM -> {
openAlbum()
}
TakeImageDialog.CAMERA -> {
checkPermission()
}
}
}.show(supportFragmentManager, "TakeImageDialog")
}
定義后面會用到的一些引數變數:
//相機拍照保存的位置
private lateinit var photoUri: Uri
companion object {
private const val REQUEST_CODE_PERMISSIONS = 1000 //權限
private const val REQUEST_CODE_ALBUM = 1001 //相冊
private const val REQUEST_CODE_CAMERA = 1002 //相機
}
打開相冊
選擇圖片
private fun openAlbum() {
val intent = Intent()
intent.type = "image/*"
intent.action = "android.intent.action.GET_CONTENT"
intent.addCategory("android.intent.category.OPENABLE")
startActivityForResult(intent, REQUEST_CODE_ALBUM)
}
固定寫法,大差不差,
既然是startActivityForResult啟動方式,來看看onActivityResult回呼
回呼
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == RESULT_OK) {
when (requestCode) {
REQUEST_CODE_ALBUM -> {
doCrop(data?.data!!)
}
...
}
}
}
在requestCode是REQUEST_CODE_ALBUM的情況下:
doCrop(data?.data!!)
data?.data!!即是選擇圖片回傳的Uri,可以直接使用,這里進行了下一步操作,剪裁
剪裁
private fun doCrop(sourceUri: Uri) {
Intrinsics.checkParameterIsNotNull(sourceUri, "資源為空")
UCrop.of(sourceUri, getDestinationUri())//當前資源,保存目標位置
.withAspectRatio(1f, 1f)//寬高比
.withMaxResultSize(500, 500)//寬高
.start(this)
}
為了方便,這里使用了一個三方庫UCrop,使用簡單方便,
getDestinationUri()是當前資源裁剪后保存的目標位置
private fun getDestinationUri(): Uri {
val fileName = String.format("fr_crop_%s.jpg", System.currentTimeMillis())
val cropFile = File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), fileName)
return Uri.fromFile(cropFile)
}
UCrop的回呼同樣也在onActivityResult中
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == RESULT_OK) {
when (requestCode) {
REQUEST_CODE_ALBUM -> {
doCrop(data?.data!!)
}
UCrop.REQUEST_CROP -> {
val resultUri: Uri = UCrop.getOutput(data!!)!!
val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(resultUri))
// todo
}
UCrop.RESULT_ERROR -> {
val error: Throwable = UCrop.getError(data!!)!!
ToastUtil.show("圖片剪裁失敗" + error.message)
}
}
}
}
UCrop.getOutput(data!!)!!,即是回傳的Uri,可以直接操作,也可以轉成bitmap,
ok,到這里打開相冊就介紹完了,
接下來看重點,打開相機,
author:yechaoa
打開相機
打開相機的流程就要稍微復雜一點了,
權限
第一步不是打開,而是先檢查是否有相機權限,這個在某些手機上是必須的,比如華為,
- 組態檔添加:
<uses-permission android:name="android.permission.CAMERA" />
- 代碼:
private fun checkPermission() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
openCamera()
} else {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), REQUEST_CODE_PERMISSIONS)
}
}
- 回呼:
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == REQUEST_CODE_PERMISSIONS) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
openCamera()
} else {
ToastUtil.show("拒絕會導致無法使用相機")
}
}
}
openCamera方法就是打開相機了,
打開前適配
private fun openCamera() {
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
photoUri = getDestinationUri()
photoUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
//適配Android 7.0檔案權限,通過FileProvider創建一個content型別的Uri
FileProvider.getUriForFile(this, "$packageName.fileProvider", File(photoUri.path!!))
} else {
getDestinationUri()
}
//android11以后強制磁區存盤,外部資源無法訪問,所以添加一個輸出保存位置,然后取值操作
intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri)
startActivityForResult(intent, REQUEST_CODE_CAMERA)
}
- 適配一:
FileProvider.getUriForFile(this, "$packageName.fileProvider", File(photoUri.path!!))
7.0以上,使用fileProvider的方式共享檔案,
- 適配二:
intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri)
android 11以后強制磁區存盤,外部資源無法訪問,所以添加一個輸出保存位置photoUri,然后取值操作
回呼
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == RESULT_OK) {
when (requestCode) {
REQUEST_CODE_ALBUM -> {
doCrop(data?.data!!)
}
REQUEST_CODE_CAMERA -> {
//從保存的位置取值
doCrop(photoUri)
}
UCrop.REQUEST_CROP -> {
val resultUri: Uri = UCrop.getOutput(data!!)!!
val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(resultUri))
// todo
}
UCrop.RESULT_ERROR -> {
val error: Throwable = UCrop.getError(data!!)!!
ToastUtil.show("圖片剪裁失敗" + error.message)
}
}
}
這里注意,不是相冊那樣從data取值了,而是從我們保存的位置里取值,
后面剪裁跟相冊都是一樣的流程了,
總結
這個功能點最大的變動就是磁區存盤了,Android 10或許還能過度一下,但是Android 11以后就是強制執行磁區存盤了,
應用可以在不需要讀寫權限的情況下,訪問自己的磁區,執行讀寫操作,卸載之后磁區檔案也相應洗掉,所以就不能有把快取檔案放到競品的檔案夾下這種操作了,還是乖乖的吧,
在Android 10以下,還是要讀寫權限的,還是可以胡作非為的,
獲取自己的磁區地址:
getExternalFilesDir(Environment.DIRECTORY_PICTURES)
對應地址:
file:///storage/emulated/0/Android/data/包名/files/Pictures
file開頭是沙盒檔案,content開頭是共享檔案,
那假如我有訪問其他檔案的需求呢,比如相冊、音樂,那還是需要讀寫權限的,且得通過MediaStore API來進行訪問了,具體可以查看檔案,
最后
寫作不易,如果對你有用,點個贊唄 ^ _ ^
Android 11開發手冊
《Android 11 開發者手冊》
參考
- 官方相機檔案
- 官方權限檔案
- 官方存盤檔案
CSDN認證博客專家
Android Jetpack
Flutter
小程式
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/254089.html
標籤:其他
