- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
在 Flutter 中开发面向 Android SDK 30+ 的 Android 应用。
我想将数据(xml 文件)读写到如下内容:
/storage/emulated/0/CustomDirectory/example.xml
四处阅读我想我应该使用 Intent.ACTION_OPEN_DOCUMENT_TREE
所以我写了一个 MethodChannel
允许我打开 SelectDialog
美好的。 (为简洁起见,我删除了所有的 try-catch 和错误处理)
private fun selectDirectory() {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.addFlags(
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
startActivityForResult(intent, 100)
}
@RequiresApi(Build.VERSION_CODES.Q)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
val uri = data.data!!
contentResolver.takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
return uri.toString())
}
我可以从 Flutter 中调用它,它会打开“选择目录”对话框,我可以选择我的 CustomDirectory
,然后返回一个内容 URI:
content://com.android.externalstorage.documents/tree/primary%3ACustomDirectory
如何将其转换为 Flutter 目录?
在 Flutter 中,我可以调用 Directory.fromUri(...)
但这只是抛出
Unsupported operation: Cannot extract a file path from a content URI
所以我有点不确定从这里去哪里,我是否需要更改我的 Intent
的标志,或者我在某处做错了什么?
最佳答案
这将是一个很长的答案,并且很多代码都是针对我的用例的,因此如果有人想重用它,您可能需要进行一些调整。
基本上随着 Android 30+ 的变化,我无法在不请求可怕的 manage_external_storage
的情况下获得写入用户手机上不是我的应用程序自己目录的目录的权限。
我通过使用原生 Kotlin 然后通过 Dart 中的接口(interface)调用这些方法来解决这个问题。
首先从Kotlin代码开始
class MainActivity : FlutterActivity() {
private val CHANNEL = "package/Main"
private var pendingResult: MethodChannel.Result? = null
private var methodCall: MethodCall? = null
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
CHANNEL
).setMethodCallHandler { call, result ->
val handlers = mapOf(
"getSavedRoot" to ::getSavedRoot,
"selectDirectory" to ::copyDirectoryToCache,
"createDirectory" to ::createDirectory,
"writeFile" to ::writeFile,
)
if (call.method in handlers) {
handlers[call.method]!!.invoke(call, result)
} else {
result.notImplemented()
}
}
}
这会设置我们的 MainActivity
以监听 setMethodCallHandler
方法中命名的方法。
关于如何在 Kotlin 中实现基本的 IO 功能,您可以找到很多示例,所以我不会在这里全部发布,而是一个如何打开内容根目录并处理结果的示例:
class MainActivity : FlutterActivity() {
//...
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private fun selectContentRoot(call: MethodCall, result: MethodChannel.Result) {
pendingResult = result
try {
val browseIntent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
startActivityForResult(browseIntent, 100)
} catch (e: Throwable) {
Log.e("selectDirectory", " error", e)
}
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 100 && resultCode == RESULT_OK) {
val uri: Uri = data?.data!!
contentResolver.takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION
)
contentResolver.takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
return pendingResult!!.success(uri.toString())
}
return
}
//..
现在为了在 Dart 中调用该代码,我创建了一个名为 AndroidInterface
的接口(interface)并实现了
class AndroidInterface {
final _platform = const MethodChannel('package/Main');
final _errors = {
'no persist document tree': FileOperationError.noSavedPersistRoot,
'pending': FileOperationError.pending,
'access error': FileOperationError.accessError,
'exists': FileOperationError.alreadyExists,
'creation failed': FileOperationError.creationFailed,
'canceled': FileOperationError.canceled,
};
String? _root;
// invoke a method with given arguments
Future<FileOperationResult<String>> _invoke(
String method, {
bool returnVoid = false,
String? root,
String? directory,
String? subdir,
String? name,
Uint8List? bytes,
bool? overwrite,
}) async {
try {
final result = await _platform.invokeMethod<String>(method, {
'root': root,
'directory': directory,
'subdir': subdir,
'name': name,
'bytes': bytes,
'overwrite': overwrite,
});
if (result != null || returnVoid) {
final fileOperationResult = FileOperationResult(result: result);
fileOperationResult.result = result;
return fileOperationResult;
}
return FileOperationResult(error: FileOperationError.unknown);
} on PlatformException catch (e) {
final error = _errors[e.code] ?? FileOperationError.unknown;
return FileOperationResult(
error: error,
result: e.code,
message: e.message,
);
}
}
Future<FileOperationResult<String>> selectContentRoot() async {
final result = await _invoke('selectContentRoot');
// release currently selected directory if new directory selected successfully
if (result.error == FileOperationError.success) {
if (_root != null) {
await _invoke('releaseDirectory', root: _root, returnVoid: true);
}
_root = result.result;
}
return result;
}
//...
它基本上通过 _platform.invokeMethod
发送请求,传递方法的名称和要发送的参数。
使用工厂模式,您可以实现运行 30+ 的接口(interface)设备,并为 Apple 和运行 29 及以下的设备使用标准内容。
类似于:
abstract class IOInterface {
//...
/// Select a subdirectory of the root directory
Future<void> selectDirectory(String? message, String? buttonText);
}
还有一个工厂来决定使用什么接口(interface)
class IOFactory {
static IOInterface? _interface;
static IOInterface? get instance => _interface;
IOFactory._create();
static Future<IOFactory> create() async {
final component = IOFactory._create();
if (Platform.isAndroid) {
final androidInfo = await DeviceInfoPlugin().androidInfo;
final sdkInt = androidInfo.version.sdkInt;
_interface = sdkInt > 29 ? AndroidSDKThirty() : AndroidSDKTwentyNine();
}
if (Platform.isIOS) {
_interface = AppleAll();
}
return component;
}
}
最后,30+ 的实现看起来像
class AndroidSDKThirty implements IOInterface {
final AndroidInterface _androidInterface = AndroidInterface();
@override
Future<void> selectDirectory(String? message, String? buttonText) async {
final contentRoot = await _androidInterface.getContentRoot();
//...
}
希望这足以让您入门并指明正确的方向。
关于android - 使用 Android SDK 30+ Flutter 读/写外部存储,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69533579/
我在不同的硬件上测试 Cassandra 已经有一段时间了。 首先我有 2 个 CPU 和 6 GB RAM 然后我更改为 16 个 CPU 和 16 GB RAM(其中只有 6 GB 可供我的测试使
我只是想从二进制文件中读/写。我一直在关注 this教程,它的工作原理......除了它似乎正在将内容写入 txt 文件。我在测试的时候把文件命名为test.bin,但是记事本可以打开并正常显示,所以
我编写了一些简单的 Java 代码来从文本文件中读取字符串,将它们组合起来,然后将它们写回。 (有关输出没有变化的简化版本,请参见下面的片段) 问题是输入文件和输出文件中的特定字符(- 和 ...)是
我真的很感兴趣——你为什么要放 readln; 从键盘读取一些值到变量后的行?例如, repeat writeln('Make your choise'); read(CH); if (CH = '1
只要程序不允许同时写入存储在模块中的共享数据结构的相同元素,它是线程安全的吗?我知道这是一个菜鸟问题,但在任何地方都找不到明确解决的问题。情况如下: 在程序开始时,数据被初始化并存储在模块级可分配数组
我有一个数据结构,其操作可以归类为读取操作(例如查找)和写入操作(例如插入、删除)。这些操作应该同步,以便: 读操作不能在写操作执行时执行(除非在同一线程上),但是读操作可以与其他读操作并发执行。 在
我在Java套接字编程中有几个问题。 在读取客户端套接字中的输入流时,如果抛出IO异常;那么我们是否需要重新连接服务器套接字/再次初始化客户端套接字? 如果我们关闭输出流,它将关闭客户端套接字吗? 如
我正在尝试从客户端将结构写入带有套接字的服务器。 结构是: typedef struct R { int a; int b; double c; double d; double result[4];
我想知道是否可以通过 Javascript 从/向 Azure Active Directory 广告读取/写入数据。我读到 Azure 上有 REST 服务,但主要问题是生成与之通信的 token
我希望有人能提供完整的工作代码,允许在 Haskell 中执行以下操作: Read a very large sequence (more than 1 billion elements) of 32
我有一个任务是制作考试模拟器。我的意思是,在老师输入某些科目的分数后,学生输入他的名字、姓氏和出生,然后他决定学生是否通过科目。所以,我有一个问题,如何用新行写入文件文本并通过重写该文件来读取(逐行读
我需要编写巨大的文件(超过 100 万行)并将文件发送到另一台机器,我需要使用 Java BufferedReader 一次读取一行。 我使用的是 indetned Json 格式,但结果不太方便,
我在 Android 应用程序中有一个读写操作。在 onCreate 上,将读取文件并将其显示为编辑文本并且可以进行编辑。当按下保存按钮时,数据将被写入 onCreate 上读取的同一文件中。但我得到
我正在编写一个程序,该程序从一个文件读取输入,然后该程序将格式化数据并将其写入另一个文件。 输入文件: Christopher kardaras,10 N Brainard,Naperville,IL
我有一个 SCALA(+ JAVA) 代码,它以一定的速率读写。分析可以告诉我代码中每个方法的执行时间。如何衡量我的程序是否达到了最大效率?为了使我的代码优化,以便它以给定配置可能的最大速度读取。我知
嗨,我想知道如何访问 java/maven 中项目文件夹中的文件,我考虑过使用 src/main/resources,但有人告诉我,写入此目录中的文件是一个坏主意,并且应该只在项目的配置中使用,所以我
我想读\写一个具有以下结构的二进制文件: 该文件由“RECORDS”组成。每个“RECORD”具有以下结构:我将以第一条记录为例 (红色)起始字节:0x5A(始终为 1 字节,固定值 0x5A) (绿
我想制作一个C程序,它将用一些参数来调用;每个参数将代表一个文件名,我想在每个参数中写一些东西。 FILE * h0; h0 = fopen(argv[0],"w"); char buff
我有一个包含团队详细信息的文件。我需要代码来读取文件,并将获胜百分比写入第二个文件。我还需要使用指示的搜索功能来搜索团队的具体信息。该代码未写入百分比文件。当菜单显示时,第一个文件的内容被打印,但代码
我正在使用 read() 和 write() 函数来处理我的类,并且我正在尝试使用一个函数来写入它所读取的内容以及我作为参数给出的前面的内容。 例如,我想给出 10 作为我的程序的参数 int mai
我是一名优秀的程序员,十分优秀!