将文件存储在 Flutter 中的 Android 101112+ 下载文件夹中。

Posted 小陈乱敲代码

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了将文件存储在 Flutter 中的 Android 101112+ 下载文件夹中。相关的知识,希望对你有一定的参考价值。

在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();
    


以上是关于将文件存储在 Flutter 中的 Android 101112+ 下载文件夹中。的主要内容,如果未能解决你的问题,请参考以下文章

Flutter Web 不显示 Firebase 存储中的 NetworkImages,但在 android 模拟器上完美运行

Flutter Android 11 权限,例如 WhatsApp

如何将csv文件保存到flutter中的内部存储下载文件夹中?

Flutter firebase 存储插件需要大量时间来上传文件

Bintray 502 的 Android 存储库 Flutter TensorFlow-lite 中的问题

如何在 Flutter 中等待文件上传到 Firebase 存储?