1. 程式人生 > 其它 >Android 10 和Android 11的適配

Android 10 和Android 11的適配

背景

最近在專案中著手做Android10Android11適配時候,期間遇到了不少的坑。之前有專門寫過qq、微信分享的適配。但是此次在針對偏業務側適配工作的時候還是碰到了一些新的問題。記錄下來,方便以後查閱,希望能幫到碰到此問題的相關同學。

一、 私有目錄下資源訪問

存在這樣一個場景:我們要分享一張圖片到qq或者微信,首先第一步是要是得到這個bitmap(通過本地生成或者網路載入),然後儲存到本地sd卡上,最後把儲存的圖片的絕對路徑傳給qq或者微信即可。

在以上的場景中,涉及到了這些關鍵點:

  • 把圖片儲存到sd卡
  • 把絕對路徑path傳遞給qq或者微信

1.1 直接訪問sd卡的根目錄

通過FileOutPutStream來完成,在Android10以下都沒問題。路徑如下:

/storage/emulated/0/demo/sharePicture/1637048769163_share.jpg

但是在Android10及以上,就會存在會報錯:

java.io.FileNotFoundException: /storage/emulated/0/demo/sharePicture/1637048769163_share.jpg: open failed: EACCES (Permission denied)
//其實儲存許可權是同意了的

這是因為,我們被儲存分割槽限制了,不能直接訪問外部目錄。因此,我們需要修改儲存路徑為scope的App-specific目錄。

1.2 改為App-specific私有目錄

該目錄自己訪問不需要許可權,如果第三方訪問需要許可權! 因此,我們後面通過FileProvider去臨時授權即可。 如果對FileProvider不熟悉,可參考篇頭的文章。

/storage/emulated/0/Android/data/com.demo.test/files

當你再通過FileOutPutStream來儲存圖片時候,是成功的。

private fun saveImage(bitmap: Bitmap, storePath: String, filePath: String): Boolean {
        val appDir = File(storePath)
        if (!appDir.exists()) {
            appDir.mkdirs()
        }
        val file = File(filePath)
        if (file.exists()) {
            file.delete()
        }
        var fos: FileOutputStream? = null
        try {
            fos = FileOutputStream(file)
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos)
            fos.flush()
            return true
        } catch (e: IOException) {
            e.printStackTrace()
        } catch (e: FileNotFoundException) {
            e.printStackTrace()
        } finally {
            fos?.close()
        }
        return false
    }

經過測試,在29的下和29 的裝置下,分享qq、微信都成功了。

1.3 分享原理總結

分享的本質就是把圖片路徑qq或微信訪問,讓他們能夠訪問到我們的圖片。分割槽之前是儲存在外部sd卡,都沒有問題。

分割槽後,qq或微信沒法訪問的我們的私有目錄App-specific。因此,我們需要通過fileprovider轉換成content:// 格式去分享,臨時授權給qq或微信來訪問我們的圖片。

qq是內部自己做了fileprovider適配,因此,我們只需要傳入絕對路徑file://格式即可,而微信是需要接收content://格式,所以需要我們外部自己來轉換。

具體的適配邏輯參考篇頭的文章~

二、公共目錄下資源訪問

Google建議我們採用mediaStore或者SAF去訪問。在Android10上公共目錄下的圖片無法通過file:// 格式去訪問,提示找不到路徑。如glide載入、圖片選擇庫、裁剪框架等等都會收到影響。

但是,這裡有個坑: 在Android10上不行,在Android11上又可以!!為什麼?

因為Google改回來了,讓Android11支援file://格式了。。。。 (wtf? 我謝謝你啊~~)

**我這裡說的Android10android 11是指targetSdkVersion哦 **

2.1 往公共目錄插入一張圖片

只能通過mediaStore方式:

ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DESCRIPTION, "This is an image");
values.put(MediaStore.Images.Media.DISPLAY_NAME, "Image.png");
values.put(MediaStore.Images.Media.MIME_TYPE, "image/png");
values.put(MediaStore.Images.Media.TITLE, "Image.png");
values.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/test");

Uri external = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
ContentResolver resolver = context.getContentResolver();
//這裡就能拿到這個insertUri
Uri insertUri = resolver.insert(external, values);
LogUtil.log("insertUri: " + insertUri);

OutputStream os = null;
try {
    if (insertUri != null) {
        os = resolver.openOutputStream(insertUri);
    }
    if (os != null) {
        final Bitmap bitmap = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888);
        bitmap.compress(Bitmap.CompressFormat.PNG, 90, os);
        // write what you want
    }
} catch (IOException e) {
    LogUtil.log("fail: " + e.getCause());
} finally {
    try {
        if (os != null) {
            os.close();
        }
    } catch (IOException e) {
        LogUtil.log("fail in close: " + e.getCause());
    }
}

2.2 content uri轉file格式路徑

public static String getFilePathFromContentUri(Uri selectedVideoUri,
                                                  ContentResolver contentResolver) {
       String filePath;
       String[] filePathColumn = {MediaStore.MediaColumns.DATA};

       Cursor cursor = contentResolver.query(selectedVideoUri, filePathColumn, null, null, null);
       cursor.moveToFirst();

       int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
       filePath = cursor.getString(columnIndex);
       cursor.close();
       return filePath;
   }

2.3 根據圖片名來獲取file格式路徑

String imageName="test";

Uri external = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
ContentResolver resolver = BaseApp.getContext().getContentResolver();
String selection = MediaStore.Images.Media.TITLE + "=?";
String[] args = new String[] {imageName};
String[] projection = new String[] {MediaStore.Images.Media._ID};
Cursor cursor = resolver.query(external, projection, selection, args, null);
// 這裡的得到content 格式的uri 
Uri imageUri = null;
//content://media/external/images/media/318952
if (cursor != null && cursor.moveToFirst()) {
    imageUri = ContentUris.withAppendedId(external, cursor.getLong(0));
    cursor.close();
}

拿到絕對路徑後,在Android11上都 glide、qq分享、第三方的圖片選擇框架等都可以正常訪問。

三、終極適配方案

  • 在Android10上

開啟標誌位 :android:requestLegacyExternalStorage="true"來開啟相容模式,關閉分割槽適配,相當於targetSdkVersion=29的時候還是以舊的方式執行,完全沒問題。完美避開無法訪問公共目錄的坑!!!

  • 在Android11上

以上標誌會自動失效。因此,應用儲存的東西還在放在App-specific目錄下。分享私有目錄可以通過fileprovider方式適配。 要分享公共目錄,因為支援File api直接訪問公共目錄,因此,可以直接把content格式轉成file格式即可,具體可回看文中的第二部分。

最後,我還想問兩個問題:

1. targetSdk=30,android:requestLegacyExternalStorage="false"執行在Android10的裝置上 會咋麼樣?

答: 肯定會碰到許可權問題。因為,Android10的裝置還是以Android10的相容模式執行的。所以要改成true

2. targetSdk=30,android:requestLegacyExternalStorage="false"執行在Android11的裝置上 會咋麼樣?

答: 如果按照上面正常適配,肯定完全沒得問題!

以上是自己適配經驗,難免有疏忽之處,如果文章有問題或者更好的建議,歡迎評論指正~

相關教程

Android基礎系列教程:

Android基礎課程U-小結_嗶哩嗶哩_bilibili

Android基礎課程UI-佈局_嗶哩嗶哩_bilibili

Android基礎課程UI-控制元件_嗶哩嗶哩_bilibili

Android基礎課程UI-動畫_嗶哩嗶哩_bilibili

Android基礎課程-activity的使用_嗶哩嗶哩_bilibili

Android基礎課程-Fragment使用方法_嗶哩嗶哩_bilibili

Android基礎課程-熱修復/熱更新技術原理_嗶哩嗶哩_bilibili

本文轉自https://juejin.cn/post/7032525748686553095,如有侵權,請聯絡刪除。