mirror of
https://github.com/lifegpc/eh_downloader_flutter.git
synced 2026-06-06 05:49:03 +08:00
feat: 实现了 Android 端写入文件和打开文件
This commit is contained in:
@@ -87,4 +87,6 @@ dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation 'com.github.DylanCaiCoding:MMKV-KTX:1.2.16'
|
||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||
implementation "org.greenrobot:eventbus:3.3.1"
|
||||
implementation "com.anggrayudi:storage:1.5.5"
|
||||
}
|
||||
|
||||
@@ -2,124 +2,41 @@ package com.lifegpc.ehf
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import com.lifegpc.ehf.annotation.ChannelMethod
|
||||
import com.lifegpc.ehf.data.mmkv.SAFSettings
|
||||
import com.lifegpc.ehf.util.ClipboardUtils
|
||||
import com.lifegpc.ehf.util.MethodChannelUtils
|
||||
import com.lifegpc.ehf.eventbus.SAFAuthEvent
|
||||
import com.lifegpc.ehf.platform.ClipboardPlugin
|
||||
import com.lifegpc.ehf.platform.MethodChannelUtils
|
||||
import com.lifegpc.ehf.platform.SAFPlugin
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
|
||||
class MainActivity : FlutterActivity() {
|
||||
private val safAuthorizationCode = 0x10086
|
||||
private var safAuthorizationResult: MethodChannel.Result? = null
|
||||
private var afterAuthSuccess: (() -> Unit)? = null
|
||||
|
||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
MethodChannelUtils.registerMethodChannel(
|
||||
"lifegpc.eh_downloader_flutter/saf",
|
||||
flutterEngine,
|
||||
this
|
||||
SAFPlugin(this)
|
||||
)
|
||||
MethodChannelUtils.registerMethodChannel(
|
||||
"lifegpc.eh_downloader_flutter/clipboard",
|
||||
flutterEngine,
|
||||
ClipboardUtils
|
||||
ClipboardPlugin
|
||||
)
|
||||
}
|
||||
|
||||
@ChannelMethod(responseManually = true)
|
||||
private fun saveFile(
|
||||
channelResult: MethodChannel.Result,
|
||||
filename: String,
|
||||
dir: String,
|
||||
mimeType: String,
|
||||
content: ByteArray
|
||||
) {
|
||||
this.safAuthorizationResult = channelResult
|
||||
if (!checkSafPermission()) {
|
||||
authSAF(channelResult) {
|
||||
doWriteFile(filename, dir, mimeType, content)
|
||||
channelResult.success(null)
|
||||
}
|
||||
} else {
|
||||
doWriteFile(filename, dir, mimeType, content)
|
||||
channelResult.success(null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun doWriteFile(filename: String, dir: String, mimeType: String, content: ByteArray) {
|
||||
var documentDir = DocumentFile.fromTreeUri(this, Uri.parse(SAFSettings.authorizedUri))!!
|
||||
val pathPart = dir.split('/', '\\')
|
||||
pathPart.forEach {
|
||||
if (it.isNotEmpty()) {
|
||||
documentDir = documentDir.createDirectory(it)!!
|
||||
}
|
||||
}
|
||||
|
||||
val filenameWithoutExtension = if (filename.indexOf('.') != -1) {
|
||||
filename.substring(0, filename.lastIndexOf('.'))
|
||||
} else {
|
||||
filename
|
||||
}
|
||||
val file = documentDir.createFile(mimeType, filenameWithoutExtension)!!
|
||||
val uri = file.uri
|
||||
contentResolver.openOutputStream(uri)!!.use {
|
||||
it.write(content)
|
||||
}
|
||||
}
|
||||
|
||||
private fun authSAF(result: MethodChannel.Result, onSuccess: (() -> Unit)? = null) {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||
startActivityForResult(intent, safAuthorizationCode)
|
||||
safAuthorizationResult = result
|
||||
this.afterAuthSuccess = onSuccess
|
||||
}
|
||||
|
||||
private fun onSafAuthSuccess(data: Intent) {
|
||||
// 保存权限
|
||||
val resultData = data.data!!
|
||||
val takeFlags =
|
||||
(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
contentResolver.takePersistableUriPermission(resultData, takeFlags)
|
||||
SAFSettings.authorizedUri = resultData.toString()
|
||||
}
|
||||
|
||||
private fun checkSafPermission(): Boolean {
|
||||
val dir = SAFSettings.authorizedUri
|
||||
if (dir.isBlank()) return false
|
||||
|
||||
val uri = Uri.parse(dir)
|
||||
|
||||
return try {
|
||||
val flags =
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
contentResolver.takePersistableUriPermission(uri, flags)
|
||||
DocumentFile.fromTreeUri(this, uri)
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
when (requestCode) {
|
||||
safAuthorizationCode -> {
|
||||
SAFPlugin.safAuthorizationCode -> {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
// 授权成功
|
||||
onSafAuthSuccess(data!!)
|
||||
afterAuthSuccess?.invoke()
|
||||
EventBus.getDefault().post(SAFAuthEvent(true, data!!))
|
||||
} else {
|
||||
// 授权失败
|
||||
safAuthorizationResult?.error("Permission denied", null, null)
|
||||
safAuthorizationResult = null
|
||||
EventBus.getDefault().post(SAFAuthEvent(false, null))
|
||||
}
|
||||
afterAuthSuccess = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.lifegpc.ehf.eventbus
|
||||
|
||||
import android.content.Intent
|
||||
|
||||
data class SAFAuthEvent(
|
||||
val success: Boolean = true,
|
||||
val data: Intent?,
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.lifegpc.ehf.util
|
||||
package com.lifegpc.ehf.platform
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
@@ -12,7 +12,7 @@ import com.lifegpc.ehf.annotation.ChannelMethod
|
||||
import java.io.File
|
||||
import java.util.UUID
|
||||
|
||||
object ClipboardUtils {
|
||||
object ClipboardPlugin {
|
||||
private const val AUTHORITY = "com.lifegpc.ehf.ClipboardImageProvider"
|
||||
|
||||
@ChannelMethod
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.lifegpc.ehf.util
|
||||
package com.lifegpc.ehf.platform
|
||||
|
||||
import android.util.Log
|
||||
import com.lifegpc.ehf.annotation.ChannelMethod
|
||||
@@ -0,0 +1,190 @@
|
||||
package com.lifegpc.ehf.platform
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import com.anggrayudi.storage.file.makeFile
|
||||
import com.anggrayudi.storage.file.openOutputStream
|
||||
import com.lifegpc.ehf.MainActivity
|
||||
import com.lifegpc.ehf.annotation.ChannelMethod
|
||||
import com.lifegpc.ehf.data.mmkv.SAFSettings
|
||||
import com.lifegpc.ehf.eventbus.SAFAuthEvent
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
|
||||
class SAFPlugin(private val activity: MainActivity) {
|
||||
private var safAuthorizationResult: MethodChannel.Result? = null
|
||||
private val fdMap = mutableMapOf<Int, DocumentFile>()
|
||||
private var onSAFAuthSuccess: (() -> Unit)? = null
|
||||
private var onSAFAuthFailed: (() -> Unit)? = null
|
||||
|
||||
companion object {
|
||||
const val safAuthorizationCode = 0x10086
|
||||
}
|
||||
|
||||
init {
|
||||
EventBus.getDefault().register(this)
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
@Suppress("unused")
|
||||
fun onSAFAuthResult(event: SAFAuthEvent) {
|
||||
if (!event.success) {
|
||||
onSAFAuthFailed?.invoke()
|
||||
onSAFAuthFailed = null
|
||||
return
|
||||
}
|
||||
|
||||
val data = event.data!!
|
||||
val authUri = data.data!!
|
||||
saveSAFPermission(authUri)
|
||||
onSAFAuthSuccess?.invoke()
|
||||
onSAFAuthSuccess = null
|
||||
}
|
||||
|
||||
@ChannelMethod(responseManually = true)
|
||||
@Suppress("unused")
|
||||
private fun saveFile(
|
||||
channelResult: MethodChannel.Result,
|
||||
filename: String,
|
||||
dir: String,
|
||||
mimeType: String,
|
||||
content: ByteArray
|
||||
) {
|
||||
this.safAuthorizationResult = channelResult
|
||||
if (!checkSafPermission()) {
|
||||
onSAFAuthSuccess = {
|
||||
doWriteFile(filename, dir, mimeType, content)
|
||||
channelResult.success(null)
|
||||
}
|
||||
onSAFAuthFailed = {
|
||||
channelResult.error("Permission denied", null, null)
|
||||
}
|
||||
authSAF()
|
||||
} else {
|
||||
doWriteFile(filename, dir, mimeType, content)
|
||||
channelResult.success(null)
|
||||
}
|
||||
}
|
||||
|
||||
@ChannelMethod(responseManually = true)
|
||||
@Suppress("unused")
|
||||
private fun openFile(
|
||||
channelResult: MethodChannel.Result,
|
||||
filenameWithoutExtension: String,
|
||||
dir: String,
|
||||
mimeType: String,
|
||||
) {
|
||||
if (!checkSafPermission()) {
|
||||
onSAFAuthSuccess = {
|
||||
channelResult.success(doOpenFile(filenameWithoutExtension, mimeType, dir))
|
||||
}
|
||||
onSAFAuthFailed = {
|
||||
channelResult.error("Permission denied", null, null)
|
||||
}
|
||||
authSAF()
|
||||
} else {
|
||||
channelResult.success(doOpenFile(filenameWithoutExtension, mimeType, dir))
|
||||
}
|
||||
}
|
||||
|
||||
private fun doOpenFile(filenameWithoutExtension: String, mimeType: String, dir: String): Int {
|
||||
var documentFile =
|
||||
DocumentFile.fromTreeUri(activity, Uri.parse(SAFSettings.authorizedUri))!!
|
||||
documentFile = createFileRecursively(documentFile, dir)
|
||||
|
||||
val f = documentFile.makeFile(activity, filenameWithoutExtension, mimeType)!!
|
||||
|
||||
val id = f.hashCode()
|
||||
fdMap[id] = f
|
||||
return id
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入文件
|
||||
* @param id Int
|
||||
* @param bytes ByteArray
|
||||
* @return Int 写入的字节数
|
||||
*/
|
||||
@ChannelMethod
|
||||
@Suppress("unused")
|
||||
private fun writeFile(id: Int, bytes: ByteArray, append: Boolean): Int {
|
||||
val f = fdMap[id]!!
|
||||
f.openOutputStream(activity, append)!!.use {
|
||||
it.write(bytes)
|
||||
}
|
||||
return bytes.size
|
||||
}
|
||||
|
||||
@ChannelMethod
|
||||
@Suppress("unused")
|
||||
private fun closeFile(id: Int) {
|
||||
fdMap.remove(id)
|
||||
}
|
||||
|
||||
private fun createFileRecursively(base: DocumentFile, dir: String): DocumentFile {
|
||||
var file = base
|
||||
val pathPart = dir.split('/', '\\')
|
||||
pathPart.forEach {
|
||||
if (it.isNotEmpty()) {
|
||||
file = file.createDirectory(it)!!
|
||||
}
|
||||
}
|
||||
return file
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入文件
|
||||
* @param filename String
|
||||
* @param dir String
|
||||
* @param mimeType String
|
||||
* @param content ByteArray
|
||||
*/
|
||||
private fun doWriteFile(filename: String, dir: String, mimeType: String, content: ByteArray) {
|
||||
var documentDir = DocumentFile.fromTreeUri(activity, Uri.parse(SAFSettings.authorizedUri))!!
|
||||
documentDir = createFileRecursively(documentDir, dir)
|
||||
|
||||
val filenameWithoutExtension = if (filename.indexOf('.') != -1) {
|
||||
filename.substring(0, filename.lastIndexOf('.'))
|
||||
} else {
|
||||
filename
|
||||
}
|
||||
val file = documentDir.createFile(mimeType, filenameWithoutExtension)!!
|
||||
val uri = file.uri
|
||||
activity.contentResolver.openOutputStream(uri)!!.use {
|
||||
it.write(content)
|
||||
}
|
||||
}
|
||||
|
||||
private fun authSAF() {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||
activity.startActivityForResult(intent, safAuthorizationCode)
|
||||
}
|
||||
|
||||
// 保存权限
|
||||
private fun saveSAFPermission(uri: Uri) {
|
||||
val takeFlags =
|
||||
(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
activity.contentResolver.takePersistableUriPermission(uri, takeFlags)
|
||||
SAFSettings.authorizedUri = uri.toString()
|
||||
}
|
||||
|
||||
private fun checkSafPermission(): Boolean {
|
||||
val dir = SAFSettings.authorizedUri
|
||||
if (dir.isBlank()) return false
|
||||
|
||||
val uri = Uri.parse(dir)
|
||||
|
||||
return try {
|
||||
val flags =
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
activity.contentResolver.takePersistableUriPermission(uri, flags)
|
||||
DocumentFile.fromTreeUri(activity, uri)
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user