react-native 原生选择文件

820 阅读3分钟

小知识,大挑战!本文正在参与“  程序员必备小知识  ”创作活动

以下分别介绍react-native选择文件功能,原生的支持,通过桥接文件,进行RN与原生的交互,来实现获取文件操作

iOS端选择文件功能

iOS端原生实现选择文件使用的是UIDocumentPickerViewController这个库。 具体代码如下:

class FilePickerUtil: NSObjectUIDocumentPickerDelegate {
    func show(){

    let documentTypes = ["public.content",

                             "public.text",

                             "public.source-code",

                             "public.image",

                             "public.audiovisual-content",

                             "com.adobe.pdf",

                             "com.apple.keynote.key",

                             "com.microsoft.word.doc",

                             "com.microsoft.excel.xls",

                             "com.microsoft.powerpoint.ppt"]


    let document = UIDocumentPickerViewController.init(documentTypes: documentTypes, in: .open)

    document.delegate = self

    document.allowsMultipleSelection = **true**

    UIApplication.shared.keyWindow?.rootViewController?.present(document, animated: true, completion: **nil**)

  }
}

其中UIDocumentPickerDelegate代理方法。

func documentPickerWasCancelled( _controller: UIDocumentPickerViewController) {
    controller.dismiss(animated: true, completion: nil)
}
func documentPicker( _controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
    for url in urls {
      do {
          let resources = **try** url.resourceValues(forKeys:[.fileSizeKey])

          let fileSize = resources.fileSize!

          print("文件大小:" + String(fileSize))
          print(fileSize != 0);
         let mediaItem = [

            "path": url.absoluteString,

            "type": "file",

            "name": url.lastPathComponent,

            "fileSize": fileSize != 0 ? Double(fileSize) / 1024.0 / 1024.0 : 0

          ] as [String : Any]
          print("文件路径:" + url.absoluteString)

      } catch {
        print( "文件获取失败")
      }
    }
    controller.dismiss(animated: true, completion: nil)
  }

以上在模拟器上没有问题,真机上出现问题,1,没有权限,2,无法预览。解决如下: 如果想copy文件到手机其他位置:

let isSecuredURL = url.startAccessingSecurityScopedResource()
        
let resources = try url.resourceValues(forKeys:[.fileSizeKey])
         
let filePath =   documentPath(subFolder:"file").appendingPathComponent(url.lastPathComponent).path;
let fileData = NSData.init(contentsOf: url);
fileData?.write(toFile: filePath, atomically: true)
if (isSecuredURL) {
    url.stopAccessingSecurityScopedResource()
}
/// 返回文档路径
  func documentPath(subFolder: String?) -> URL {
    
    let url = URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(
      FileManager.SearchPathDirectory.documentDirectory,
      FileManager.SearchPathDomainMask.userDomainMask, true
    ).first!)
    if (subFolder == nil) {
      return url
    }
    let result = url.appendingPathComponent(subFolder!)
    
    // 没有文件夹,尝试创建文件夹
    if (!FileManager.default.fileExists(atPath: result.path)) { 
      do {
        try FileManager.default.createDirectory(at: result, withIntermediateDirectories: true, attributes: nil)
      } catch {
        return url
      }
    }
    
    return result
  }

android端选择文件功能

使用Intent进行选择文件功能,具体使用代码如下:MEDIA_REQUEST_FILEPICK为自己定义的值,用于在结果中判断回调

private var tag = "FilePickerUtil"
   private val DOC = "application/msword"
   private val XLS = "application/vnd.ms-excel"
   private val PPT = "application/vnd.ms-powerpoint"
   private val XLS1 = "application/x-excel"
   private val DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
   private val XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
   private val PPTX = "application/vnd.openxmlformats-officedocument.presentationml.presentation"
   private val PDF = "application/pdf"
   
   val activity = context.currentActivity
       val intent = Intent(Intent.ACTION_GET_CONTENT)

       intent.addCategory(Intent.CATEGORY_OPENABLE)
       intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE,true)
       intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
       val mimeTypes = arrayOf(DOC, DOCX, PDF, PPT, PPTX, XLS, XLS1, XLSX)
       intent.type = "application/*"

       intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes)
       activity?.startActivityForResult(intent, MEDIA_REQUEST_FILEPICK)

得到的结果在onActivityResult方法里面获取

public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
 // 选文件
    if (requestCode == Constant.MEDIA_REQUEST_FILEPICK) {
      //文件信息
      result = this.getFileResult(context, data);
    }
}

统一调用方法:

fun getFileResult(context: ReactContext, data: Intent?): WritableArray {

        val result = Arguments.createArray()

        if (data == null) {
            return result
        }
        if (data.clipData != null) {
            //多个文件
            val count: Int = data.clipData!!.itemCount

            var currentIndex = 0;
            while (currentIndex < count) {
                result.pushMap(getFileInfo(context, data.clipData!!.getItemAt(currentIndex).uri));
                currentIndex += 1
            }

        } else if (data.data != null) {
            result.pushMap(getFileInfo(context, data.data!!));
        }
        return result
    }

getFileInfo 是获取对象的地址,这块做的比较复杂的地方是将选择到的文件地址copy到指定文件中去,方便以后进行上传操作 方法如下:其中FileUtil为公共类。

 private fun getFileInfo(context: ReactContext, uri: Uri): WritableMap? {
        val map = Arguments.createMap()
        val fileInfo: Map<String, String> = FileUtil.getFileInfoByUri(context, uri) as Map<String, String>
        var fileSize: Double? = fileInfo["size"]?.toDouble()
        var fileName: String? = fileInfo["filename"];
        var filePath = "";
        if (fileSize != null && fileSize > 25 * 1024 * 1024) {
            // 文件太大不允许copy
            map.putDouble("fileSize", fileSize)
            map.putString("type", "file")
            map.putString("name", fileName);
            return map;
        }
        if (fileName!= null) {
            var extension: String = fileName!!.split('.')[1];
            filePath = FileUtil.saveOriginalFile(context, uri, extension)
        }

        map.putString("path", filePath)
        map.putString("type", "file")
        map.putString("name", fileName);
        if (fileSize != null) {
            map.putDouble("fileSize", fileSize)
        };

        return map
    }

其中FileUtil涉及到的方法如下:

 /**
   * 获取文件信息 包括文件名,文件大小
   * @param uri uri
   * @return
   */
  public static Map getFileInfoByUri(Context context, Uri uri) {
    String filename = "";
    String size = "";
    Map<String,String> mMap = new HashMap<String,String>();
    Cursor returnCursor = context.getContentResolver().query(uri, null,
            null, null, null);
    if (returnCursor != null) {
      int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
      returnCursor.moveToFirst();
      filename = returnCursor.getString(nameIndex);
      mMap.put("filename", filename);
      int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);
      returnCursor.moveToFirst();
      size = returnCursor.getString(sizeIndex);
      mMap.put("size", size.toString());
      returnCursor.close();
    }
    return mMap;
  }
/**
   * copy文件到指定存储位置
   * @param uri uri
   * @return
   */
  public static String saveOriginalFile(Context ctx, Uri uri, String extension) {
    try {
      String path = FileUtil.externalPath(ctx, "Files").getAbsolutePath() + '/' +  UUID.randomUUID().toString() + '.' + extension;
      FileUtil.copy(ctx, uri, path);
      return path;
    } catch (Exception exception) {
      Logger.e(TAG, exception);
      return "";
    }
  }
  
    /**
   * 返回文档路径,子文件夹不存在会创建
   *
   * @param context   context
   * @param subFolder subFolder
   * @return file
   */
  public static File externalPath(Context context, String subFolder) {

    // 不指定 type,使用 files 根目录
    File folder = context.getExternalFilesDir("");
    if (subFolder == null) {
      return folder;
    }

    File full = new File(folder, subFolder);
    if (!full.exists()) {
      boolean success = full.mkdirs();
      if (!success) {
        return folder;
      }
    }

    return full;
  }
  
  /**
   * 根据Uri拷贝文件
   *
   * @param context Context
   * @param uri     Uri
   * @param dst     String destination
   * @throws IOException exception
   */
  public static void copy(Context context, Uri uri, String dst) throws IOException {

    try (AssetFileDescriptor fileDescriptor = getDescriptorFromUri(context, uri)) {
      if (fileDescriptor == null) {
        throw new IOException("Copy from uri failed: open descriptor failed");
      }
      FileInputStream inStream = fileDescriptor.createInputStream();
      FileOutputStream outStream = new FileOutputStream(dst);
      FileChannel inChannel = inStream.getChannel();
      FileChannel outChannel = outStream.getChannel();
      inChannel.transferTo(0, inChannel.size(), outChannel);
      inStream.close();
      outStream.close();
    } catch (IOException e) {
     // 如果出现了IO异常或读取文件描述符异常,则尝试使用从路径中读写文件
    } catch (Exception e) {
      throw e;
    }
  }

以上就是选择文件的全部内容,具体怎么上传到服务器,这个就不多说啦,因为路径已经可以拿到啦,剩下的很简单了。