我来为您详细介绍Android系统应用中的文件管理开发。
## 1. 基础权限配置
### 在 AndroidManifest.xml 中添加必要权限
```xml
<!-- 读写存储权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- Android 11+ 需要 -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<!-- 访问所有文件 -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
```
## 2. 动态权限请求
### Android 6.0+ 需要动态请求权限
```kotlin
class FileManagerActivity : AppCompatActivity() {
private val PERMISSION_REQUEST_CODE = 100
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
checkAndRequestPermissions()
}
private fun checkAndRequestPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// Android 11+ 处理
if (!Environment.isExternalStorageManager()) {
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
intent.data = Uri.parse("package:$packageName")
startActivity(intent)
}
} else {
// Android 10 及以下
val permissions = arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
if (!hasPermissions(permissions)) {
ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST_CODE)
}
}
}
private fun hasPermissions(permissions: Array<String>): Boolean {
return permissions.all {
ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED
}
}
}
```
## 3. 文件操作工具类
```kotlin
object FileUtils {
// 获取文件列表
fun getFileList(path: String): List<FileModel> {
val fileList = mutableListOf<FileModel>()
val directory = File(path)
if (directory.exists() && directory.isDirectory) {
directory.listFiles()?.forEach { file ->
fileList.add(FileModel(
name = file.name,
path = file.absolutePath,
isDirectory = file.isDirectory,
size = if (file.isFile) file.length() else 0,
lastModified = file.lastModified(),
extension = file.extension
))
}
}
// 排序:文件夹在前,文件在后
return fileList.sortedWith(compareBy({ !it.isDirectory }, { it.name.lowercase() }))
}
// 复制文件
fun copyFile(source: File, destination: File): Boolean {
return try {
source.copyTo(destination, overwrite = true)
true
} catch (e: Exception) {
e.printStackTrace()
false
}
}
// 移动文件
fun moveFile(source: File, destination: File): Boolean {
return try {
source.renameTo(destination)
} catch (e: Exception) {
e.printStackTrace()
false
}
}
// 删除文件或文件夹
fun deleteFile(file: File): Boolean {
return if (file.isDirectory) {
file.deleteRecursively()
} else {
file.delete()
}
}
// 创建文件夹
fun createDirectory(path: String, name: String): Boolean {
val newDir = File(path, name)
return newDir.mkdirs()
}
// 重命名文件
fun renameFile(file: File, newName: String): Boolean {
val newFile = File(file.parent, newName)
return file.renameTo(newFile)
}
// 获取文件大小(格式化)
fun getFormattedFileSize(size: Long): String {
val units = arrayOf("B", "KB", "MB", "GB", "TB")
var fileSize = size.toDouble()
var unitIndex = 0
while (fileSize >= 1024 && unitIndex < units.size - 1) {
fileSize /= 1024
unitIndex++
}
return String.format("%.2f %s", fileSize, units[unitIndex])
}
// 获取文件MIME类型
fun getMimeType(file: File): String {
val extension = MimeTypeMap.getFileExtensionFromUrl(file.absolutePath)
return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) ?: "*/*"
}
}
```
## 4. 文件数据模型
```kotlin
data class FileModel(
val name: String,
val path: String,
val isDirectory: Boolean,
val size: Long,
val lastModified: Long,
val extension: String,
var isSelected: Boolean = false
) {
val formattedSize: String
get() = FileUtils.getFormattedFileSize(size)
val formattedDate: String
get() = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()).format(Date(lastModified))
}
```
## 5. 文件列表适配器
```kotlin
class FileListAdapter(
private val onItemClick: (FileModel) -> Unit,
private val onItemLongClick: (FileModel) -> Boolean
) : RecyclerView.Adapter<FileListAdapter.FileViewHolder>() {
private var fileList = mutableListOf<FileModel>()
var isMultiSelectMode = false
fun updateList(newList: List<FileModel>) {
fileList.clear()
fileList.addAll(newList)
notifyDataSetChanged()
}
inner class FileViewHolder(private val binding: ItemFileBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(file: FileModel) {
binding.apply {
tvFileName.text = file.name
tvFileInfo.text = if (file.isDirectory) {
"文件夹"
} else {
"${file.formattedSize} • ${file.formattedDate}"
}
// 设置图标
ivFileIcon.setImageResource(
if (file.isDirectory) {
R.drawable.ic_folder
} else {
getFileIcon(file.extension)
}
)
// 多选模式显示复选框
cbSelect.visibility = if (isMultiSelectMode) View.VISIBLE else View.GONE
cbSelect.isChecked = file.isSelected
root.setOnClickListener {
if (isMultiSelectMode) {
file.isSelected = !file.isSelected
notifyItemChanged(adapterPosition)
} else {
onItemClick(file)
}
}
root.setOnLongClickListener {
onItemLongClick(file)
}
}
}
private fun getFileIcon(extension: String): Int {
return when (extension.lowercase()) {
"jpg", "jpeg", "png", "gif", "bmp" -> R.drawable.ic_image
"mp4", "avi", "mkv", "mov" -> R.drawable.ic_video
"mp3", "wav", "flac", "aac" -> R.drawable.ic_audio
"pdf" -> R.drawable.ic_pdf
"doc", "docx" -> R.drawable.ic_word
"xls", "xlsx" -> R.drawable.ic_excel
"ppt", "pptx" -> R.drawable.ic_ppt
"txt" -> R.drawable.ic_text
"zip", "rar", "7z" -> R.drawable.ic_zip
"apk" -> R.drawable.ic_apk
else -> R.drawable.ic_file
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FileViewHolder {
val binding = ItemFileBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
return FileViewHolder(binding)
}
override fun onBindViewHolder(holder: FileViewHolder, position: Int) {
holder.bind(fileList[position])
}
override fun getItemCount() = fileList.size
fun getSelectedFiles(): List<FileModel> {
return fileList.filter { it.isSelected }
}
}
```
## 6. 文件管理主界面
```kotlin
class FileManagerFragment : Fragment() {
private lateinit var binding: FragmentFileManagerBinding
private lateinit var fileAdapter: FileListAdapter
private var currentPath = Environment.getExternalStorageDirectory().absolutePath
private val pathStack = Stack<String>()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = FragmentFileManagerBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupRecyclerView()
setupToolbar()
loadFiles(currentPath)
}
private fun setupRecyclerView() {
fileAdapter = FileListAdapter(
onItemClick = { file ->
if (file.isDirectory) {
pathStack.push(currentPath)
currentPath = file.path
loadFiles(currentPath)
} else {
openFile(file)
}
},
onItemLongClick = { file ->
showFileOptionsDialog(file)
true
}
)
binding.recyclerView.apply {
layoutManager = LinearLayoutManager(context)
adapter = fileAdapter
addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
}
}
private fun setupToolbar() {
binding.toolbar.setNavigationOnClickListener {
onBackPressed()
}
binding.toolbar.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) {
R.id.action_create_folder -> showCreateFolderDialog()
R.id.action_sort -> showSortDialog()
R.id.action_select_all -> selectAllFiles()
}
true
}
}
private fun loadFiles(path: String) {
lifecycleScope.launch(Dispatchers.IO) {
val files = FileUtils.getFileList(path)
withContext(Dispatchers.Main) {
fileAdapter.updateList(files)
binding.tvCurrentPath.text = path
binding.tvEmptyView.visibility = if (files.isEmpty()) View.VISIBLE else View.GONE
}
}
}
private fun openFile(file: FileModel) {
val intent = Intent(Intent.ACTION_VIEW)
val uri = FileProvider.getUriForFile(
requireContext(),
"${requireContext().packageName}.fileprovider",
File(file.path)
)
intent.setDataAndType(uri, FileUtils.getMimeType(File(file.path)))
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
try {
startActivity(intent)
} catch (e: ActivityNotFoundException) {
Toast.makeText(context, "没有应用可以打开此文件", Toast.LENGTH_SHORT).show()
}
}
private fun showFileOptionsDialog(file: FileModel) {
val options = arrayOf("复制", "剪切", "重命名", "删除", "属性")
AlertDialog.Builder(requireContext())
.setTitle(file.name)
.setItems(options) { _, which ->
when (which) {
0 -> copyFile(file)
1 -> cutFile(file)
2 -> showRenameDialog(file)
3 -> showDeleteConfirmDialog(file)
4 -> showFileProperties(file)
}
}
.show()
}
private fun showCreateFolderDialog() {
val editText = EditText(context)
editText.hint = "文件夹名称"
AlertDialog.Builder(requireContext())
.setTitle("新建文件夹")
.setView(editText)
.setPositiveButton("创建") { _, _ ->
val folderName = editText.text.toString().trim()
if (folderName.isNotEmpty()) {
if (FileUtils.createDirectory(currentPath, folderName)) {
loadFiles(currentPath)
Toast.makeText(context, "文件夹创建成功", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, "文件夹创建失败", Toast.LENGTH_SHORT).show()
}
}
}
.setNegativeButton("取消", null)
.show()
}
private fun onBackPressed() {
if (pathStack.isNotEmpty()) {
currentPath = pathStack.pop()
loadFiles(currentPath)
} else {
requireActivity().onBackPressed()
}
}
}
```
## 7. FileProvider 配置
### 在 AndroidManifest.xml 中添加
```xml
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
```
### 创建 res/xml/file_paths.xml
```xml
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path name="external_files" path="." />
<files-path name="files" path="." />
<cache-path name="cache" path="." />
</paths>
```
这个文件管理系统提供了基本的文件浏览、复制、移动、删除、重命名等功能。您可以根据需要添加更多功能,如文件搜索、压缩解压、文件分享等。