将文件存储在 Flutter 中的 Android 10、11、12+ 下载文件夹中。

在Flutter中,您可能会遇到在 Android 10+ 上将文件保存在公共存储中的问题。这可能令人沮丧,因为无法使用path_provider插件或任何其他插件保存文件。也可能是没有授予足够的权限。在本文中,我们将看到如何在使用 Flutter 在 Android 上进行范围存储后将文件保存到下载文件夹中。

我们将创建一个示例,首先使用Dio包从 URL 下载文件,然后将其存储在其中Download/AppName文件夹。

步骤:
1、在pubspec.yaml中添加相关包。
2、在AndroidManifest.xml中添加相关的存储和网络权限。
3、 从flutter调用一个原生的android函数。
4、请求存储运行时权限。
5、下载文件到本地目录。
6、将文件移动到 Download/AppName 文件夹。

第1步:

添加相关包pubspec.yaml

   path_provider: ^2.0.11
   dio: ^4.0.6
   device_info_plus: ^4.0.1

第2步:

在AndroidManifest.xml.

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

第 3 步:

现在,我们必须请求运行时存储权限。我们不会向 Flutter 请求权限,而是要从 Flutter 调用 android 的原生函数,这将请求权限。我们必须向本机函数请求权限,否则这将不起作用。

创建一个Button我们将请求权限的对象。

ElevatedButton(
              onPressed: requestStoragePermission,
              child: Text('Request Storage Permissions'),
            ),

创建一个将调用本机 android 方法的函数。

Future<void> requestStoragePermission() async {
    try {
      await platform.invokeMethod('requestStoragePermission');
    } on PlatformException catch (e) {
      print(e);
    }
  }

在您的 MainActivity 中创建一个名为 CHANNEL 的变量并为其设置一个值。此值应与 dart invokeMethod 值匹配。如果它们不匹配,颤振代码将无法调用 android 方法。

class MainActivity: FlutterActivity() {

private val CHANNEL ="com.example.androidstorage.android_12_flutter_storage/storage"

}

覆盖configureFlutterEngine内部MainActivity并添加MethodChannel代码。

override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
           if (call.method == "requestStoragePermission") {
                requestPermission(this@MainActivity as Context)
            } else {
                result.notImplemented()
            }
        }
    }

创建一个名为的新 kt 文件FileUtils.kt并添加一个名为requestPermission.在该方法中,调用ActivityCompat.requestPermissions以请求存储运行时权限。

object FileUtils {

 fun requestPermission(context: Context) {
        ActivityCompat.requestPermissions(
            context as Activity, arrayOf(
                android.Manifest.permission.READ_EXTERNAL_STORAGE,
                android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
                android.Manifest.permission.ACCESS_MEDIA_LOCATION
            ), 101
        );
    }

}

第4步:

下载文件到本地目录。

ElevatedButton(
              onPressed: downloadRecording,
              child: const Text('Download Recording!'),
            ),
void downloadRecording() async {
    String url =
        "https://file-examples.com/storage/fe8faa459062eec049e62d4/2017/11/file_example_MP3_700KB.mp3";

    String fileName = "Audio-Recording.mp3";
    String path = await _getFilePath(fileName);

    await dio.download(
      url,
      path,
      onReceiveProgress: (receivedBytes, totalBytes) {
        print("Rec: $receivedBytes , Total: $totalBytes");

        setState(() {
          progress = ((receivedBytes / totalBytes) * 100);
          if (progress == 100.0) {
            _saveFileToRecordings(path);
          }
        });
      },
      deleteOnError: true,
    ).then((value) => print(value.toString()));
  }
  Future<void> _saveFileToRecordings(String path) async {
    DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
    final androidInfo = await deviceInfoPlugin.androidInfo;
    print("Device Version: ${androidInfo.version.sdkInt}");

    if (Platform.isAndroid && androidInfo.version.sdkInt! >= 29) {
      try {
        await platform.invokeMethod('saveFile', {'path': path});
      } on PlatformException catch (e) {
        print(e);
      }
    } else {}
  }
  Future<String> _getFilePath(String fileName) async {
    DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
    final androidInfo = await deviceInfoPlugin.androidInfo;
    print("Device Version: ${androidInfo.version.sdkInt}");

    if (Platform.isAndroid && androidInfo.version.sdkInt! >= 29) {
      final dir = await getExternalStorageDirectory();
      print("File Name: ${dir!.path}/$fileName");
      return "${dir.path}/$fileName";
    } else {
      var dir = Directory('/storage/emulated/0/Download/AppName');
      print("File Name: ${dir.path}/$fileName");
      return "${dir.path}/$fileName";
    }
  }

第5步:

将文件移动到 Download/AppName 文件夹。

override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
            if (call.method == "saveFile") {
                val hashMap = call.arguments as HashMap<*,*> //Get the arguments as a HashMap
                val path = hashMap["path"]
                FileUtils.saveBitmapToStorage(this@MainActivity as Context,path.toString())
            } else if (call.method == "requestStoragePermission") {
                requestPermission(this@MainActivity as Context)
            } else {
                result.notImplemented()
            }
        }
    }
fun checkPermissionForExternalStorage(context: Context): Boolean {
        return ActivityCompat.checkSelfPermission(
            context, android.Manifest.permission.WRITE_EXTERNAL_STORAGE

        ) === PackageManager.PERMISSION_GRANTED
    }
    fun saveBitmapToStorage(context: Context, bitmap: String): Uri? {
        var result: Uri? = null
        if (checkPermissionForExternalStorage(context)) {
            var filename: File? = null
            val outputStream: java.io.OutputStream?
            val DEFAULT_IMAGE_NAME: String = java.util.UUID.randomUUID().toString()
            try {

/*Check if the android version is equal or greater than Android 10*/
                if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
                    val resolver: ContentResolver = context.getContentResolver()
                    val contentValues = ContentValues()
                    contentValues.put(MediaStore.Audio.Media.DISPLAY_NAME, DEFAULT_IMAGE_NAME)
                    contentValues.put(MediaStore.Audio.Media.TITLE, DEFAULT_IMAGE_NAME)
                    contentValues.put(
                        MediaStore.Audio.Media.MIME_TYPE,
                        getMIMEType(context, bitmap)
                    )
                    contentValues.put(MediaStore.Audio.Media.RELATIVE_PATH, "Music/" + "AppName")
                    val imageUri: Uri? =
                        resolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, contentValues)
                    var file = File(bitmap);

                    outputStream = resolver.openOutputStream(imageUri!!)
                    val bytesArray: ByteArray = file.readBytes()

                    outputStream!!.write(bytesArray)
                    outputStream!!.flush()
                    result = imageUri
                    Log.d("FileUtils", bitmap);
                    SingleMediaScanner(context, file)
                } else {
                }
            } catch (e: java.lang.Exception) {
                Log.d("FileUtils", e.message!!);
                e.printStackTrace()
            }
        }
        return result
    }
fun getMIMEType(con: Context, url: String?): String? {

        var mType: String? = null
        val mExtension = MimeTypeMap.getFileExtensionFromUrl(url)
        if (mExtension != null) {
            mType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(mExtension)
        }
        return mType
    }

第6步:

SingleMediaScanner在 android 目录中创建。

import android.content.Context;
import android.media.MediaScannerConnection;
import android.net.Uri;

import java.io.File;

public class SingleMediaScanner implements MediaScannerConnection.MediaScannerConnectionClient {

    private MediaScannerConnection mMs;
    private File mFile;

    public SingleMediaScanner(Context context, File f) {
        mFile = f;
        mMs = new MediaScannerConnection(context, this);
        mMs.connect();
    }

    @Override
    public void onMediaScannerConnected() {
        mMs.scanFile(mFile.getAbsolutePath(), null);
    }

    @Override
    public void onScanCompleted(String path, Uri uri) {
        mMs.disconnect();
    }

}
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值