Android 存儲路徑淺析

Android 文件系統

在 Android Studio 可以在 DDMS 的 File Exploer 窗口中查看文件系統,下圖就是一個 Android 文件系統目錄。
內部存儲

Android 存儲分類 (/data 目錄和 /sdcard 目錄)

Android 的存儲可以分爲三類:內存、內置 SD 卡,外置 SD 卡

一、內部存儲

對應的就是 /data 目錄,需要系統 root 之後才能查看,該目錄下有很多子目錄,其中對於軟件開發比較重要的是:

1、 /data/app

該文件夾存放着系統中安裝的第三方應用的 apk 文件,當我們調試一個app的時候,可以看到控制檯輸出的內容,有一項是
uploading ……就是上傳我們的apk到這個文件夾,上傳成功之後纔開始安裝。

在這裏插入圖片描述

Android 中應用的安裝就是將應用的安裝包原封不動地拷貝到 /data/app 目錄下,每個應用安裝包本質上就是一個 zip 格式的壓縮文件。爲了提升應用的啓動效率,Android 會將解壓出來的 dex 格式的應用代碼文件解析提取後,緩存在 /data/dalvik-cache 目錄下。

2、/data/data

該文件夾存放存儲包私有數據,對於設備中每一個安裝的 App,系統都會在內部存儲空間的 data/data 目錄下以應用包名爲名字自動創建與之對應的文件夾。
用戶卸載 App 時,系統自動刪除 data/data 目錄下對應包名的文件夾及其內容。
該目錄下又把存儲內容進行了分類:

data/data/包名/cache: 存放的 APP 的緩存信息
data/data/包名/databases: 存放 APP 的數據庫信息
data/data/包名/files: 存放 APP 的文件信息
data/data/包名/shared_prefs: 存放 APP 內的 SharedPreferences

3、API

1 /data

Environment.getDataDirectory();

2 /data/data/包名/files

context.getFilesDir();

對於 Files 目錄下的文件,通常不會通過 File 類的方式直接進行讀寫,而是利用一些封裝過的類或函數進行操作:

public FileInputStream openFileInput(String name)
public FileOutputStream openFileOutput(String name, int mode)

還可以直接刪除或查詢該目錄下的文件:

context.deleteFile(name)
context.fileList()

3 /data/data/包名/cache

context.getCacheDir();

4 /data/data/包名/shared_prefs

context.getSharedPreferences(name,mode)//返回的是 SharedPreferences 對象
context.deleteSharedPreferences(name)

5 /data/data/包名/databases

context.getDataDir()
context.getDatabasePath(name)
context.deleteDatabase(name)

6 /data/data/包名/app_name

context.getDir(name,mode)

經測試該方法會在 /data/data/包名/ 目錄下生成一個以 app_ 開頭的目錄

二、外部存儲

每個兼容 Android 的設備都支持可用於保存文件的共享“外部存儲”。 該存儲可能是可移除的存儲介質(例如 SD 卡)或內部(不可移除)存儲。 保存到外部存儲的文件是全局可讀取文件,而且,在計算機上啓用 USB 大容量存儲以傳輸文件後,可由用戶修改這些文件。

在這裏插入圖片描述
外部存儲在 Android 文件系統中是 sdcard 目錄,這裏只是一個快捷方式,真正的目錄是 /storage/emulated/legacy 文件夾

1、獲取外存路徑和狀態

要讀取或寫入外部存儲上的文件,應用必須獲取READ_EXTERNAL_STORAGE 或 WRITE_EXTERNAL_STORAGE系統權限。

1 獲取狀態
Environment.getExternalStorageState()

返回值是以下一種:

MEDIA_UNKNOWN
MEDIA_REMOVED
MEDIA_UNMOUNTED
MEDIA_CHECKING
MEDIA_NOFS
MEDIA_MOUNTED
MEDIA_MOUNTED_READ_ONLY
MEDIA_SHARED
MEDIA_BAD_REMOVAL
MEDIA_UNMOUNTABLE

2 獲取目錄
Environment.getExternalStorageDirectory()

返回的路徑是 /storage/emulated/0

2、公共目錄

Android 在外部存儲上提供了十個公共目錄來存儲相對應的文件:
通過 API Environment.getExternalStoragePublicDirectory(type) 來訪問

  • DIRECTORY_MUSIC:/storage/emulated/0/Music
  • DIRECTORY_PODCASTS:/storage/emulated/0/Podcasts
  • DIRECTORY_RINGTONES:/storage/emulated/0/Ringtones
  • DIRECTORY_ALARMS:/storage/emulated/0/Alarms
  • DIRECTORY_NOTIFICATIONS:/storage/emulated/0/Notifications
  • DIRECTORY_PICTURES:/storage/emulated/0/Pictures
  • DIRECTORY_MOVIES:/storage/emulated/0/Movies
  • DIRECTORY_DOWNLOADS:/storage/emulated/0/Downloads
  • DIRECTORY_DCIM:/storage/emulated/0/Dcim
  • DIRECTORY_DOCUMENTS:/storage/emulated/0/Documents

三、私有目錄

Android2.2 引入了基於擴展存儲器的應用緩存目錄,該目錄指向大容量的擴展存儲器。與應用的內存私有目錄一樣,緩存目錄會隨着應用的卸載一併刪除。
和內部存儲一樣,會在 SD 卡的 Android/data 目錄下生成對應包名的文件夾

1 /storage/emulated/0/Android/data/應用包名/files
context.getExternalFilesDir(type)

2 /storage/emulated/0/Android/data/應用包名/cache

context.getExternalCacheDir()

3 在 Android 目錄下除了 data 目錄還有一個 obb 目錄
/storage/emulated/0/Android/obb/應用包名

context.getObbDir()

Android 文件系統一些其它目錄(可以忽略這部分,用的並不多)

1 /cache 目錄

通過 API Environment.getDownloadCacheDirectory() 訪問,存儲下載文件的緩存路徑
比如app有一些下載到本地的文件,又不想app卸載後被刪除,可以放在這個地方.利用公共目錄或者這裏的緩存目錄,而不需要啓動app後,開發者自己創建文件路徑

在這裏插入圖片描述

2 /system 目錄

通過 API Environment.getRootDirectory() 訪問,該目錄下也有一個 app 目錄,存放的是系統應用的 apk 文件。

/system/app 和 /data/app 的區別
/data/app 裏軟件權限沒全開,/system/app 裏的軟件獲取了所有權限
/data/app 可以應用卸載,/system/app 只能 root 後刪除
/data/app 文件夾大小隨便,/system/app 文件夾有大小限制
卸載/system/app 目錄下的文件並不會增加系統空間,即可用 ROM 空間

3 /mnt 目錄

這個目錄專門用來當作掛載點掛在外部設備的,如 SD 卡,sdcard
將會被系統視作一個文件夾,這個文件夾將會被系統嵌入到收集系統的 mnt 目錄中,所以在 /mnt 目錄下也會看到一個 sdcard 的快捷方式:

在這裏插入圖片描述

附:

Android獲取各種系統路徑的方法

Android 文件系統一些其它目錄
  • Environment.getDataDirectory() /data
  • Environment.getDownloadCacheDirectory() /cache
  • Environment.getRootDirectory() /system
公有目錄
Environment.getExternalStoragePublicDirectory(DIRECTORY_ALARMS)	/storage/sdcard0/Alarms
Environment.getExternalStoragePublicDirectory(DIRECTORY_DCIM)	/storage/sdcard0/DCIM
Environment.getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS)	/storage/sdcard0/Download
Environment.getExternalStoragePublicDirectory(DIRECTORY_MOVIES)	/storage/sdcard0/Movies
Environment.getExternalStoragePublicDirectory(DIRECTORY_MUSIC)	/storage/sdcard0/Music
Environment.getExternalStoragePublicDirectory(DIRECTORY_NOTIFICATIONS)	/storage/sdcard0/Notifications
Environment.getExternalStoragePublicDirectory(DIRECTORY_PICTURES)	/storage/sdcard0/Pictures
Environment.getExternalStoragePublicDirectory(DIRECTORY_PODCASTS)	/storage/sdcard0/Podcasts
Environment.getExternalStoragePublicDirectory(DIRECTORY_RINGTONES)	/storage/sdcard0/Ringtones
私有目錄
  • getExternalFilesDir() /storage/emulated/0/Android/data/cwj.test(包名)/files/test
  • getExternalCacheDir /storage/emulated/0/Android/data/cwj.test(包名)/cache/test
通過Context獲取的
  • Context.getDatabasePath() 返回通過Context.openOrCreateDatabase 創建的數據庫文件
  • Context.getCacheDir().getPath() : 用於獲取APP的cache目錄/data/data//cache目錄
  • Context.getExternalCacheDir().getPath() : 用於獲取APP的在SD卡中的cache目錄/mnt/sdcard/android/data//cache
  • Context.getFilesDir().getPath() : 用於獲取APP的files目錄 /data/data//files
  • Context.getObbDir().getPath(): 用於獲取APPSDK中的obb目錄/mnt/sdcard/Android/obb/
  • Context.getPackageName() : 用於獲取APP的所在包目錄
  • Context.getPackageCodePath() : 來獲得當前應用程序對應的 apk 文件的路徑
  • Context.getPackageResourcePath() : 獲取該程序的安裝包路徑
完整 操作手機文件 工具類

    public void saveToPhone(View v) {
//        FileDir();
        SDCardTest();
    }

    private void FileDir() {
        boolean mExternalStorageAvailable = false;
        boolean mExternalStorageWriteable = false;
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state)) {
        // We can read and write the media
            mExternalStorageAvailable = mExternalStorageWriteable = true;
        } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
            // We can only read the media
            mExternalStorageAvailable = true;
            mExternalStorageWriteable = false;
        } else {
            // Something else is wrong. It may be one of many other states, but all we need
            //  to know is we can neither read nor write
            mExternalStorageAvailable = mExternalStorageWriteable = false;
        }


        Log.i("codecraeer", "getFilesDir = " + getFilesDir());
        Log.i("codecraeer", "getExternalFilesDir = " + getExternalFilesDir("exter_test").getAbsolutePath());
        Log.i("codecraeer", "getDownloadCacheDirectory = " + Environment.getDownloadCacheDirectory().getAbsolutePath());
        Log.i("codecraeer", "getDataDirectory = " + Environment.getDataDirectory().getAbsolutePath());
        Log.i("codecraeer", "getExternalStorageDirectory = " + Environment.getExternalStorageDirectory().getAbsolutePath());
        Log.i("codecraeer", "getExternalStoragePublicDirectory = " + Environment.getExternalStoragePublicDirectory("pub_test"));
    }

    /**
     * SDcard操作
     */
    public void SDCardTest(){
        try {
            //獲取擴展SD卡設備狀態
            String sDStateString = Environment.getExternalStorageState();
            //擁有可讀可寫權限
            if(sDStateString.equals(Environment.MEDIA_MOUNTED)){
                //獲取擴展存儲設備的文件目錄
                    File SDFile = Environment.getExternalStorageDirectory();
                String fileDirectoryPath=SDFile.getAbsolutePath()+File.separator+"測試文件夾";
                File fileDirectory=new File(fileDirectoryPath);
                //打開文件
                File myFile = new File(fileDirectoryPath+File.separator+"MyFile.txt");
                //判斷是否存在,不存在則創建
                if (!fileDirectory.exists()) {
                        //按照指定的路徑創建文件夾
                        fileDirectory.mkdirs();
                }
                if (!myFile.exists()) {
                    try {
                        //在指定的文件夾中創建文件
                        myFile.createNewFile();
                    } catch (Exception e) {
                    }
                }
                //寫數據
                String szOutText="Hello, World!+姚佳偉";
                FileOutputStream outputStream=new FileOutputStream(myFile);
                outputStream.write(szOutText.getBytes());
                outputStream.close();
            }
            //擁有隻讀權限
            else if(sDStateString.endsWith(Environment.MEDIA_MOUNTED_READ_ONLY)){
                //獲取擴展存儲設備的文件目錄
                File SDFile=android.os.Environment.getExternalStorageDirectory();
                //創建一個文件
                File myFile=new File(SDFile.getAbsolutePath()+File.separator+"MyFile.txt");
                //判斷文件是否存在
                if(myFile.exists()){
                    //讀數據
                    FileInputStream inputStream=new FileInputStream(myFile);
                    byte[]buffer=new byte[1024];
                    inputStream.read(buffer);
                    inputStream.close();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
            Log.d("yjw","檢查權限");
        }
    }

public class FileUtils {
    static String directoryPath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "FileDemo" + File.separator;
    String fileName = directoryPath + getISO8601TimeFileName() + ".txt";

    //創建文件夾及文件
    public void CreateText() throws IOException {
        File file = new File(directoryPath);
        if (!file.exists()) {
            try {
                //按照指定的路徑創建文件夾
                file.mkdirs();
            } catch (Exception e) {
                // TODO: handle exception
            }
        }
        File dir = new File(fileName);
        if (!dir.exists()) {
            try {
                //在指定的文件夾中創建文件
                dir.createNewFile();
            } catch (Exception e) {
            }
        }
    }

    //向已創建的文件中寫入數據
    public void print(String str) {
        FileWriter fw = null;
        BufferedWriter bw = null;
        String datetime = "";
        try {
            CreateText();
            SimpleDateFormat tempDate = new SimpleDateFormat("yyyy-MM-dd" + " " + "hh:mm:ss");
            datetime = tempDate.format(new java.util.Date()).toString();
            fw = new FileWriter(fileName, true);//
            // 創建FileWriter對象,用來寫入字符流
            /**
             * 如果想要每次寫入,清除之前的內容,使用FileOutputStream流
             */
            bw = new BufferedWriter(fw); // 將緩衝對文件的輸出
            String myreadline = datetime + "[]" + str;

            bw.write(myreadline + "\n"); // 寫入文件
            bw.newLine();
            bw.flush(); // 刷新該流的緩衝
            bw.close();
            fw.close();
        } catch (IOException e) {
            e.printStackTrace();
            try {
                bw.close();
                fw.close();
            } catch (IOException e1) {
            }
        }
    }

    /**
     * 此方法爲android程序寫入sd文件文件,用到了android-annotation的支持庫@
     *
     * @param buffer   寫入文件的內容
     * @param folder   保存文件的文件夾名稱,如log;可爲null,默認保存在sd卡根目錄
     * @param fileName 文件名稱,默認app_log.txt
     * @param append   是否追加寫入,true爲追加寫入,false爲重寫文件
     * @param autoLine 針對追加模式,true爲增加時換行,false爲增加時不換行
     */
    public synchronized static void writeFiledToSDCard(@NonNull final byte[] buffer, @Nullable final String folder, @Nullable final String fileName, final boolean append, final boolean autoLine) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                boolean sdCardExist = Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED);
                String folderPath = "";
                if (sdCardExist) {
                    //TextUtils爲android自帶的幫助類
                    if (TextUtils.isEmpty(folder)) {
                        //如果folder爲空,則直接保存在sd卡的根目錄
//                        folderPath = Environment.getExternalStorageDirectory() + File.separator;
                        folderPath = directoryPath;
                    } else {
                        folderPath = Environment.getExternalStorageDirectory() + File.separator + folder + File.separator;
                    }
                } else {
                    return;
                }

                File fileDir = new File(folderPath);
                if (!fileDir.exists()) {
                    if (!fileDir.mkdirs()) {
                        return;
                    }
                }
                File file;
                //判斷文件名是否爲空
                if (TextUtils.isEmpty(fileName)) {
                    file = new File(folderPath + getISO8601TimeFileName() + ".txt");
                } else {
                    file = new File(folderPath + fileName);
                }
                RandomAccessFile raf = null;
                FileOutputStream out = null;
                try {
                    if (append) {
                        //如果爲追加則在原來的基礎上繼續寫文件
                        raf = new RandomAccessFile(file, "rw");
                        raf.seek(file.length());
                        raf.write(buffer);
                        if (autoLine) {
                            raf.write("\n".getBytes());
                        }
                    } else {
                        //重寫文件,覆蓋掉原來的數據
                        out = new FileOutputStream(file);
                        out.write(buffer);
                        out.flush();
                    }
                    Log.d("yjw", "writeFiledToSDCard===" + "文件存入成功");
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    try {
                        if (raf != null) {
                            raf.close();
                        }
                        if (out != null) {
                            out.close();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }

    public static String getISO8601TimeFileName() {
        TimeZone tz = TimeZone.getTimeZone("Asia/Shanghai");
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
        df.setTimeZone(tz);
        String nowAsISO = df.format(new Date());
        return nowAsISO;
    }
}

總結

app中自己定義的第三方非Android系統原生寫法的數據庫應該放在私有目錄中,因爲這樣app卸載後可以隨着刪除,避免遺留bug.或者放在內部存儲中
app 保存的圖片緩存可以放在私有目錄中
app 保存的用戶下載的文件,可以保存在公共目錄中
app 一些用戶的配置信息可以保存在app內部存儲shared_prefs中

反正一點app保存的數據最好不要放在功能公共目錄,要放的話就放在內部存儲或者私有目錄(注意開發者自定義的路徑,要防止用戶清理緩存後,app出現數據爲NULL的bug),除非用戶自己下載好的文件才放入公共目錄,用戶卸載了apk也可以查看,例如歌曲.如果特別隱私的信息,例如用戶的登錄配置信息,應該放在內部存儲中,因爲需要手機root後才能被查看.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章