Android CameraX適配Android11的踩坑之路

?11月份Google發(fā)通知上架的應(yīng)用必須適配到Android30描孟,要不然提交到google play的app不能發(fā)布更新鸠儿,用戶(hù)就只能使用舊版本潦闲。

CameraX適配Android11的整個(gè)流程圖如下:

?1.我們來(lái)看看Google的通知說(shuō)明:

自 2021 年 11 月 1 日起挤悉,針對(duì) Google Play 上的應(yīng)用和游戲的更新必須以 Android 11(API 級(jí)別 30)或更高版本為目標(biāo)運(yùn)行環(huán)境泣刹。此日期過(guò)后辈讶,您將無(wú)法上傳targetSdkVersion低于 30 的新 app bundle 和 APK命浴。

請(qǐng)注意,Wear OS應(yīng)用不受關(guān)于 API 級(jí)別 30 的要求限制贱除。

將您的應(yīng)用配置為使用新近的 API 級(jí)別能使安全性和性能上的顯著改進(jìn)惠及用戶(hù)生闲,同時(shí)仍然允許您的應(yīng)用在較低版本的 Android(低至minSdkVersion)上運(yùn)行。

查看遷移指南

2.目標(biāo)版本:

compileSdkVersion30

buildToolsVersion"30.0.3"

defaultConfig{

? ? applicationId"com.example.cameraxapp"

? ? minSdkVersion21

? ? targetSdkVersion30

? ? versionCode1

? ? versionName"1.0"

? ? testInstrumentationRunner"androidx.test.runner.AndroidJUnitRunner"

}


3.我們把sdk的版本改為30之后出現(xiàn)的錯(cuò)誤如下:

?以上錯(cuò)誤信息具體意思就是在Android11及以上的手機(jī)讀寫(xiě)文件失敗

4.先看沒(méi)有適配Android11之前的代碼:

private fun takePhoto() {

val imageCapture =imageCamera ?:return

/*? ? ? val photoFile = createFile(outputDirectory, DATE_FORMAT, PHOTO_EXTENSION)

val metadata = ImageCapture.Metadata().apply {

// Mirror image when using the front camera

isReversedHorizontal = lensFacing == CameraSelector.LENS_FACING_FRONT

}*/

? ? ? val mFileForMat = SimpleDateFormat(DATE_FORMAT, Locale.US)

val file = File(FileManager.getAvatarPath(mFileForMat.format(Date()) +".jpg"))

val outputOptions =

ImageCapture.OutputFileOptions.Builder(file).build()

imageCapture.takePicture(outputOptions, ContextCompat.getMainExecutor(this),

object : ImageCapture.OnImageSavedCallback {

override fun onError(exc: ImageCaptureException) {

Log.e(TAG,"Photo capture failed: ${exc.message}", exc)

ToastUtils.shortToast(" 拍照失敗 ${exc.message}")

}

override fun onImageSaved(output: ImageCapture.OutputFileResults) {

val savedUri = output.savedUri ?: Uri.fromFile(file)

ToastUtils.shortToast(" 拍照成功 $savedUri")

Log.d(TAG, savedUri.path.toString())

val mimeType = MimeTypeMap.getSingleton()

.getMimeTypeFromExtension(savedUri.toFile().extension)

MediaScannerConnection.scanFile(

this@MainActivity,

arrayOf(savedUri.toFile().absolutePath),

arrayOf(mimeType)

){ _, uri->

? ? ? ? ? ? ? ? ? ? ? Log.d(TAG,"Image capture scanned into media store: $uri")

}

? ? ? ? ? ? ? }

})

}


5.適配之后的正確代碼:

/**

? * 開(kāi)始拍照

? */

? private fun takePhoto() {

val imageCapture =imageCamera ?:return

? ? ? val photoFile = createFile(outputDirectory,DATE_FORMAT,PHOTO_EXTENSION)

val metadata = ImageCapture.Metadata().apply {

? ? ? ? ? // Mirror image when using the front camera

? ? ? ? ? isReversedHorizontal =lensFacing == CameraSelector.LENS_FACING_FRONT

? ? ? }

/*? ? ? val mFileForMat = SimpleDateFormat(DATE_FORMAT, Locale.US)

val file = File(FileManager.getAvatarPath(mFileForMat.format(Date()) + ".jpg"))*/

? ? ? val outputOptions =

ImageCapture.OutputFileOptions.Builder(photoFile).setMetadata(metadata).build()

imageCapture.takePicture(outputOptions, ContextCompat.getMainExecutor(this),

object : ImageCapture.OnImageSavedCallback {

override fun onError(exc: ImageCaptureException) {

Log.e(TAG,"Photo capture failed: ${exc.message}", exc)

ToastUtils.shortToast(" 拍照失敗 ${exc.message}")

}

override fun onImageSaved(output: ImageCapture.OutputFileResults) {

val savedUri = output.savedUri ?: Uri.fromFile(photoFile)

ToastUtils.shortToast(" 拍照成功 $savedUri")

Log.d(TAG, savedUri.path.toString())

val mimeType = MimeTypeMap.getSingleton()

.getMimeTypeFromExtension(savedUri.toFile().extension)

MediaScannerConnection.scanFile(

this@MainActivity,

arrayOf(savedUri.toFile().absolutePath),

arrayOf(mimeType)

){ _, uri->

? ? ? ? ? ? ? ? ? ? ? Log.d(TAG,"Image capture scanned into media store: $uri")

}

? ? ? ? ? ? ? }

})

}

?6.適配步驟:

6.1 初始化文件和圖片輸出路徑

6.2.創(chuàng)建一個(gè)文件:

?6.3.文件創(chuàng)建成功后把圖片插入媒體庫(kù):

val metadata = ImageCapture.Metadata().apply {

? ? // Mirror image when using the front camera

? ? isReversedHorizontal = lensFacing == CameraSelector.LENS_FACING_FRONT

}

6.4.構(gòu)建圖片輸出對(duì)象outputOptions:

val outputOptions =

? ? ImageCapture.OutputFileOptions.Builder(photoFile).setMetadata(metadata).build()

6.5.拍照成功后通過(guò)MediaScannerConnection.scanFile刷新圖庫(kù)照片

?7.拍照成功后的日志如下:

?拍照成功后的截圖:

8.拍照適配Android11步驟:

8.1 請(qǐng)求文件讀寫(xiě)權(quán)限月幌,這里在首頁(yè)已經(jīng)請(qǐng)求過(guò)了直接上代碼碍讯,實(shí)際項(xiàng)目根據(jù)需要每個(gè)界面都要?jiǎng)討B(tài)請(qǐng)求權(quán)限

if (allPermissionsGranted()) {

? ? // ImageCapture

? ? startCamera()

} else {

? ? ActivityCompat.requestPermissions(

? ? ? ? this, REQUIRED_PERMISSIONS, Constants.REQUEST_CODE_PERMISSIONS

? ? )

}

private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {

? ? ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED

}

override fun onRequestPermissionsResult(

? ? requestCode: Int, permissions: Array<String>, grantResults:

? ? IntArray

) {

? ? if (requestCode == Constants.REQUEST_CODE_PERMISSIONS) {

? ? ? ? if (allPermissionsGranted()) {

? ? ? ? ? ? startCamera()

? ? ? ? } else {

? ? ? ? ? ? ToastUtils.shortToast("請(qǐng)您打開(kāi)必要權(quán)限")

? ? ? ? ? ? finish()

? ? ? ? }

? ? }

}

8.2 調(diào)起系統(tǒng)相機(jī)拍照

/**

* 調(diào)起系統(tǒng)相機(jī)拍照

*/

private fun startSystemCamera() {

? ? val takeIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)

? ? val values = ContentValues()

? ? //根據(jù)uri查詢(xún)圖片地址

? ? photoUri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)

? ? Log.w("lzq", "photoUri:" + photoUri?.authority + ",photoUri:" + photoUri?.path)

? ? //放入拍照后的地址

? ? takeIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri)

? ? //調(diào)起拍照

? ? startActivityForResult(

? ? ? ? takeIntent,

? ? ? ? REQUEST_CODE_CAMERA

? ? )

}

8.3 拍照和裁剪回調(diào),由于加了系統(tǒng)裁剪扯躺,所以在拍照成功后會(huì)調(diào)用裁剪方法

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {

? ? super.onActivityResult(requestCode, resultCode, data)

? ? if (resultCode == RESULT_OK) {

? ? ? ? if (requestCode == REQUEST_CODE_CAMERA) {//拍照回調(diào)

? ? ? ? ? ? workCropFun(photoUri)

? ? ? ? } else if (requestCode == REQUEST_CODE_CROP) {//裁剪回調(diào)

? ? ? ? ? ? setAvatar()

? ? ? ? }

? ? }

}

8.4 圖片裁剪方法冲茸,適配Android11

/**

* 系統(tǒng)裁剪方法

*/

private fun workCropFun(imgPathUri: Uri?) {

? ? mUploadImageUri = null

? ? mUploadImageFile = null

? ? if (imgPathUri != null) {

? ? ? ? val imageObject: Any = FileUtil.getHeadJpgFile()

? ? ? ? if (imageObject is Uri) {

? ? ? ? ? ? mUploadImageUri = imageObject

? ? ? ? }

? ? ? ? if (imageObject is File) {

? ? ? ? ? ? mUploadImageFile = imageObject

? ? ? ? }

? ? ? ? val intent = Intent("com.android.camera.action.CROP")

? ? ? ? if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {

? ? ? ? ? ? intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)

? ? ? ? }

? ? ? ? intent.run {

? ? ? ? ? ? setDataAndType(imgPathUri, "image/*")// 圖片資源

? ? ? ? ? ? putExtra("crop", "true") // 裁剪

? ? ? ? ? ? putExtra("aspectX", 1) // 寬度比

? ? ? ? ? ? putExtra("aspectY", 1) // 高度比

? ? ? ? ? ? putExtra("outputX", 150) // 裁剪框?qū)挾?/p>

? ? ? ? ? ? putExtra("outputY", 150) // 裁剪框高度

? ? ? ? ? ? putExtra("scale", true) // 縮放

? ? ? ? ? ? putExtra("return-data", false) // true-返回縮略圖-data屯阀,false-不返回-需要通過(guò)Uri

? ? ? ? ? ? putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString()) // 保存的圖片格式

? ? ? ? ? ? putExtra("noFaceDetection", true) // 取消人臉識(shí)別

? ? ? ? ? ? if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {

? ? ? ? ? ? ? ? putExtra(MediaStore.EXTRA_OUTPUT, mUploadImageUri)

? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? val imgCropUri = Uri.fromFile(mUploadImageFile)

? ? ? ? ? ? ? ? putExtra(MediaStore.EXTRA_OUTPUT, imgCropUri)

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? startActivityForResult(

? ? ? ? ? ? intent, REQUEST_CODE_CROP

? ? ? ? )

? ? }

}

從上圖紅框內(nèi)容可以看到當(dāng)系統(tǒng)版本為Android11及以上時(shí)裁剪后直接獲取url和文件路徑的方式會(huì)報(bào)錯(cuò),提示讀寫(xiě)失敗轴术,解決方法為在Android11及以上的手機(jī)上通過(guò)MediaStore把uri插入到file中难衰,從而得到文件路徑.

?8.5 裁剪成功后設(shè)置用戶(hù)頭像,這里需要注意一下裁剪完之后這個(gè)路徑在Android11上面是不能直接獲取到的逗栽,也是需要MediaStore查詢(xún)媒體庫(kù)然后轉(zhuǎn)為file盖袭,最后才能把路徑設(shè)置為用戶(hù)頭像

/**

* 設(shè)置用戶(hù)頭像

*/

private fun setAvatar() {

? ? val file: File? = if (mUploadImageUri != null) {

? ? ? ? FileManager.getMediaUri2File(mUploadImageUri)

? ? } else {

? ? ? ? mUploadImageFile

? ? }

? ? Glide.with(this).load(file).into(iv_avatar)

? ? Log.d("filepath", file!!.absolutePath)

}

8.6 打印拍照成功后的圖片路徑為:

?總結(jié):

在Google11月份要求必須適配到30后,我們查閱很多資料彼宠,第一時(shí)間進(jìn)行了適配鳄虱,但是一路坎坷,所有文件權(quán)限可以解決文件讀寫(xiě)問(wèn)題凭峡,但是這個(gè)權(quán)限若應(yīng)用不是殺毒或文件管理類(lèi)這個(gè)權(quán)限是不允許隨便申請(qǐng)的拙已,即使你申請(qǐng)了上架google play的時(shí)候?qū)徍艘矔?huì)被拒絕,Android11外部文件不允許隨便讀寫(xiě)和刪除摧冀,今天只是講解了拍照和錄像時(shí)適配內(nèi)外部存儲(chǔ)權(quán)限倍踪,還有應(yīng)用可見(jiàn)性、Toast索昂、后臺(tái)運(yùn)行權(quán)限等等一些列的適配建车,在后面會(huì)寫(xiě)一篇文章全面總結(jié)一下最近的Android11適配工作。

最后給出最新的demo地址:感興趣的同學(xué)可以看看椒惨,如有問(wèn)題及時(shí)提出缤至,一起成長(zhǎng).

CameraXApp: Android CameraX相機(jī)Api的使用實(shí)例

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市康谆,隨后出現(xiàn)的幾起案子领斥,更是在濱河造成了極大的恐慌,老刑警劉巖沃暗,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件戒突,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡描睦,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)导而,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)忱叭,“玉大人,你說(shuō)我怎么就攤上這事今艺≡铣螅” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵虚缎,是天一觀的道長(zhǎng)撵彻。 經(jīng)常有香客問(wèn)我钓株,道長(zhǎng),這世上最難降的妖魔是什么陌僵? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任轴合,我火速辦了婚禮,結(jié)果婚禮上碗短,老公的妹妹穿的比我還像新娘受葛。我一直安慰自己,他們只是感情好偎谁,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布总滩。 她就那樣靜靜地躺著,像睡著了一般巡雨。 火紅的嫁衣襯著肌膚如雪闰渔。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,482評(píng)論 1 302
  • 那天铐望,我揣著相機(jī)與錄音冈涧,去河邊找鬼。 笑死蝌以,一個(gè)胖子當(dāng)著我的面吹牛炕舵,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播跟畅,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼咽筋,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了徊件?” 一聲冷哼從身側(cè)響起奸攻,我...
    開(kāi)封第一講書(shū)人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎虱痕,沒(méi)想到半個(gè)月后睹耐,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡部翘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年硝训,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片新思。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡窖梁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出夹囚,到底是詐尸還是另有隱情纵刘,我是刑警寧澤,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布荸哟,位于F島的核電站假哎,受9級(jí)特大地震影響瞬捕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜舵抹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一肪虎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧掏父,春花似錦笋轨、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至陶缺,卻和暖如春钾挟,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背饱岸。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工掺出, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人苫费。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓汤锨,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親百框。 傳聞我的和親對(duì)象是個(gè)殘疾皇子闲礼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • Android11新特性及部分適配 以下我分為兩部分講述,分別是 以Android11為目標(biāo)版本的應(yīng)用(targe...
    林凱k閱讀 13,172評(píng)論 7 6
  • 只簡(jiǎn)述我發(fā)現(xiàn)問(wèn)題的根源铐维,有些是適配了7.0柬泽,會(huì)報(bào)權(quán)限失敗問(wèn)題,那是由于沒(méi)有動(dòng)態(tài)授權(quán)導(dǎo)致嫁蛇,接下來(lái)我一步一步給大家實(shí)現(xiàn)...
    Wocus閱讀 2,362評(píng)論 4 5
  • 由于Google強(qiáng)制取消android:requestLegacyExternalStorage="true"锨并,所...
    資本家大惡人閱讀 791評(píng)論 0 0
  • Android7.0發(fā)布已經(jīng)有一個(gè)多月了,Android7.0在給用戶(hù)帶來(lái)一些新的特性的同時(shí)睬棚,也給開(kāi)發(fā)者帶來(lái)了新的...
    善良的老農(nóng)閱讀 767評(píng)論 0 5
  • Android7.0發(fā)布已經(jīng)有一個(gè)多月了第煮,Android7.0在給用戶(hù)帶來(lái)一些新的特性的同時(shí),也給開(kāi)發(fā)者帶來(lái)了新的...
    東經(jīng)315度閱讀 1,364評(píng)論 0 14