package com.khronos.vulkan_samples; import android.content.Context; import android.content.res.AssetManager; import android.os.Environment; import android.util.Log; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.util.HashSet; import java.util.Set; public class AssetsCopyUtil { private static final String TAG = "AssetsCopyUtil"; private static final String PREF_NAME = "assets_copy_pref"; private static final String PREF_KEY_COPIED_FILES = "copied_files"; /** * 复制 assets 到应用专属外部存储的 files 目录(递归复制子文件夹) */ public static void copyAssetsToAppFiles(Context context, String assetsPath) { try { AssetManager assetManager = context.getAssets(); // 获取应用专属外部存储的 files 目录 File externalFilesDir = context.getExternalFilesDir(null); if (externalFilesDir == null) { Log.e(TAG, "External files directory is not available"); return; } // 递归复制 copyAssetsRecursive(context, assetManager, assetsPath, externalFilesDir.getAbsolutePath()); Log.i(TAG, "Assets copy completed for: " + assetsPath); } catch (Exception e) { Log.e(TAG, "Error copying assets from: " + assetsPath, e); } } /** * 递归复制 assets 中的文件和文件夹 */ private static void copyAssetsRecursive(Context context, AssetManager assetManager, String assetsPath, String targetBasePath) { try { // 获取 assets 路径下的所有项目 String[] items = assetManager.list(assetsPath); if (items == null || items.length == 0) { Log.w(TAG, "No items found in assets path: " + assetsPath); return; } // 创建目标目录 File targetDir = new File(targetBasePath, assetsPath); if (!targetDir.exists() && !targetDir.mkdirs()) { Log.e(TAG, "Failed to create target directory: " + targetDir.getAbsolutePath()); return; } // 获取已复制的文件列表 Set copiedFiles = getCopiedFiles(context); boolean hasNewFiles = false; for (String item : items) { String assetItemPath = assetsPath.isEmpty() ? item : assetsPath + "/" + item; String fileKey = assetItemPath; // 检查是否已经复制过 if (copiedFiles.contains(fileKey)) { Log.d(TAG, "Item already copied: " + assetItemPath); continue; } try { // 判断是文件还是目录的正确方法 boolean isFile = isAssetFile(assetManager, assetsPath, item); if (isFile) { // 复制文件 if (copySingleFile(assetManager, assetItemPath, targetDir, item)) { copiedFiles.add(fileKey); hasNewFiles = true; Log.d(TAG, "Successfully copied file: " + assetItemPath); } } else { // 递归复制子目录 copyAssetsRecursive(context, assetManager, assetItemPath, targetBasePath); // 将目录也标记为已处理 copiedFiles.add(fileKey); hasNewFiles = true; Log.d(TAG, "Processed directory: " + assetItemPath); } } catch (Exception e) { Log.e(TAG, "Error processing: " + assetItemPath, e); } } // 如果有新文件被复制,更新记录 if (hasNewFiles) { saveCopiedFiles(context, copiedFiles); } } catch (Exception e) { Log.e(TAG, "Error in recursive copy for path: " + assetsPath, e); } } /** * 正确判断 assets 中的项目是文件还是目录 */ private static boolean isAssetFile(AssetManager assetManager, String basePath, String item) { try { // 方法1:尝试列出子项,如果能列出且不为空,说明是目录 String testPath = basePath.isEmpty() ? item : basePath + "/" + item; String[] subItems = assetManager.list(testPath); // 如果子项为空,说明是文件或者是空目录 if (subItems == null) { return true; // 应该是文件 } // 如果有子项,说明是目录 if (subItems.length > 0) { return false; // 是目录 } // 如果子项为空数组,需要进一步判断 // 尝试打开它,如果能打开说明是文件,不能打开说明是空目录 try { InputStream test = assetManager.open(testPath); test.close(); return true; // 能打开,是文件 } catch (Exception e) { return false; // 不能打开,是空目录 } } catch (Exception e) { Log.e(TAG, "Error checking asset type: " + item, e); return true; // 默认当作文件处理 } } /** * 更可靠的递归复制方法 */ public static void copyAssetsToAppFilesSimple(Context context, String assetsPath) { try { AssetManager assetManager = context.getAssets(); File externalFilesDir = context.getExternalFilesDir(null); if (externalFilesDir == null) { Log.e(TAG, "External storage not available"); return; } // 创建目标目录 File targetBaseDir = new File(externalFilesDir, assetsPath); if (!targetBaseDir.exists() && !targetBaseDir.mkdirs()) { Log.e(TAG, "Failed to create base directory"); return; } copyAssetsRecursiveSimple(assetManager, assetsPath, targetBaseDir.getAbsolutePath()); Log.i(TAG, "Simple copy completed for: " + assetsPath); } catch (Exception e) { Log.e(TAG, "Error in simple copy", e); } } private static void copyAssetsRecursiveSimple(AssetManager assetManager, String assetsPath, String targetPath) { try { String[] items = assetManager.list(assetsPath); if (items == null || items.length == 0) { return; } for (String item : items) { String fullAssetPath = assetsPath.isEmpty() ? item : assetsPath + "/" + item; // 判断是文件还是目录 boolean isFile = isAssetFileSimple(assetManager, fullAssetPath); if (isFile) { // 复制文件 copySingleFileSimple(assetManager, fullAssetPath, targetPath, item); Log.d(TAG, "Copied file: " + fullAssetPath + " to " + targetPath); } else { // 创建子目录并递归复制 File subDir = new File(targetPath, item); if (!subDir.exists()) { subDir.mkdirs(); } copyAssetsRecursiveSimple(assetManager, fullAssetPath, subDir.getAbsolutePath()); Log.d(TAG, "Processed directory: " + fullAssetPath); } } } catch (Exception e) { Log.e(TAG, "Error in simple recursive copy: " + assetsPath, e); } } private static boolean isAssetFileSimple(AssetManager assetManager, String path) { try { String[] subItems = assetManager.list(path); if (subItems == null) return true; // 应该是文件 if (subItems.length > 0) return false; // 有子项,是目录 // 空数组,尝试打开判断 try { InputStream test = assetManager.open(path); test.close(); return true; // 能打开,是文件 } catch (Exception e) { return false; // 不能打开,是空目录 } } catch (Exception e) { return true; // 出错时默认当作文件 } } /** * 复制单个文件 */ private static boolean copySingleFile(AssetManager assetManager, String assetFilePath, File targetDir, String filename) { InputStream in = null; OutputStream out = null; try { File outFile = new File(targetDir, filename); in = assetManager.open(assetFilePath); out = new FileOutputStream(outFile); byte[] buffer = new byte[1024]; int read; while ((read = in.read(buffer)) != -1) { out.write(buffer, 0, read); } Log.d(TAG, "File copied: " + outFile.getAbsolutePath()); return true; } catch (Exception e) { Log.e(TAG, "Failed to copy file: " + assetFilePath, e); return false; } finally { try { if (in != null) in.close(); if (out != null) { out.flush(); out.close(); } } catch (Exception e) { Log.e(TAG, "Error closing streams", e); } } } private static void copySingleFileSimple(AssetManager assetManager, String assetFilePath, String targetPath, String filename) { InputStream in = null; OutputStream out = null; try { File outFile = new File(targetPath, filename); in = assetManager.open(assetFilePath); out = new FileOutputStream(outFile); byte[] buffer = new byte[1024]; int read; while ((read = in.read(buffer)) != -1) { out.write(buffer, 0, read); } } catch (Exception e) { Log.e(TAG, "Error copying: " + assetFilePath, e); } finally { try { if (in != null) in.close(); if (out != null) { out.flush(); out.close(); } } catch (Exception e) { Log.e(TAG, "Error closing streams", e); } } } /** * 调试方法:打印 assets 结构 */ public static void printAssetsStructure(Context context, String path) { try { Log.d(TAG, "=== Assets Structure for: " + path + " ==="); AssetManager am = context.getAssets(); printAssetsRecursive(am, path, 0); Log.d(TAG, "=== End of Assets Structure ==="); } catch (Exception e) { Log.e(TAG, "Error printing assets structure", e); } } private static void printAssetsRecursive(AssetManager am, String path, int depth) { try { String indent = ""; for (int i = 0; i < depth; i++) indent += " "; String[] items = am.list(path); if (items != null) { for (String item : items) { String fullPath = path.isEmpty() ? item : path + "/" + item; boolean isFile = isAssetFileSimple(am, fullPath); if (isFile) { Log.d(TAG, indent + "📄 " + item + " (file)"); } else { Log.d(TAG, indent + "📁 " + item + " (dir)"); printAssetsRecursive(am, fullPath, depth + 1); } } } } catch (Exception e) { Log.e(TAG, "Error in recursive print", e); } } /** * 获取已复制的文件列表 */ @SuppressWarnings("unchecked") private static Set getCopiedFiles(Context context) { return context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) .getStringSet(PREF_KEY_COPIED_FILES, new HashSet()); } /** * 保存已复制的文件列表 */ private static void saveCopiedFiles(Context context, Set copiedFiles) { context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) .edit() .putStringSet(PREF_KEY_COPIED_FILES, new HashSet<>(copiedFiles)) .apply(); } /** * 清空复制记录 */ public static void clearCopyRecords(Context context) { context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) .edit() .remove(PREF_KEY_COPIED_FILES) .apply(); Log.d(TAG, "Copy records cleared"); } /** * 检查外部存储是否可用 */ public static boolean isExternalStorageAvailable() { String state = Environment.getExternalStorageState(); return Environment.MEDIA_MOUNTED.equals(state); } /** * 获取目标文件的完整路径 */ public static String getFilePath(Context context, String relativePath) { File externalFilesDir = context.getExternalFilesDir(null); if (externalFilesDir == null) return null; File targetFile = new File(externalFilesDir, relativePath); return targetFile.getAbsolutePath(); } /** * 检查文件是否存在 */ public static boolean fileExists(Context context, String relativePath) { File externalFilesDir = context.getExternalFilesDir(null); if (externalFilesDir == null) return false; File targetFile = new File(externalFilesDir, relativePath); return targetFile.exists(); } }