Flutter 微信Excel分享到App

1.Flutter部分

構(gòu)建一個類 用于調(diào)用原生方法 代碼如下

class FileShare {
  static const MethodChannel _channel = MethodChannel('YOUR_CHANNEL_NAME');   //channel名字

  /// 獲取分享的文件地址
  static Future<String> getOpenFileUrl() async {
    var path = "";
    path = await _channel.invokeMethod('getOpenFileUrl');  //方法名
    return path;
  }
}

2.iOS原生

2.1配置,在info.plist增加需要打開的文件類型配置,iOS文件配置參考鏈接:鏈接

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
     ...
    <key>CFBundleDocumentTypes</key>
    <array>
        <dict>
            <key>CFBundleTypeName</key>
            <string>Microsoft Excel</string>
            <key>LSHandlerRank</key>
            <string>Alternate</string>
            <key>LSItemContentTypes</key>
            <array>
                <string>com.microsoft.excel.xls</string>
                <string>org.openxmlformats.spreadsheetml.sheet</string>
            </array>
        </dict>
    </array>
</dict>
</plist>

2.2 原生方法獲取文件地址(此處注意Flutter框架會報錯,不過運行是沒有問題的)

import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
    /// 文件路徑 (iOS獲取的是文件在Documents目錄下的路徑存放在Inbox下面)
    var filePath = ""
    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        GeneratedPluginRegistrant.register(with: self)
        let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
        ///調(diào)用的方法 (channel名字與flutter統(tǒng)一)
        let methodChannel = FlutterMethodChannel(name: "YOUR_CHANNEL_NAME", binaryMessenger: controller.binaryMessenger)
        
        methodChannel.setMethodCallHandler { [weak self] (call, result) in
            if "getOpenFileUrl" == call.method {
                result(self?.filePath)
            }
        }
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
    
    override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
        print("完整文件地址為====\(url.path)")
        let completePath = url.path
        var path = ""
        if let range = completePath.range(of: "Documents/") {
            path = String(completePath.suffix(from: range.upperBound))
        }
        self.filePath = path
        print("Documents后面路徑為====\(self.filePath)")
        return true
    }
}

3. 安卓原生(安卓新手,有誤勿怪)

3.1配置,在AndroidManifest.xml增加需要打開的文件類型配置,文件配置參考鏈接:鏈接

...
   <activity
            android:name=".MainActivity"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:exported="true"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
            ...
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="application/vnd.ms-excel" />
                <data android:mimeType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" />
            </intent-filter>

        </activity>
...

3.2 原生方法獲取文件地址

package com.example.device_repair

import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

class MainActivity: FlutterActivity() {
    // TODO: CHANGE METHOD CHANNEL NAME
    private val CHANNEL = "YOUR_CHANNEL_NAME"

    var openPath: String? = null
    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        val channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
        channel.setMethodCallHandler { call, result ->
            if (call.method == "getOpenFileUrl") {
                result.success(openPath)
                openPath = null
            } else {
                result.notImplemented()
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        handleOpenFileUrl(intent)
    }

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        handleOpenFileUrl(intent)
    }

    private fun handleOpenFileUrl(intent: Intent?) {
        var uri: Uri? = intent?.data
        if (uri == null) {
            uri = intent?.getParcelableExtra(Intent.EXTRA_STREAM)
        }

        //獲取文件真實地址
        val filePath: String? = UriUtils.getFileFromUri(activity, uri)
//        val path = intent?.data?.path
        if (filePath != null) {
            openPath = filePath
        }
    }
}


3.2.1 andriod獲取真實路徑 鏈接

package com.example.device_repair

import android.content.ContentResolver
import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.provider.MediaStore
import java.io.*

object UriUtils {
    /**
     * 獲取真實路徑
     *
     * @param context
     */
     fun getFileFromUri(context: Context, uri: Uri?): String? {
        return if (uri == null) {
            null
        } else when (uri.getScheme()) {
            ContentResolver.SCHEME_CONTENT ->                 //Android7.0之后的uri content:// URI
                getFilePathFromContentUri(context, uri)
            ContentResolver.SCHEME_FILE ->                 //Android7.0之前的uri file://
                File(uri.getPath()).getAbsolutePath()
            else -> File(uri.getPath()).getAbsolutePath()
        }
    }

    /**
     * 從uri獲取path
     *
     * @param uri content://media/external/file/109009
     *
     *
     * FileProvider適配
     * content://com.tencent.mobileqq.fileprovider/external_files/storage/emulated/0/Tencent/QQfile_recv/
     * content://com.tencent.mm.external.fileprovider/external/tencent/MicroMsg/Download/
     */
    private fun getFilePathFromContentUri(context: Context, uri: Uri?): String? {
        if (null == uri) return null
        var data: String? = null
        val filePathColumn =
            arrayOf<String>(MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.DISPLAY_NAME)
        val cursor: Cursor? = context.contentResolver.query(uri, filePathColumn, null, null, null)
        if (null != cursor) {
            if (cursor.moveToFirst()) {
                val index: Int = cursor.getColumnIndex(MediaStore.MediaColumns.DATA)
                data = if (index > -1) {
                    cursor.getString(index)
                } else {
                    val nameIndex: Int = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME)
                    val fileName: String = cursor.getString(nameIndex)
                    getPathFromInputStreamUri(context, uri, fileName)
                }
            }
            cursor.close()
        }
        return data
    }

    /**
     * 用流拷貝文件一份到自己APP私有目錄下
     *
     * @param context
     * @param uri
     * @param fileName
     */
    private fun getPathFromInputStreamUri(context: Context, uri: Uri, fileName: String): String? {
        var inputStream: InputStream? = null
        var filePath: String? = null
        if (uri.authority != null) {
            try {
                inputStream = context.contentResolver.openInputStream(uri)
                val file = createTemporalFileFrom(context, inputStream, fileName)
                filePath = file!!.path
            } catch (e: Exception) {
            } finally {
                try {
                    if (inputStream != null) {
                        inputStream.close()
                    }
                } catch (e: Exception) {
                }
            }
        }
        return filePath
    }

    @Throws(IOException::class)
    private fun createTemporalFileFrom(
        context: Context,
        inputStream: InputStream?,
        fileName: String
    ): File? {
        var targetFile: File? = null
        if (inputStream != null) {
            var read: Int
            val buffer = ByteArray(8 * 1024)
            //自己定義拷貝文件路徑
            targetFile = File(context.externalCacheDir, fileName)
            if (targetFile.exists()) {
                targetFile.delete()
            }
            val outputStream: OutputStream = FileOutputStream(targetFile)
            while (inputStream.read(buffer).also { read = it } != -1) {
                outputStream.write(buffer, 0, read)
            }
            outputStream.flush()
            try {
                outputStream.close()
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }
        return targetFile
    }
}

4.如何調(diào)用 (可以根據(jù)path使用path_provider創(chuàng)建file,然后做上傳服務(wù)器后臺之類的操作)

import 'package:flutter/material.dart';
import 'package:file_share/file_share.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({Key key}) : super(key: key);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
  /// 文件路徑 (安卓為全路徑,iOS為沙盒Documents下面的路徑)
  var _filePath = "";

  @override
  void initState() {
    super.initState();
    getOpenFileUrl();

  }

  /// 獲取分享到的excel文件
  getOpenFileUrl() async {
    String url = "";

    url = await FileShare.getOpenFileUrl();
    setState(() {
      _filePath = url;
    });
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    switch (state) {
      case AppLifecycleState.inactive: // 處于這種狀態(tài)的應(yīng)用程序應(yīng)該假設(shè)它們可能在任何時候暫停。
        print("處于這種狀態(tài)的應(yīng)用程序應(yīng)該假設(shè)它們可能在任何時候暫停。");
        break;
      case AppLifecycleState.resumed: // 應(yīng)用程序可見,前臺
        print("應(yīng)用程序可見,前臺");
        getOpenFileUrl();
        break;
      case AppLifecycleState.paused: // 應(yīng)用程序不可見,后臺
        print("應(yīng)用程序不可見,后臺");
        break;
      case AppLifecycleState.detached: // 申請將暫時暫停
        print("申請將暫時暫停");
        break;
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Center(
          child: Text('文件地址為: $_filePath'),
        ),
      ),
    );
  }
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容