簡體   English   中英

Android 29/Q 上的 MediaScanner scanFile / CameraRoll 的替代方案

[英]Alternative for MediaScanner scanFile / CameraRoll on Android 29/Q

Google Play 商店針對使用清單標志requestLegacyExternalStorage的應用程序設置了關於 Android 范圍存儲的新要求。
我的應用正在使用來自 React Native 社區的 CameraRoll package,該社區尚不支持范圍存儲(並且需要requestLegacyExternalStorage標志才能工作),而且時間線很短( 2021 年 5 月 5 日)。 CameraRoll 有什么替代品嗎? 此處的目標是在用戶圖庫應用程序中顯示圖像,例如 Google 照片或供應商默認圖庫,而無需在用戶端進行額外操作。

原始 Google Play 消息:

 Starting May 5th, you must let us know why your app requires broad storage access APPNAME 14 avr. 2021 19:26 We've detected that your app contains the requestLegacyExternalStorage flag in the manifest file of 1 or more of your app bundles or APKs. Developers with apps on devices running Android 11+ must use Scoped Storage to give users better access control over their device storage. To release your app on Android 11 or newer after May 5th, you must either: Update your app to use more privacy friendly best practices, such as the Storage Access Framework or Media Store API Update your app to declare the All files access (MANAGE_EXTERNAL_STORAGE) permission in the manifest file, and complete the All files access permission declaration in Play Console from May 5th Remove the All files access permission from your app entirely For apps targeting Android 11, the requestLegacyExternalStorage flag will be ignored. You must use the All files access permission to retain broad access. Apps requesting access to the All files access permission without a permitted use will be removed from Google Play, and you won't be able to publish updates.

深入研究 ReactNative CameraRoll package,它所做的不僅僅是掃描文件以供操作系統顯示在用戶圖庫應用程序中。 這里的解決方案有一些影響:

  1. 圖像需要位於公共目錄中才能在任何圖庫應用程序中顯示(= 讀取訪問權限),而不是在應用程序外部存儲中:

    • 不是: storage/android/data/com.example/Pictures
    • PicturesDCIMDownload中(檢查Environment.DIRECTORY_DCIM兄弟姐妹)
  2. android 需要原生 package

編碼:

反應原生部分:

import RNFS from 'react-native-fs'
const { PNModule } = ReactNative.NativeModules

try {
    if (Platform.OS === 'android' && Platform.Version >= 29) {
        // Google ask that the requestLegacyExternalStorage is no longer used when targeting android 11, and use
        // the scoped storage or the new global permission, see https://gitlab.inria.fr/floristic/pn-mobile-test/-/issues/417
        // Solution here, custom module which use the MediaStore API and copy the file to the DCIM folders.
        const segments = path.split('/')
        const fileName = segments[segments.length - 1]

        const fileUriPath = await PNModule.moveToMediaStore(path.replace('file://', ''), fileName)
        if (!fileUriPath) {
            return null
        }
        const scanResult = await RNFS.scanFile(fileUriPath)
        if (fileUriPath.startsWith('file:///')) {
            return fileUriPath
        }
        return `file://${fileUriPath}`
    }
    return await CameraRoll.save(path)
} catch (error) {
    console.error(error)
}

本機 package (不要忘記用您的應用文件夾替換APPNAME

    @ReactMethod
    public void moveToMediaStore(String filePath, String fileName, Promise promise) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
            promise.resolve(null);
            return;
        }
        ContentValues values = new ContentValues();
        values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
        values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");

        values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM + "/APPNAME");
        values.put(MediaStore.MediaColumns.IS_PENDING, 1);

        ContentResolver resolver = getReactApplicationContext().getContentResolver();
        Uri imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);

        try {
            OutputStream fos = resolver.openOutputStream(imageUri);
            copy(new File(filePath), fos);
            values.clear();
            values.put(MediaStore.Video.Media.IS_PENDING, 0);
            resolver.update(imageUri, values, null, null);
            promise.resolve(getNameFromContentUri(getReactApplicationContext(), imageUri));
        } catch (Exception e) {
            e.printStackTrace();
            promise.reject(e);
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.Q)
    public static void copy(File src, OutputStream out) throws IOException {
        try (InputStream in = new FileInputStream(src)) {
            FileUtils.copy(in, out);
        }
    }

    // From https://stackoverflow.com/a/64359655/1377145
    public static String getNameFromContentUri(Context context, Uri contentUri){
        ContentResolver contentResolver = context.getContentResolver();
        Cursor cursor = contentResolver.query(contentUri, null, null, null, null);
        cursor.moveToFirst();
        String document_id = cursor.getString(0);
        document_id = document_id.substring(document_id.lastIndexOf(":") + 1);
        cursor.close();

        cursor = contentResolver.query(
            android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            null, MediaStore.Images.Media._ID + " = ? ", new String[]{document_id}, null);
        cursor.moveToFirst();
        String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
        cursor.close();
        return path;
    }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM