04-AndroidQ及以上版本给x应用设置动态返回权限时总返回null的处理

本文介绍了如何解决Android Q上相机应用中设置照片存储位置的问题。通过调整代码使用新的API,实现了点击设置后能正确弹出请求外部存储权限的对话框,并成功将照片保存到SD卡。

最近需要实现一个在androidQ上相机添加可设置存储位置为sd卡或机身的开关。
(其实功能在android9.0已经实现了,直接Merge过来发现点击设置的存储到sd卡后功能无法实现,用9.0的机器试了一下,发现点击后会出现一个动态访问权限的弹窗,提示要获取读取sd卡的权限,而在androidQ上则不会出现)
经过打Log发现在

    private void createAccessPermission(String path) {
        StorageVolume storageVolume = mStorageManager.getStorageVolume(new File(path));
        Intent intent = storageVolume.createAccessIntent(null);
        Log.d(TAG,"intent"+intent);
        intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
        mApp.getActivity().startActivityForResult(intent,      CameraActivity.EXTERNAL_ACCESS_PERMISSION_CODE);
    }

中intent返回值一直为null
后经排查发现storageVolume.createAccessIntent(null);在AndroidQ及以上版本被移除了

/*Note: 10.0 or higher, createAccessIntent is deprecated.
Callers should migrate to using createOpenDocumentTreeIntent{@link Intent#ACTION_OPEN_DOCUMENT_TREE} instead*/

根据提示,替换代码

//Intent intent = storageVolume.createAccessIntent(null);
 Intent intent = storageVolume.createOpenDocumentTreeIntent();

后编译运行,点击打开sd卡存储,会弹出访问sd卡的动态权限,给了权限后,即可实现拍照存储在sd卡上了。

总结
版本升级后有些方法不一定管用,多关注版本更新后方法的更新和实现,多看官方API,谢谢大家观看。

<think>我们注意到在Avalonia 11.x中,`Application.Current.ApplicationLifetime`的API发生了变化,原先的`TryGetTarget`方法不再适用。因此,我们需要寻找新的方式来获取Android的Context。 根据Avalonia 11.x的更新,我们可以通过`Avalonia.Application`的`TopLevel`来获取平台相关的服务。具体来说,我们可以使用`TopLevel.GetTopLevel(control)`来获取当前顶级窗口,然后通过它来获取`IActivatableLifetime`,进而获取Android的Context。 但是,在Android平台上,我们实际上需要的是`AndroidActivity`实例,它包含了我们需要的Context。 以下是调整后的实现步骤: ### 调整后的核心代码 ```csharp using System; using System.IO; using Avalonia; using Avalonia.Controls; using Avalonia.Android; using Android.Content; using Android.Net; using AndroidX.Core.Content; using Java.IO; public static class FileLauncher { public static void OpenTextFile(Control control, string filePath) { if (!File.Exists(filePath)) throw new FileNotFoundException("文件不存在", filePath); if (OperatingSystem.IsAndroid()) { OpenWithAndroid(control, filePath); } else { throw new PlatformNotSupportedException("仅支持Android平台"); } } private static void OpenWithAndroid(Control control, string filePath) { // 获取当前顶级窗口 var topLevel = TopLevel.GetTopLevel(control); if (topLevel == null) throw new InvalidOperationException("无法获取TopLevel"); // 获取Android平台的Activity var activity = topLevel.PlatformImpl as AndroidActivity; if (activity == null) throw new InvalidOperationException("无法获取AndroidActivity"); var context = activity.Context; if (context == null) throw new InvalidOperationException("无法获取Android上下文"); // 创建Java文件对象 var javaFile = new File(filePath); // 根据Android版本选择URI生成方式 Uri uri; if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.Q) { // Android 10+ 使用FileProvider uri = FileProvider.GetUriForFile( context, $"{context.PackageName}.fileprovider", javaFile); } else { // Android 9及以下使用传统方式 uri = Uri.FromFile(javaFile); } // 创建并配置Intent var intent = new Intent(Intent.ActionView); intent.SetDataAndType(uri, "text/plain"); intent.AddFlags(ActivityFlags.GrantReadUriPermission); intent.AddFlags(ActivityFlags.NewTask); // 确保在新任务中启动 try { context.StartActivity(intent); } catch (ActivityNotFoundException) { throw new Exception("未找到可以打开文本文件的应用程序"); } } } ``` ### 使用示例 在Avalonia的视图代码中,我们可以这样使用: ```csharp // 假设在一个按钮点击事件中,this是当前的Control(比如Window或UserControl) public void OpenTxtFileButton_Click(object sender, RoutedEventArgs e) { try { string filePath = "/storage/emulated/0/Documents/example.txt"; FileLauncher.OpenTextFile(this, filePath); } catch (Exception ex) { // 处理异常 } } ``` ### 关键变化说明 1. **获取TopLevel**: 我们通过`TopLevel.GetTopLevel(control)`来获取当前控件的顶级窗口。这里的`control`可以是当前页面中的任意控件(如按钮所在的Window或UserControl)。 2. **获取AndroidActivity**: 通过`topLevel.PlatformImpl`我们可以得到`AndroidActivity`实例(在Android平台上)。这个对象包含了我们需要的`Context`。 3. **参数传递**: 我们需要将一个`Control`作为参数传递给`OpenTextFile`方法,以便获取`TopLevel`。 ### 注意事项 - 确保在调用此方法,传递的`control`已经附加到可视化树上,否则`TopLevel.GetTopLevel(control)`可能返回null- 文件路径需要是应用权限访问的路径。如果文件位于应用私有目录,可能需要先复制到外部存储,或者通过FileProvider共享。 ### 其他配置 其他配置(如AndroidManifest.xml的权限和FileProvider配置)与之前相同,请参考之前的步骤。 ### 替代方案:使用依赖注入 如果上述方法不可行,或者你希望避免传递Control参数,可以考虑在应用启动保存Android的Context。例如,在App.axaml.cs中: ```csharp public static Context? AndroidContext { get; private set; } public override void OnFrameworkInitializationCompleted() { if (ApplicationLifetime is ISingleTopLevelApplicationLifetime single) { single.MainWindow = new MainWindow(); } else if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { desktop.MainWindow = new MainWindow(); } else if (ApplicationLifetime is ISingleViewApplicationLifetime singleView) { singleView.MainView = new MainView(); } // 保存Android Context if (Avalonia.Application.Current?.ApplicationLifetime is AndroidApplication androidApp) { AndroidContext = androidApp.Context; } base.OnFrameworkInitializationCompleted(); } ``` 然后在FileLauncher中直接使用`App.AndroidContext`。但请注意,这种方法可能在某些情况下(如多窗口应用)不够灵活。 ### 结 在Avalonia 11.x中,由于API变化,我们通过`TopLevel.GetTopLevel(control).PlatformImpl`来获取Android的Context,从而实现了使用默认应用打开txt文件的功能。同,我们仍然需要注意Android版本差异和文件权限问题。 §§相关问题§§ 1. Avalonia 11.x 中如何获取当前控件的 TopLevel? 2. 在 Avalonia 11.x Android 应用中,如何通过依赖注入获取 Context? 3. 如何检查 Android 应用是否有权限访问外部存储? 4. 在 Android 10+ 中,如何配置 FileProvider 以共享文件?</think>在 Avalonia 11.x 中实现使用 Android 默认应用打开 TXT 文件,由于 API 变更导致 `Application.Current.ApplicationLifetime.TryGetTarget` 方法不可用,以下是替代方案和完整实现: ### 替代方案:获取 Android Context 的新方法 ```csharp using System; using System.IO; using Android.Content; using Android.Net; using AndroidX.Core.Content; using Avalonia.Android; using Avalonia.Controls; using Java.IO; public static class FileLauncher { public static void OpenTextFile(Control anchorControl, string filePath) { if (!File.Exists(filePath)) throw new FileNotFoundException("文件不存在", filePath); if (OperatingSystem.IsAndroid()) { OpenWithAndroid(anchorControl, filePath); } else { throw new PlatformNotSupportedException("仅支持Android平台"); } } private static void OpenWithAndroid(Control anchorControl, string filePath) { // 获取Android上下文的新方法 var topLevel = TopLevel.GetTopLevel(anchorControl); if (topLevel?.PlatformImpl is not AndroidPlatformHandle androidHandle) throw new InvalidOperationException("无法获取Android上下文"); var context = androidHandle.Activity; if (context == null) throw new InvalidOperationException("Android上下文为空"); // 创建Java文件对象 var javaFile = new File(filePath); // 根据Android版本选择URI生成方式 Uri uri; if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.Q) { // Android 10+ 使用FileProvider uri = FileProvider.GetUriForFile( context, $"{context.PackageName}.fileprovider", javaFile); } else { // Android 9及以下使用传统方式 uri = Uri.FromFile(javaFile); } // 创建并配置Intent var intent = new Intent(Intent.ActionView); intent.SetDataAndType(uri, "text/plain"); intent.AddFlags(ActivityFlags.GrantReadUriPermission); intent.AddFlags(ActivityFlags.NewTask); // 确保在新任务中启动 try { context.StartActivity(intent); } catch (ActivityNotFoundException) { throw new Exception("未找到可以打开文本文件的应用程序"); } } } ``` ### 使用示例 ```csharp // 在按钮点击事件中调用 public void OpenTxtFileButton_Click(object sender, RoutedEventArgs e) { try { // 获取当前控件作为锚点 var control = sender as Control; if (control == null) return; // 示例文件路径 string filePath = "/storage/emulated/0/Documents/example.txt"; FileLauncher.OpenTextFile(control, filePath); } catch (Exception ex) { // 处理异常 } } ``` ### 关键变化说明 1. **获取 Android 上下文的新方式**: ```csharp var topLevel = TopLevel.GetTopLevel(anchorControl); var androidHandle = topLevel?.PlatformImpl as AndroidPlatformHandle; var context = androidHandle?.Activity; ``` 这是 Avalonia 11.x 中推荐的获取 Android Context 的方法[^1] 2. **参数传递要求**: - 需要传递一个可视控件作为锚点(如按钮、窗口等) - 该控件必须已附加到可视化树中 ### 必需的 Android 配置 #### 1. AndroidManifest.xml 添加权限 ```xml <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> ``` #### 2. 配置 FileProvider ```xml <provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> ``` #### 3. 创建 file_paths.xml ```xml <?xml version="1.0" encoding="utf-8"?> <paths> <external-path name="external_files" path="." /> <external-files-path name="external_files" path="." /> <cache-path name="cache" path="." /> </paths> ``` ### 常见问题解决方案 1. **获取的 Context 为 null**: - 确保传递的控件已附加到可视化树 - 在控件加载完成的事件中调用(如 `Loaded` 事件) 2. **权限问题**: ```csharp // 动态请求存储权限 public async Task RequestStoragePermission() { var status = await Permissions.RequestAsync<Permissions.StorageRead>(); if (status != PermissionStatus.Granted) { // 处理权限拒绝 } } ``` 3. **文件路径问题**: - 使用 `Android.OS.Environment.GetExternalStoragePublicDirectory()` 获取标准路径 - 对于应用私有文件,使用 `context.GetExternalFilesDir(null)` 获取路径 ### 备选方案:全局保存 Context 在 App 初始化保存 Context: ```csharp // App.axaml.cs public partial class App : Application { public static Context? AndroidContext { get; private set; } public override void OnFrameworkInitializationCompleted() { if (ApplicationLifetime is AndroidApplication androidApp) { AndroidContext = androidApp.Context; } base.OnFrameworkInitializationCompleted(); } } // 使用全局Context FileLauncher.OpenWithAndroid(App.AndroidContext, filePath); ``` 此方案避免了传递控件参数,但需确保在 Android 平台上运行[^1]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值