Android_存儲之文件存儲


前面幾篇隨筆 講到的關於存儲的,SharedPreferences、Room、數據庫等 最終都是以文件形式 存儲到手機上的(除特殊的存儲於手機內存的:如Room可以創建內存數據庫)。

這些存儲方式,Android都提供了相應的API 方便操作數據:

SharedPreferences:最終存儲為一個xml文件。

數據庫:以數據庫的形式存儲,在手機中是一個.db的文件。前面有講過兩種 一種是Room,一種是ContentProvider。

 

下面,詳細的介紹下在Android 直接讀寫文件

 

一、內部存儲和外部存儲

 這里的存儲指的是永久非易失的存儲(rom),不是內存(ram)。

這里的外部存儲不是指 特定的可移動的存儲介質(如SD卡),現在很多Android手機都是一體機,普通消費者無法拆卸的,也沒有擴展SD卡的的卡槽了,我們把手機本身自帶的rom叫機身存儲或內置存儲現在的Android設備都將機身存儲 划分了內部存儲和外部存儲,體現內部和外部的區別。如果可擴展可移動存儲(如SD卡),這也是外部存儲,這樣手機則包含多個外部存儲。

注:下面的討論都是基於Android 4.4之后的,因為在老的Android版本(4.4之前)上 機身存儲沒有划分內部存儲和外部存儲,機身存儲即內部存儲,SD卡即外部存儲。

因為外部存儲事可移動的 可移除的,存在明顯的差異,它們具體不同的使用場景和功能,下面是大致差異,后面再具體說明。

內部存儲

  • 可用: 一直可用
  • 訪問:應用保存文件在這里,只有應用本身能訪問
  • 卸載:卸載應用時,應用的所有文件都會從內部存儲中刪除

外部存儲

  • 可用:非一直可用,可用掛載外部存儲作為USB存儲,甚至可用移除外部存儲
  • 訪問:存儲在外部存儲的文件,很容易被其他應用訪問和修改
  • 卸載:卸載應用時,應用在外部存儲創建的文件不一定都會被刪除掉,只是在getExternalFilesDir()路徑下的文件會被刪除(后面會具體說明)

 

1、內部存儲

 當存儲在內部存儲時,可用通過下面方法獲取合適的File對象。

Context   getFilesDir(): 返回應用程序的內部files目錄的File對象。如:/data/user/0/com.flx.testfilestorage/files

Context   getCacheDir():返回一個應用程序內部臨時文件目錄的File對象,這個臨時文件在不需要時會被刪除,在系統可用存儲很低時也會被系統刪除。如:/data/user/0/com.flx.testfilestorage/cache

 

應用files目錄中文件的操作

創建:這里給出了如下兩種方式

  • 構造File對象,包含了目錄和文件名。通過createNewFile()進行創建,如果文件不存在則創建。
File createFiles = new File(context.getFilesDir(), "testfile.txt");
try {
createFiles.createNewFile();
} catch (IOException e) {
Log.d( TAG, "files err:"+e.getMessage() );
}
  • 通過Context的api直接操作,通過openFileOutput直接獲取FileOutputStream輸出流對象,如果文件不存在,則直接創建。

注:openFileOutput的第二個參數(mode)文件的操作模式,其他值在API 17以后被棄用,只能使用MODE_PRIVATE。在SharedPreferences中有詳細說明:https://www.cnblogs.com/fanglongxiang/p/11390013.html

        try {
            //mode參數注意下,這里使用的Context.MODE_PRIVATE
            FileOutputStream outputStream = context.openFileOutput( "testfile22.txt", Context.MODE_PRIVATE );
            outputStream.write( "Use OutputStream Create file\n".getBytes() );
            outputStream.close();
        } catch (IOException e) {
            Log.d( TAG, "outputStream err:"+e.getMessage() );
        }

 

獲取文件:

  • 同上,通過目錄和文件名 構造File對象
File createFiles = new File(context.getFilesDir(), "testfile.txt");
  • 上面通過openFileOutput獲取文件輸出流 可寫文件(不存在則創建),也可通過openFileInput獲取文件輸入流 進行讀取文件內容。
FileOutputStream outputStream = context.openFileOutput( "testfile22.txt", Context.MODE_PRIVATE );//輸出流
FileInputStream fileInputStream = context.openFileInput( "testfile22.txt" );//輸入流
  • 還可直接獲取files里的所有文件列表
String[] arrFiles = context.fileList();

  

讀寫文件:

直接構造文件輸出輸入流讀寫文件。

FileOutputStream fileOutputStream = context.openFileOutput( "testfile22.txt", Context.MODE_PRIVATE );
FileOutputStream fileOutputStream2 = new FileOutputStream( createFiles );

FileInputStream fileInputStream = context.openFileInput( "testfile22.txt" );
FileInputStream fileInputStream2 = new FileInputStream( createFiles );

  

刪除文件:

  • File對象直接調用delete()方法刪除
createFiles.delete();
  • Context直接通過文件名刪除
context.deleteFile( "testfile22.txt" );

  

創建臨時文件:

上述files目錄操作方式同樣適用(File對象需要指定路徑的可以,而Context直接通過文件名的則不可以),另外添加一個createTempFile()的方法。

File.createTempFile( "tempfile", null, context.getCacheDir() );

  

2、外部存儲

首先,簡單介紹下兩個方法的差異以及主外部存儲。

先看下這段代碼,

String state = Environment.getExternalStorageState();
File externalFile = context.getExternalFilesDir( null );
File[] externalFiles = context.getExternalFilesDirs( Environment.DIRECTORY_PICTURES );
for (File file : externalFiles) {
Log.d( TAG, "state="+ state + ";\nexternalFiles=" + file + ";\nexternalFile="+externalFile);
try {
FileOutputStream fileOutputStream = new FileOutputStream( new File( file, "aaaa.txt" ) );
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}

運行的手機支持SD卡 並插入了一張SD卡,看看運行結果

2019-07-15 14:46:07.819 12704-12704/com.flx.testfilestorage D/flx_storage: state=mounted;
    externalFiles=/storage/emulated/0/Android/data/com.flx.testfilestorage/files/Pictures;
    externalFile=/storage/emulated/0/Android/data/com.flx.testfilestorage/files
2019-07-15 14:46:07.821 12704-12704/com.flx.testfilestorage D/flx_storage: state=mounted;
    externalFiles=/storage/553C-0E05/Android/data/com.flx.testfilestorage/files/Pictures;
    externalFile=/storage/emulated/0/Android/data/com.flx.testfilestorage/files

從上面看到,getExternalFilesDirs獲取的有兩個外部存儲,getExternalFilesDir是一個。這兩個外部存儲,一個是主外部存儲 即手機本身存儲中分為 內部存儲和外部存儲的 外部存儲部分,另一個是SD卡的掛載路徑。

getExternalFilesDir(),獲取就是主外部存儲路徑。

getExternalFilesDirs(),獲取所有外部存儲的路徑,包括本身的外部存儲 和 擴展出來的存儲(如SD卡)。

在一開始就說過,應用存儲到外部存儲的文件 當應用卸載時只有getExternalFilesDir()路徑下的會被刪除。

上面代碼在log后,所有外部存儲路徑下 都創建了aaaa.txt的文件,實際操作結果也是符合的,當卸載應用時,/storage/553C-0E05/Android/data/com.flx.testfilestorage/files/這個下面的aaaa.txt 仍然存在的。

 


可用性判斷: 

由於外部存儲不一定一直有用,可能被移除等情況,所以使用外部存儲需要進行判斷,外部存儲是否可用。

Environment.getExternalStorageState([File path])這個方法可以返回外部存儲的狀態,不過它只返回主外部存儲的狀態。從源碼中,也能直接的看出來

 

 保存文件到公共目錄:

保存文件到外部存儲的公共目錄,其他應用也能直接訪問,有以下兩種方法(非直接創建讀寫文件)

  • 媒體文件如圖片、音頻文件、錄像文件等,使用MediaStore的API進行操作。
  • 保存其他文檔文件,如PDF,使用ACTION_CREATE_DOCUMENT intent操作。這個就屬於SAF(Storage Access Framework)相關的了。

注:如果媒體文件,不希望被 Media Scanner 掃描,可以在相應目錄創建一個空的文件命名為 .nomedia  。這樣就能阻止Media Scanner的掃描和讀取文件 並 通過MediaStore的API提供給其他應用使用。

 

保存文件到私有目錄:

應用保存文件到外部存儲的私有目錄,可以使用getExternalFilesDir()方法。具體實例 參考外部存儲開始的示例。

注意:1.注意getExternalFilesDir()和getExternalFilesDirs()的區別、使用。

      2.getExternalFilesDir()里的文件 會隨着應用的卸載而刪除。

   3.注意這兩個方法的參數,盡量正確使用API提供的常量,這樣系統才能正確處理文件。如:使用Environment.DIRECTORY_RINGTONES,系統media scanner能夠識別到是鈴聲而不是音樂。

 

二、存儲路徑

從上面的介紹可用看到,應用存儲在 內部存儲和外部存儲上的私有目錄(getExternalFilesDir())下的,是和應用相關的,在應用卸載時 對應目錄文件會被刪除。

下面列舉了常用的存儲路徑,

        int i = 0;
        for (File file:context.getExternalFilesDirs( null )) {
            Log.d( TAG, " \ncontext.getExternalFilesDirs()[" + ++i + "]=" + file );
        }
        i=0;
        Log.d( TAG, " \ncontext.getExternalFilesDir()=" + context.getExternalFilesDir( null ) );
        Log.d( TAG, " \ncontext.getCacheDir()=" + context.getCacheDir() );
        Log.d( TAG, " \ncontext.getCodeCacheDir()=" +  context.getCodeCacheDir());
        Log.d( TAG, " \ncontext.getDatabasePath()=" + context.getDatabasePath( "external.db" ));
        Log.d( TAG, " \ncontext.getDataDir()=" + context.getDataDir() );
        Log.d( TAG, " \ncontext.getDir()=" + context.getDir( null, Context.MODE_PRIVATE ) );
        Log.d( TAG, " \ncontext.getExternalCacheDir()=" + context.getExternalCacheDir() );
        Log.d( TAG, " \ncontext.getFilesDir()=" + context.getFilesDir() );
        Log.d( TAG, " \ncontext.getObbDir()=" + context.getObbDir() );
        for (File file:context.getExternalCacheDirs()) {
            Log.d( TAG, " \ncontext.getExternalCacheDirs()[" + ++i + "]=" + file );
        }

        Log.d( TAG, " \nEnvironment.getDataDirectory()=" + Environment.getDataDirectory() );
        Log.d( TAG, " \nEnvironment.getExternalStorageDirectory()="+Environment.getExternalStorageDirectory() );
        Log.d( TAG, " \nEnvironment.getDownloadCacheDirectory()="+Environment.getDownloadCacheDirectory() );
        Log.d( TAG, " \nEnvironment.getRootDirectory()="+Environment.getRootDirectory() );
        Log.d( TAG, " \nEnvironment.getExternalStoragePublicDirectory()= "+Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_PICTURES ) );
        

 

log打印出具體路徑:

2019-09-11 16:38:08.799 19469-19469/com.flx.testfilestorage D/flx_storage:  
    context.getExternalFilesDirs()[1]=/storage/emulated/0/Android/data/com.flx.testfilestorage/files
2019-09-11 16:38:08.799 19469-19469/com.flx.testfilestorage D/flx_storage:  
    context.getExternalFilesDirs()[2]=/storage/553C-0E05/Android/data/com.flx.testfilestorage/files
2019-09-11 16:38:08.803 19469-19469/com.flx.testfilestorage D/flx_storage:  
    context.getExternalFilesDir()=/storage/emulated/0/Android/data/com.flx.testfilestorage/files
2019-09-11 16:38:08.805 19469-19469/com.flx.testfilestorage D/flx_storage:  
    context.getCacheDir()=/data/user/0/com.flx.testfilestorage/cache
2019-09-11 16:38:08.806 19469-19469/com.flx.testfilestorage D/flx_storage:  
    context.getCodeCacheDir()=/data/user/0/com.flx.testfilestorage/code_cache
2019-09-11 16:38:08.807 19469-19469/com.flx.testfilestorage D/flx_storage:  
    context.getDatabasePath()=/data/user/0/com.flx.testfilestorage/databases/external.db
2019-09-11 16:38:08.807 19469-19469/com.flx.testfilestorage D/flx_storage:  
    context.getDataDir()=/data/user/0/com.flx.testfilestorage
2019-09-11 16:38:08.808 19469-19469/com.flx.testfilestorage D/flx_storage:  
    context.getDir()=/data/user/0/com.flx.testfilestorage/app_null
2019-09-11 16:38:08.813 19469-19469/com.flx.testfilestorage D/flx_storage:  
    context.getExternalCacheDir()=/storage/emulated/0/Android/data/com.flx.testfilestorage/cache
2019-09-11 16:38:08.814 19469-19469/com.flx.testfilestorage D/flx_storage:  
    context.getFilesDir()=/data/user/0/com.flx.testfilestorage/files
2019-09-11 16:38:08.818 19469-19469/com.flx.testfilestorage D/flx_storage:  
    context.getObbDir()=/storage/emulated/0/Android/obb/com.flx.testfilestorage
2019-09-11 16:38:08.823 19469-19469/com.flx.testfilestorage D/flx_storage:  
    context.getExternalCacheDirs()[1]=/storage/emulated/0/Android/data/com.flx.testfilestorage/cache
2019-09-11 16:38:08.824 19469-19469/com.flx.testfilestorage D/flx_storage:  
    context.getExternalCacheDirs()[2]=/storage/553C-0E05/Android/data/com.flx.testfilestorage/cache
2019-09-11 16:38:08.824 19469-19469/com.flx.testfilestorage D/flx_storage:  
    Environment.getDataDirectory()=/data
2019-09-11 16:38:08.828 19469-19469/com.flx.testfilestorage D/flx_storage:  
    Environment.getExternalStorageDirectory()=/storage/emulated/0
2019-09-11 16:38:08.828 19469-19469/com.flx.testfilestorage D/flx_storage:  
    Environment.getDownloadCacheDirectory()=/data/cache
2019-09-11 16:38:08.828 19469-19469/com.flx.testfilestorage D/flx_storage:  
    Environment.getRootDirectory()=/system
2019-09-11 16:38:08.832 19469-19469/com.flx.testfilestorage D/flx_storage:  
    Environment.getExternalStoragePublicDirectory()= /storage/emulated/0/Pictures

Context獲取的都是包含包名的路徑。仔細看下就能知道 方法獲取的是手機中的路徑。

這些方法獲取到的都是File對象,可用使用getTotalSpace()或getFreeSpace()獲取總空間大小或剩余空間大小,注意單位時byte.

 

下面是 /data/user/0/com.flx.testfilestorage/ 在手機中的顯示,不是所有應用下面的文件夾都有的,一些是上面運行產生的。正常如果沒有對應文件 則對應文件夾是不存在的,如沒有數據庫文件 則databases是沒有的,如果有SharedPreferences數據則還會有shared_prefs文件夾。不過cache,code_cache,files一般都是有的。

 

 路徑疑問:

以前隨筆中介紹的,很多路徑是/data/data/xxx,如SharedPreferences存儲在/data/data/<packagename>/shared_prefs/下,數據庫存儲在/data/data/<package_name>/databases下。而上面獲取到的是/data/user/0/xxxx下,這是怎么回事呢?

通過Android Studio看下/data/data/com.flx.testfilestorage/ 里面的內容可以看到是一樣的.

其實,/data/user/0/ 就是鏈接的/data/data/.所以他們的目標目錄都是/data/data/xxx下的。

 

 還可以通過命令查看,第一位的l就是表示一個鏈接。

 

Android系統中,這樣的鏈接還有不少。如果對路徑有疑問,可以通過這兩個方法看下,是否是鏈接的文件

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM