Google Play Store对Android范围内的应用程序存储设置了新的要求,使用清单标志requestLegacyExternalStorage
我的应用程序正在使用React Native community提供的CameraRoll软件包,该软件包尚不支持作用域存储(并且需要requestLegacyExternalStorage
标志才能工作),时间非常短(2021年5月5日)。CameraRoll还有其他选择吗?这里的目标是在用户图库应用程序中显示图像,比如谷歌照片或供应商默认图库,而无需在用户端执行额外操作。
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软件包,它不仅仅是简单地扫描文件,让操作系统显示在用户库应用程序中。这里的解决方案是一些再强调:
>
映像需要在公共目录中才能显示(=读取访问权限)在任何库应用程序中,因此不在应用程序外部存储中:
storage/android/data/com。示例/图片
图片
或DCIM
或下载
(检查环境.目录_DCIM
)需要一个android的本地包
代码:
反应原生部分:
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)
}
本机软件包(别忘了用你的应用文件夹替换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;
}
我已经阅读了这个问答,我了解到有一些实用程序库实现了基类中缺少的东西。是否有实现的库类? 例如,我需要执行
问题内容: 出于各种原因,在编写 Java应用程序时 ,调用会被皱眉,所以如何通知调用过程并非一切都按计划进行? 编辑: 1是任何非零退出代码的。 问题答案: 当“应用程序”实际上是较大的Java应用程序(服务器)的子应用程序(例如servlet,applet)时,对的使用会被拒绝:在这种情况下,它可能会停止JVM并因此停止所有其他子应用程序。在这种情况下,抛出适当的异常(最好由应用程序框架/服务
问题内容: 我知道这个话题已经解决了上千次。但是我找不到解决办法。 我正在尝试计算列表(df2.list2)的列中出现列表(df1.list1的每一行)的频率。所有列表仅包含唯一值。List1包含约300.000行,list2包含30.000行。 我有一个有效的代码,但是它的运行速度非常慢(因为我使用的是迭代程序)。我也尝试过itertuples(),但它给了我一个错误(“要解压缩的值太多(预期2
允许我填充包含复选框和单选按钮的HTML表单的替代方法。 我已经设法使用eclipse中的HtmlUnit库将数据发送到html表单并检索页面(我已经发布了下面的Java代码)。 然而,当我将这些代码复制到我的Android项目中时,我发现Android不支持HtmlUnit库。 对于Android来说,HtmlUnit还有其他替代方案吗?另一种方法应该能够将文本、复选框、单选按钮填写到Html表
问题内容: 以前,我总是以为Vector在长度未知的情况下可以很好地用于非描述对象。据我所知,我也认为它也是线程安全的 有什么改变不应该再使用了,替代方案是什么? 问题答案: 您应该使用而不是。虽然使用了内部同步,但是对于实际的一致性而言,这很少够用,只会在真正不需要时降低执行速度。 另请参阅此stackoverflow问题。
问题内容: 我过去曾使用ServiceWrapper几次,但是,对于商业产品,双重许可有些复杂(通常需要付费)。是否存在具有类似功能的完全FOSS替代产品? 问题答案: 尽管我尚未完成评估,但YAJSW似乎是最好的选择。