我來為您詳細介紹Android系統(tǒng)應用中的文件管理開發(fā)。
## 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. 動態(tài)權限請求
### Android 6.0+ 需要動態(tài)請求權限
```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()
? ? ? ? }
? ? }
? ? // 創(chuàng)建文件夾
? ? 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)
? ? }
? ? // 獲取文件大?。ǜ袷交?/p>
? ? 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("創(chuàng)建") { _, _ ->
? ? ? ? ? ? ? ? val folderName = editText.text.toString().trim()
? ? ? ? ? ? ? ? if (folderName.isNotEmpty()) {
? ? ? ? ? ? ? ? ? ? if (FileUtils.createDirectory(currentPath, folderName)) {
? ? ? ? ? ? ? ? ? ? ? ? loadFiles(currentPath)
? ? ? ? ? ? ? ? ? ? ? ? Toast.makeText(context, "文件夾創(chuàng)建成功", Toast.LENGTH_SHORT).show()
? ? ? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? ? ? Toast.makeText(context, "文件夾創(chuàng)建失敗", 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>
```
### 創(chuàng)建 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>
```
這個文件管理系統(tǒng)提供了基本的文件瀏覽、復制、移動、刪除、重命名等功能。您可以根據需要添加更多功能,如文件搜索、壓縮解壓、文件分享等。