最近一直在学习Compose知识,花了些时间用Compose Desktop写了一款公司内部使用的小工具,文件管理器只是其中一项功能。我将其分离出来,先看最终实现效果
效果是不是改可以,对比Android Studio自带的Device Explorer优化了导入功能,支持多文件拖拽文件导入,增加了直接编辑保存文本功能。
实现思路
所有实现操作均通过Adb命令,通过代码Runtime.getRuntime().exec()执行
一、文件列表查询
C:\Users\Administrator>adb shell ls -l -p / | sort
dr-xr-xr-x 17 root root 0 1970-01-01 08:00 sys/
dr-xr-xr-x 1004 root root 0 1970-01-01 08:00 proc/
drwx--x--- 4 shell everybody 80 1972-01-02 15:51 storage/
drwxr-xr-x 1 root root 1024 2023-11-23 00:36 system/
drwxr-xr-x 2 root root 27 2009-01-01 08:00 acct/
drwxr-xr-x 2 root root 27 2009-01-01 08:00 debug_ramdisk/
drwxr-xr-x 2 root root 27 2009-01-01 08:00 oem/
lrw-r--r-- 1 root root 17 2009-01-01 08:00 d -> /sys/kernel/debug
lrw-r--r-- 1 root root 21 2009-01-01 08:00 sdcard -> /storage/self/primary
...
total 86
由于打印文件太长,我这里做了部分删减,下面我来简单分析这组信息,如:dr-xr-xr-x,首字母d代表他是一个目录,如果l代表是一个链接文件,可以理解为windows快捷方式,如lrw-r--r-- 1 root root 17 2009-01-01 08:00 d -> /sys/kernel/debug,实际跳转的路径是/sys/kernel/debug,而不是d,这个地方要处理如果首字母是-测代表是是一个文件,通过以上命令就实现了文件管理器核心的功能文件显示
二、文件编辑
如图,我进入到了
/Storage/emulated/0/Download路径下,我要对以上文件编辑,那么我知道该文件的路径是:/Storage/emulated/0/Download/aa.txt,通过
C:\Users\Administrator>adb shell cat /storage/emulated/0/Download/aa.txt
123444ww454545
我们可以得到文件的内容是123444ww454545,然后我们用代码吧UI画出来
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun FileEditUI(
adbDevicePoller: AdbDevicePoller,
scope: CoroutineScope,
dirList: List<String>,
currentFileName: String,
editDialog: Boolean,
editString: String,
editCommandCallback: () -> Unit
) {
var edit by remember { mutableStateOf("") }
edit = editString
if (editDialog) {
val bringIntoViewRequester = remember { BringIntoViewRequester() }
val scrollState = rememberScrollState()
Window(state = rememberWindowState(width = 600.dp, height = 800.dp),
title = "编辑", onCloseRequest = {
editCommandCallback.invoke()
}) {
Box {
BasicTextField(value = edit, onValueChange = {
edit = it
}, modifier = Modifier
.fillMaxSize()
.verticalScroll(scrollState)
.bringIntoViewRequester(bringIntoViewRequester)
.onFocusChanged {
if (it.isFocused) {
scope.launch {
bringIntoViewRequester.bringIntoView()
}
}
}
)
VerticalScrollbar(
modifier = Modifier.align(Alignment.CenterEnd),
adapter = rememberScrollbarAdapter(scrollState)
)
Button(
modifier = Modifier.padding(bottom = 20.dp, end = 30.dp)
.wrapContentSize()
.align(Alignment.BottomEnd),
onClick = {
val escapedJsonString = edit.replace(""", "\"")
scope.launch {
val dir = dirList.joinToString("/")
adbDevicePoller.exec(
"""shell "cat > /${dir}/${currentFileName}" <<< '$escapedJsonString'"""
) {
editCommandCallback.invoke()
}
}
}) {
Text("保存")
}
}
}
}
}
通过以上代码可以看到,我这里点击保存执行了
adbDevicePoller.exec("""shell "cat > /${dir}/${currentFileName}" <<< '$escapedJsonString'""")
比如我想对刚刚的文件重新写入个0进去,只需执行
adb shell "cat > /Storage/emulated/0/Download/aa.txt" <<< '0'"
三、文件拖拽导入实现
data class DropBoundsBean(
var x: Float = 0f,
var y: Float = 0f,
var width: Int = 600.dp.value.toInt(),
var height: Int = 480.dp.value.toInt(),
)
@Composable
fun DropBoxPanel(
modifier: Modifier,
window: ComposeWindow,
component: JPanel = JPanel(),
onFileDrop: (List<String>) -> Unit
) {
val dropBoundsBean = remember {
mutableStateOf(DropBoundsBean())
}
Box(
modifier = modifier.onPlaced {
dropBoundsBean.value = DropBoundsBean(
x = it.positionInWindow().x,
y = it.positionInWindow().y,
width = it.size.width,
height = it.size.height
)
}) {
LaunchedEffect(true) {
component.setBounds(
dropBoundsBean.value.x.roundToInt(),
dropBoundsBean.value.y.roundToInt(),
dropBoundsBean.value.width,
dropBoundsBean.value.height
)
window.contentPane.add(component)
object : DropTarget(component, object : DropTargetAdapter() {
override fun drop(event: DropTargetDropEvent) {
event.acceptDrop(DnDConstants.ACTION_REFERENCE)
val dataFlavors = event.transferable.transferDataFlavors
println(dataFlavors)
dataFlavors.forEach {
if (it == DataFlavor.javaFileListFlavor) {
val list = event.transferable.getTransferData(it) as List<*>
val pathList = mutableListOf<String>()
list.forEach { filePath ->
pathList.add(filePath.toString())
}
onFileDrop(pathList)
}
}
event.dropComplete(true)
}
}) {
// dragExit
}
}
SideEffect {
component.setBounds(
dropBoundsBean.value.x.roundToInt(),
dropBoundsBean.value.y.roundToInt(),
dropBoundsBean.value.width,
dropBoundsBean.value.height
)
}
DisposableEffect(true) {
onDispose {
window.contentPane.remove(component)
}
}
}
}
四、其他
// 文件导入
adb push [targetPath] [originPath]
// 文件导出
adb pull [originPath] [targetPath]
// 文件夹创建
adb shell mkdir [originPath/dirName]
总结
本文通过对adb命令的应用,列举了在开发遇到的一些痛点,提前总结出来,文章中所有代码已经开源到Github中,第一次分享,如果对文章有疑问或者存在错误,欢迎在评论区探讨!