1. 程式人生 > >Android五種資料儲存方式之檔案儲存 內部儲存 外部儲存 檔案讀取儲存操作封裝

Android五種資料儲存方式之檔案儲存 內部儲存 外部儲存 檔案讀取儲存操作封裝

檔案儲存

前言

眾所周知,資料儲存在每個應用中都會用到,那所用到的技術應該怎麼選呢,這裡Android給開發者提供了幾種方法去儲存常用應用資料,至於你想選擇哪一種方式,取決於你的特定需求;例如這個資料是本應用私有的還是跟其它應用共享以及資料儲存所需的記憶體空間等

  • Shared Preferences:這種方式通常用來儲存私有原始資料,以鍵值對形式儲存;這也就意味著這些資料只能由本應用訪問
  • Internal Storage:這種方式是將私有資料儲存在內部儲存(裝置記憶體)中,實際上是使用檔案流進行讀寫
  • External Storage:這種方式是將公共資料儲存在共享外部儲存上;也就是將資料儲存在SD卡上,儲存在這上面說明資料是開放的,其它應用可以直接訪問;這跟上面一種都是平常所說的檔案儲存
  • SQLite Databases:這種方式是將結構化資料儲存在私有資料庫中;這也就是常說的資料庫儲存,使用SQLite儲存資料,這些資料是私有的
  • Network Connection:這種方式是將資料儲存在Web伺服器上,也就是通常所說的網路儲存

筆者上一篇文章講述了Shared Preferences的工作原理及使用封裝,這篇文章來掰掰第二種和第三種方式

檔案儲存

本文所含程式碼隨時更新,可從這裡下載最新程式碼
傳送門Mango

說到檔案儲存,就必須說下Android世界中的檔案系統了

大家使用Android手機的時候,多多少少都去過設定->應用介面,選擇一個應用開啟,可以看到上面有清除資料,清除快取兩個按鈕;有的手機可能做的頁面不一樣了,只有一個清除資料按鈕,但是還是能看到列舉出來的型別;那你知道這兩個按鈕是清除的哪裡的資料嗎?

我們經常聽到記憶體,內部儲存,外部儲存這幾個概念,那它們分別表示什麼呢?我們開發者儲存資料的時候應該把資料儲存到什麼地方呢?

記憶體

記憶體指的是手機在執行應用程式時需要的儲存空間,也稱為RAM,即執行記憶體,這個值基本上在手機出廠後就確定了;當你買手機的時候,銷售員跟你說這個手機配備8G記憶體+128G儲存等配置時,這個8G指的就是RAM,也就是應用執行時理論上能利用到的最大記憶體了;這個有點類似於電腦上的記憶體條,只不過電腦記憶體條可以後期增加,理論上這個值越大,基本上手機執行的就越流暢

內部儲存

英文稱為Internal Storage,我們可以將檔案直接儲存在裝置的內部儲存中。 預設情況下,儲存到內部儲存的檔案對應用程式是私有的,而其他應用程式無法訪問它們(使用者也無法訪問,除非root),使用內部儲存不需要額外的許可權; 當用戶解除安裝應用程式時,將刪除這些檔案;從技術上來講如果你在建立內部儲存檔案的時候將檔案屬性設定成可讀,其他app能夠訪問自己應用的資料,前提是他知道你這個應用的包名,如果一個檔案的屬性是私有(private),那麼即使知道包名其他應用也無法訪問。 內部儲存空間十分有限,因而顯得可貴,另外,它也是系統本身和系統應用程式主要的資料儲存所在地,一旦內部儲存空間耗盡,手機也就無法使用了。所以對於內部儲存空間,我們要儘量避免使用

我們開發中經常說某些檔案在data目錄下,這個data資料夾就是我們常說的內部儲存,裡面有兩個比較重要的子資料夾

  • app資料夾:這裡存放應用的apk檔案
  • data資料夾:這裡有很多以應用包名命名的資料夾,存放著應用資料

開啟其中一個應用資料夾,通常有如下檔案

data/data/包名/shared_prefs //該目錄下存放很多SharedPreferences資料,都是一些xml檔案
data/data/包名/databases //該目錄下存放db格式的檔案,也就是資料庫資料
data/data/包名/files //該目錄下存放普通資料
data/data/包名/cache //該目錄下存放快取檔案

外部儲存

英文稱為External Storage,每個Android相容裝置都支援可用於儲存檔案的共享“外部儲存”,它可能是可移除的儲存介質(典型如SD卡),也可能是不可移除的儲存介質(如現在很多一體機內建的儲存器);外部儲存是相對於內部儲存而言的,不過儲存在這上面的檔案是所有者可見的,所有人都有許可權操作,不過前提是需要申請許可權

要在外部儲存上讀取或寫入檔案,您的應用必須獲取READ_EXTERNAL_STORAGE或WRITE_EXTERNAL_STORAGE系統許可權;如果您需要同時讀取和寫入檔案,則只需要請求WRITE_EXTERNAL_STORAGE許可權,因為它也隱式地要求讀取許可權

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

注意:android6.0以後引入了執行時許可權的概念,要注意只在AndroidManifest.xml申請是不夠的
注意:從Android 4.4(api19)開始,如果您在外部儲存上只讀取或寫入應用程式專用的檔案(即在私有目錄讀寫),則不需要這些許可權

外部儲存的最外層目錄是storage資料夾,也可能是mnt資料夾,這個根據廠家不同有不同的結果
一般來說,在storage資料夾中有一個sdcard資料夾,這個資料夾中的檔案又分為兩類,一類是公共目錄,還有一類是私有目錄

  • 公共目錄:有九大類,比如DCIM、Download、Music、Movies、Pictures、Ringtones等這種系統為我們建立的資料夾;這些目錄裡的檔案所有應用可以分享
  • 私有目錄:就是Android這個資料夾,這個資料夾開啟之後裡邊有一個data資料夾,開啟這個data資料夾,裡邊有許多包名組成的資料夾,這個資料夾存放了應用私有資料

私有目錄如下

storage/sdcard/Android/data/包名/files
storage/sdcard/Android/data/包名/cache

經過上面的分析,應該對這些有一個大致的瞭解了,RAM記憶體是我們開發的過程中需要注意的,不要記憶體洩漏,不要無限new記憶體,因為那樣會使得手機執行記憶體緊張,執行卡頓;通常來說我們很少去操作內部儲存空間,因為沒有root許可權,我們動不了這塊,內部儲存通常是由系統來維護的,不過在程式碼中Google還是給了我們API訪問這些資料夾

通常情況下內部儲存空間都很有限,在開發中我們操作的最多的還是外部儲存,Google官方也建議開發者應該將資料儲存在外部儲存的私有目錄中該APP包名命名的資料夾下,這樣某些情況下其它應用是無法對你的應用檔案進行寫操作;同時當應用被解除安裝後,相關資料也會被一起清除掉,同時也不會引起使用者的反感;如果我們把資料儲存在公有目錄和storage/sdcard目錄下,資料是不會隨著應用的解除安裝而刪除的


內部儲存操作

API

操作內部儲存的api都是Context類的

  • getFilesDir():返回檔案系統上特定應用程式的檔案目錄的絕對路徑;返回一個File物件,它的目錄是 data/data/包名/files
  • fileList():返回應用程式當前儲存的檔案陣列;返回一個字串陣列,即由data/data/包名/files目錄下檔案的檔名組成的
  • deleteFile(String name):刪除儲存在內部儲存上的檔案;該檔案位於data/data/包名/files目錄下,返回一個boolean值表示是否刪除成功
  • getCacheDir():返回檔案系統上應用程式的快取目錄的絕對路徑;返回一個File物件,它的目錄是 data/data/包名/cache
  • getDir(String name, int mode):在內部儲存空間中建立(或開啟現有)目錄;返回一個File物件;請注意,通過File物件建立的檔案只能由您自己的應用程式訪問; 您只能設定整個目錄的模式,而不能設定單個檔案的模式;該資料夾是在data/data/包名 目錄上建立的
  • openFileOutput(String name, int mode):開啟內部儲存中與本應用程式包關聯的私有檔案以進行寫入,如果檔案尚不存在,則建立該檔案;返回一個輸出流FileOutputStream;該檔案位於data/data/包名/files目錄下
  • openFileInput(String name):開啟內部儲存中與本應用程式包關聯的私有檔案以進行讀取;返回一個輸入流FileInputStream,該檔案位於data/data/包名/files目錄下

其中openFileOutput方法第二個引數有如下幾個可選值:

  • Context.MODE_PRIVATE:預設模式,建立檔案(或替換同名檔案),只能由呼叫程式(或共享相同使用者ID的所有應用程式)訪問
  • Context.MODE_WORLD_READABLE:允許所有其他應用程式對建立的檔案具有讀的訪問許可權,使用這個模式Android N(7.0)開始將丟擲SecurityException,這個模式從API17已標記被棄用,建立全域性可讀檔案非常危險,可能引發安全漏洞
  • Context.MODE_WORLD_WRITEABLE:允許所有其他應用程式具有對建立檔案的寫訪問權,,使用這個模式Android N(7.0)開始將丟擲SecurityException,這個模式從API17已標記被棄用,建立全域性可寫檔案非常危險,可能引發安全漏洞
  • Context.MODE_APPEND:建立檔案,如果檔案已存在,則將資料寫入現有檔案的末尾而不是抹掉它。

注意:如果您想快取某些資料,而不是持久儲存它們,則應使用getCacheDir()開啟一個File,該File表示應用程式應儲存臨時快取檔案的內部目錄;當裝置內部儲存空間不足時,Android可能會刪除這些快取檔案以恢復空間。 但是,您不應該依賴系統來清理這些檔案。 您應該始終自己維護快取檔案並保持合理的空間限制,例如1MB。 當用戶解除安裝您的應用程式時,將刪除這些檔案

讀寫操作

/**
     * 獲取檔案內容
     * @param fileName 內部儲存中檔名
     * @return 按行讀取檔案內容
     */
    public List<String> getStringFromInternalStorage(String fileName){

        List<String> content = new ArrayList<>();
        InputStream is = null;
        BufferedReader br = null;
        try {
            is = mContext.get().openFileInput(fileName);
            br = new BufferedReader(new InputStreamReader(is,"UTF-8"));
            String line ;
            while ( (line = br.readLine()) != null){
                content.add(line);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {

            closeInputStream(is);
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        return content;
    }


    /**
     * 獲取內部儲存檔案資料
     * @param fileName 內部儲存中檔名
     * @return 返回檔案二進位制資料,以便傳輸
     */
    public byte[] getDataFromInternalStorage(String fileName){

        BufferedInputStream bis = null;
        FileInputStream fis = null;
        ByteArrayOutputStream bos = null;
        try {
            fis = mContext.get().openFileInput(fileName);
            bis = new BufferedInputStream(fis);
            bos = new ByteArrayOutputStream();
            byte[] buff = new byte[8*1024];
            int len ;
            while ((len = bis.read(buff)) != -1){
                bos.write(buff,0,len);
            }
            return bos.toByteArray();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            closeInputStream(fis);
            closeInputStream(bis);
            closeOutputStream(bos);
        }
        return null;
    }

    /**
     * 儲存字串資料到內部儲存
     * @param content 儲存的內容
     * @param fileName 檔名
     * @param mode 訪問模式
     */
    public void putStringToInternalStorage(String content,String fileName,int mode){

        FileOutputStream fos = null;
        try {
            fos = mContext.get().openFileOutput(fileName,mode);
            fos.write(content.getBytes());
            fos.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            closeOutputStream(fos);
        }
    }

    /**
     * 儲存資料到內部儲存
     * @param content 儲存的內容
     * @param fileName 檔名
     * @param mode 訪問模式
     */
    public void putDataToInternalStorage(byte[] content,String fileName,int mode){

        FileOutputStream fos = null;
        try {
            fos = mContext.get().openFileOutput(fileName,mode);
            fos.write(content);
            fos.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            closeOutputStream(fos);
        }
    }

外部儲存操作

做外部儲存操作之前一定要判斷外部儲存狀態

/**
     * 判斷sd卡是否處於就緒狀態 可讀可寫
     * MEDIA_UNKNOWN:未知狀態
     * MEDIA_REMOVED:移除狀態(外部儲存不存在)
     * MEDIA_UNMOUNTED:未裝載狀態(外部儲存存在但是沒有裝載)
     * MEDIA_CHECKING:磁碟檢測狀態
     * MEDIA_NOFS:外部儲存存在,但是磁碟為空或使用了不支援的檔案系統
     * MEDIA_MOUNTED:就緒狀態(可讀、可寫)
     * MEDIA_MOUNTED_READ_ONLY:只讀狀態
     * MEDIA_SHARED:共享狀態(外部儲存存在且正通過USB共享資料)
     * MEDIA_BAD_REMOVAL:異常移除狀態(外部儲存還沒有正確解除安裝就被移除了)
     * MEDIA_UNMOUNTABLE:不可裝載狀態(外部儲存存在但是無法被裝載,一般是磁碟的檔案系統損壞造成的)
     * @return
     */
    public boolean isSdCardMount(){
        return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
    }

上面說到外部儲存的時候講到過這裡有公共目錄和私有目錄之分

公共目錄

通常情況下,使用者通過我們的應用獲取的比如多媒體檔案,像圖片、視訊、鈴聲等檔案,應該放在這些公共目錄中,例如Music /,Pictures /和Ringtones /;將檔案儲存到相應的媒體型別目錄,系統的媒體掃描程式可以正確地對系統中的檔案進行分類(例如,鈴聲在系統設定中顯示為鈴聲,而不是音樂);同時使用者可以輕鬆地從裝置找到並複製它們,也能更好的與其它應用分享;即使應用被解除安裝,這些檔案依然保留

如果要將檔案儲存在相應的公共目錄,可以通過呼叫getExternalStoragePublicDirectory(String type),向其傳遞所需的目錄型別,例如DIRECTORY_MUSIC,DIRECTORY_PICTURES,DIRECTORY_RINGTONES或其他,引數不可為null,返回一個File物件,如果不存在,即建立

/**
     * 獲取公共目錄
     * @param type DIRECTORY_MUSIC 音樂型別
     *             DIRECTORY_RINGTONES 鈴聲型別
     *             DIRECTORY_PODCASTS 播客音訊型別
     *             DIRECTORY_ALARMS 鬧鐘提示音型別
     *             DIRECTORY_NOTIFICATIONS 通知提示音型別
     *             DIRECTORY_PICTURES 圖片型別
     *             DIRECTORY_MOVIES 電影型別
     *             DIRECTORY_DOWNLOADS 下載檔案型別
     *             DIRECTORY_DCIM 相機照片型別
     *             DIRECTORY_DOCUMENTS 文件型別
     * @return 相應型別目錄檔案
     */
    public File getExternalStoragePublicDirectory(String type){
        File file = Environment.getExternalStoragePublicDirectory(type);
        if (!file.exists()) {
            file.mkdir();
        }
        return file;
    }

獲取到的目錄類似於/storage/sdcard0/Music

假如不想存放在這些目錄裡,需要存放在一些自定義的目錄中,那就通過Environment.getExternalStorageDirectory()獲取外部儲存的根目錄,通常是SD卡的根目錄,比如/storage/sdcard0,然後你就可以在這個目錄上新建目錄


私有目錄

私有檔案

如果您正在處理不適合其他應用程式使用的檔案(例如僅由您的應用程式使用的檔案),則應通過呼叫getExternalFilesDir()在外部儲存上使用專用儲存目錄。 此方法還接收型別引數來指定子目錄的型別(例如DIRECTORY_MOVIES),如果目錄不存在Android會建立。 如果您不需要特定的媒體目錄,請傳遞null以接收應用程式私有檔案目錄的根目錄(/storage/sdcard0/Android/data/包名/files/)。

這在作用上有點類似於內部儲存中的getFilesDir()方法,同時都是屬於Context的API

/**
     * 獲取私有指定型別目錄
     * @param type DIRECTORY_MUSIC 音樂型別
     *             DIRECTORY_PODCASTS 播客音訊型別
     *             DIRECTORY_RINGTONES 鈴聲型別
     *             DIRECTORY_ALARMS 鬧鐘提示音型別
     *             DIRECTORY_NOTIFICATIONS 通知提示音型別
     *             DIRECTORY_PICTURES 圖片型別
     *             DIRECTORY_MOVIES 電影型別
     * @return 相應型別目錄檔案 例如/storage/sdcard0/Android/data/com.mango.datasave/files/Music
     */
    public File getExternalStoragePrivateDirectory(String type){
        File file = mContext.get().getExternalFilesDir(type);
        return file;
    }

注意:某些移動裝置可能既提供了內建儲存器作為外部儲存空間(通常是手機自帶的),同時又提供了SD卡作為外部儲存空間。也就是說,在這些裝置中外部儲存實際上包含了兩塊磁碟。在Android 4.3(API 18)及以下,Context的getExternalFilesDir方法僅僅會返回內建儲存器對應的外部儲存空間,而無法訪問SD卡對應的儲存空間。從Android 4.4(API 19)開始,Context新增了getExternalFilesDirs方法。這個方法的返回值是一個File陣列,包含兩個物件(可能為null),這樣就可以實現對內建儲存器和SD卡的訪問。陣列的第一個物件預設是內建儲存器,官方的開發建議是除非這個位置已滿或不可用,否則應該使用這個位置

私有快取

同內部儲存一樣,外部儲存也有儲存快取檔案的目錄,可以通過Context的getExternalCacheDir方法訪問快取檔案目錄,返回值是一個File物件,對應的目錄是 /storage/sdcard0/Android/data/com.mango.datasave/cache ,其中com.mango.datasave是我的測試應用包名;如果目錄不存在Android會建立

如上面的注意點,外部儲存可能同時包含內建儲存器和SD卡兩個儲存空間,因此在Android 4.4(API 19)及以上還可以通過Context的getExternalCacheDirs方法訪問這兩個儲存空間。這個方法會返回一個File陣列,包含兩個物件,第一個物件預設是外部儲存內建儲存器的快取檔案目錄

File getExternalCacheDir()
File[] getExternalCacheDirs()

注意:當用戶解除安裝您的應用程式時,將刪除 /storage/sdcard0/Android/data/包名 目錄及其所有內容。 此外,系統掃描程式不讀取這些目錄中的檔案,因此無法從MediaStore內容提供程式訪問它們。 因此,您不應將這些目錄用於存放屬於使用者的媒體,例如使用您的應用程式捕獲或編輯的照片,或使用者使用您的應用程式購買的音樂 - 這些檔案應儲存在公共目錄中

檔案各種操作封裝

/**
 * @Description TODO(檔案操作輔助類)
 * @author cxy
 * @Date 2018/10/30 16:26
 */
public class FileStorageTools {

    private String TAG = FileStorageTools.class.getSimpleName();

    private WeakReference<Context> mContext ;
    private static FileStorageTools instance;

    private FileStorageTools(Context context){
        mContext = new WeakReference<>(context);
    }
    public static FileStorageTools getInstance(Context context){
        if(instance == null){
            instance = new FileStorageTools(context);
        }
        return instance;
    }

    //獲取ram可用記憶體
    public String getRAMAvailMem(){
        ActivityManager am=(ActivityManager)mContext.get().getSystemService(Context.ACTIVITY_SERVICE);
        ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
        am.getMemoryInfo(mi);
        return reviseFileSize(mi.availMem);
    }

    //獲取ram總記憶體
    public String getRAMTotalMem(){
        ActivityManager am=(ActivityManager)mContext.get().getSystemService(Context.ACTIVITY_SERVICE);
        ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
        am.getMemoryInfo(mi);
        return reviseFileSize(mi.totalMem);
    }

    //獲取sd卡總大小
    public String getSDTotalSize(){
        if(isSdCardMount()){
            File file=Environment.getExternalStorageDirectory();
            StatFs statFs=new StatFs(file.getPath());
            long blockSize=statFs.getBlockSizeLong();
            long totalBlocks=statFs.getBlockCountLong();
            return reviseFileSize(totalBlocks*blockSize);
        }else {
            return null;
        }
    }

    //獲取sd卡可用大小
    public String getSDAvailableSize(){
        if(isSdCardMount()){
            File file=Environment.getExternalStorageDirectory();
            StatFs statFs=new StatFs(file.getPath());
            long blockSize=statFs.getBlockSizeLong();
            long availableBlocks=statFs.getFreeBlocksLong();
            return reviseFileSize(availableBlocks*blockSize);
        }else {
            return null;
        }
    }


    /**================================================內部儲存操作=================================================================**/

    /**
     * 獲取檔案內容
     * @param fileName 內部儲存中檔名
     * @return 按行讀取檔案內容
     */
    public List<String> getStringFromInternalStorage(String fileName){

        List<String> content = new ArrayList<>();
        InputStream is = null;
        BufferedReader br = null;
        try {
            is = mContext.get().openFileInput(fileName);
            br = new BufferedReader(new InputStreamReader(is,"UTF-8"));
            String line ;
            while ( (line = br.readLine()) != null){
                content.add(line);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {

            closeInputStream(is);
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        return content;
    }


    /**
     * 獲取內部儲存檔案資料
     * @param fileName 內部儲存中檔名
     * @return 返回檔案二進位制資料,以便傳輸
     */
    public byte[] getDataFromInternalStorage(String fileName){

        BufferedInputStream bis = null;
        FileInputStream fis = null;
        ByteArrayOutputStream bos = null;
        try {
            fis = mContext.get().openFileInput(fileName);
            bis = new BufferedInputStream(fis);
            bos = new ByteArrayOutputStream();
            byte[] buff = new byte[8*1024];
            int len ;
            while ((len = bis.read(buff)) != -1){
                bos.write(buff,0,len);
            }
            return bos.toByteArray();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            closeInputStream(fis,bis);
            closeOutputStream(bos);
        }
        return null;
    }

    /**
     * 儲存字串資料到內部儲存
     * @param content 儲存的內容
     * @param fileName 檔名
     * @param mode 訪問模式
     */
    public void putStringToInternalStorage(String content,String fileName,int mode){

        FileOutputStream fos = null;
        try {
            fos = mContext.get().openFileOutput(fileName,mode);
            fos.write(content.getBytes());
            fos.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            closeOutputStream(fos);
        }
    }

    /**
     * 儲存資料到內部儲存
     * @param content 儲存的內容
     * @param fileName 檔名
     * @param mode 訪問模式
     */
    public void putDataToInternalStorage(byte[] content,String fileName,int mode){

        FileOutputStream fos = null;
        try {
            fos = mContext.get().openFileOutput(fileName,mode);
            fos.write(content);
            fos.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            closeOutputStream(fos);
        }
    }

    /**
     * 將內容儲存到內部儲存的快取目錄 儘量別將檔案儲存在這裡,記憶體有限
     * @param content
     * @param fileName
     * @param append 是否追加到檔案尾部
     */
    public void putInternalStorageCache(String content,String fileName,boolean append){

        FileOutputStream fos = null;
        try {
            File cache = new File(mContext.get().getCacheDir(),fileName);
            fos = new FileOutputStream(cache,append);
            fos.write(content.getBytes());
            fos.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            closeOutputStream(fos);
        }
    }

    /**====================================================外部儲存操作=======================================================**/

    /**====================================構建目錄=================================================**/

    /**
     * 獲取公共目錄
     * @param type DIRECTORY_MUSIC 音樂型別
     *             DIRECTORY_RINGTONES 鈴聲型別
     *             DIRECTORY_PODCASTS 播客音訊型別
     *             DIRECTORY_ALARMS 鬧鐘提示音型別
     *             DIRECTORY_NOTIFICATIONS 通知提示音型別
     *             DIRECTORY_PICTURES 圖片型別
     *             DIRECTORY_MOVIES 電影型別
     *             DIRECTORY_DOWNLOADS 下載檔案型別
     *             DIRECTORY_DCIM 相機照片型別
     *             DIRECTORY_DOCUMENTS 文件型別
     * @return 相應型別目錄檔案 例如/storage/sdcard0/Music
     */
    public File getExternalStoragePublicDirectory(String type){
        File file = Environment.getExternalStoragePublicDirectory(type);
        if (!file.exists()) {
            file.mkdir();
        }
        return file;
    }

    /**
     * 獲取私有檔案目錄
     * @param type DIRECTORY_MUSIC 音樂型別
     *             DIRECTORY_PODCASTS 播客音訊型別
     *             DIRECTORY_RINGTONES 鈴聲型別
     *             DIRECTORY_ALARMS 鬧鐘提示音型別
     *             DIRECTORY_NOTIFICATIONS 通知提示音型別
     *             DIRECTORY_PICTURES 圖片型別
     *             DIRECTORY_MOVIES 電影型別
     * @return 相應型別目錄檔案 例如/storage/sdcard0/Android/data/com.mango.datasave/files/Music
     */
    public File getExternalStoragePrivateDirectory(String type){
        File file = mContext.get().getExternalFilesDir(type);
        return file;
    }

    /**
     * 獲取私有快取目錄
     * @return /storage/sdcard0/Android/data/com.mango.datasave/cache
     */
    public File getExternalStoragePrivateCache(){
        File file = mContext.get().getExternalCacheDir();
        return file;
    }

    /**
     * 構建檔案目錄
     * @param path 例如 /file/movie
     * @return 返回完整目錄 /storage/sdcard0/file/movie
     */
    public String makeFilePath(String path){
        if (StringTools.isEmpty(path)) throw new NullPointerException("path cant be null");
        return Environment.getExternalStorageDirectory().getAbsolutePath() + path;
    }


    /**
     * 建立檔案
     * @param base
     * @param fileName
     * @return
     */
    public File makeFile(File base,String fileName){
        if (StringTools.isEmpty(fileName)) throw new NullPointerException("fileName cant be null");
        if(fileName.indexOf(File.separator) < 0){
            return new File(base,fileName);
        }
        throw new IllegalArgumentException(
                "File " + fileName + " contains a path separator");
    }

    /**====================================儲存資料=================================================**/

    /**
     * 將內容寫到外部儲存檔案
     * @param content 內容
     * @param parent 目標檔案父目錄
     * @param fileName 檔名
     * @param append 內容是追加到檔案末尾還是覆蓋
     */
    public void putStringToExternalStorage(String content,File parent, String fileName,boolean append){

        FileOutputStream fos = null;

        File file = makeFile(parent,fileName);
        try {
            fos = new FileOutputStream(file,append);
            fos.write(content.getBytes());
            fos.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            closeOutputStream(fos);
        }
    }

    /**
     * 將內容寫到外部儲存檔案
     * @param parent 目標檔案父目錄
     * @param fileName 檔名
     * @param content 內容
     *                @String.getBytes()
     */
    public void putDataToExternalStorage(File parent, String fileName,byte[] content){

        FileOutputStream fos = null;
        try {
            File file = makeFile(parent,fileName);
            fos = new FileOutputStream(file);
            fos.write(content);
            fos.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            closeOutputStream(fos);
        }
    }

    /**
     * 將流資料儲存到外部儲存檔案
     * @param parent 目標檔案父目錄
     * @param fileName 檔名
     * @param is 流
     */
    public void putStreamToExternalStorage(File parent, String fileName,InputStream is){

        BufferedOutputStream bos = null;
        BufferedInputStream bis = null;

        try {
            File file = makeFile(parent,fileName);
            bis = new BufferedInputStream(is);
            bos = new BufferedOutputStream(new FileOutputStream(file));
            byte[] buff = new byte[8*1024];
            int len;
            while ( (len = bis.read(buff)) != -1) {
                bos.write(buff,0,len);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            closeInputStream(is,bis);
            closeOutputStream(bos);
        }

    }

    /**
     * 將bitmap儲存到外部儲存檔案
     * @param parent 目標檔案父目錄
     * @param fileName 檔名
     * @param bitmap 圖片
     */
    public void putBitmapToExternalStorage(File parent, String fileName, Bitmap bitmap){

        BufferedOutputStream bos = null;

        File bit = makeFile(parent,fileName);
        try {
            bos = new BufferedOutputStream(new FileOutputStream(bit));
            if (fileName.contains(".png") || fileName.contains(".PNG")) {
                bitmap.compress(Bitmap.CompressFormat.PNG, 100, bos);
            } else {
                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);
            }
            bos.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            closeOutputStream(bos);
        }

    }


    /**====================================讀取資料=================================================**/

    /**
     * 讀取外部檔案資料
     * @param path 檔案路徑
     * @return 檔案的位元組陣列
     */
    public byte[] getDataFromExternalStorage(String path){

        byte[] data = null;
        File file = new File(path);
        if (!file.exists()) return null;

        BufferedInputStream bis = null;
        ByteArrayOutputStream bos = null;
        try {
            bis = new BufferedInputStream(new FileInputStream(file));
            bos = new ByteArrayOutputStream();
            byte[] buff = new byte[8*1024];
            int len;
            while ((len = bis.read(buff)) != -1) {
                bos.write(buff,0,len);
            }
            data = bos.toByteArray();
            bos .flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            closeInputStream(bis);
            closeOutputStream(bos);
        }
        return data;
    }

    /**
     * 按行讀取檔案內容
     * @param path 檔案路徑
     * @return
     */
    public List<String> getStringFromExternalStorage(String path){

        File file = new File(path);
        if (!file.exists()) return null;

        List<String> data = new ArrayList<>();
        InputStreamReader isr = null;
        BufferedReader br = null;
        try {
            isr = new InputStreamReader(new FileInputStream(file),"utf-8");
            br = new BufferedReader(isr);
            data.add(br.readLine());
            isr.close();
            br.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return data;
    }

    /**====================================檔案拷貝操作=================================================**/

    /**
     * 單個檔案複製
     * @param oldFile 原檔案目錄
     * @param newFile 新檔案
     */
    public void copyFile(String oldFile,File newFile){

        File oldF = new File(oldFile);
        if(!oldF.exists()) return;

        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream(oldF);
            fos = new FileOutputStream(newFile);
            byte[] buff = new byte[8*1024];
            int len;
            while ((len = fis.read(buff)) != -1) {
                fos.write(buff,0,len);
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            closeInputStream(fis);
            closeOutputStream(fos);
        }
    }

    /**====================================資原始檔操作=================================================**/

   /**
     * 獲取raw目錄下檔案資料流 呼叫 getDataFromRaw(R.raw.mango)
     * @param resourceID R.raw.mango
     * @return
     */
    public InputStream getStreamFromRaw(int resourceID){

        InputStream in = null;
        try {
            in = mContext.get().getResources().openRawResource(resourceID);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return in;
    }

    /**
     * 獲取assert目錄下檔案資料流
     * 呼叫getDataFromAssets("mango.txt")
     * 如果多層目錄就要帶上父級目錄 getStreamFromAssets("today/day.txt")
     * @param fileName 檔案全名,包括字尾
     * @return 資料流
     */
    public InputStream getStreamFromAssets(String fileName){
        InputStream in = null;
        try {
            in = mContext.get().getResources().getAssets().open(fileName);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return in;
    }

    /**
     * 從資源目錄下讀取資料
     * @param is 資料流
     * @return
     */
    public byte[] getDataFromResource(InputStream is){

        if(is == null) return null;

        try {
            int lenght = is.available();
            byte[]  buffer = new byte[lenght];
            //將檔案中的資料讀到byte陣列中
            is.read(buffer);
            is.close();
            return buffer;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 將資原始檔拷貝到外部儲存
     * @param file 輸出目標檔案
     * @param is 資原始檔流
     */
    public  void moveResourceFileToExternalStorage(File file, InputStream is){

        if(is == null) return;
        FileOutputStream os = null;
        try {
            os = new FileOutputStream(file);
            byte[] buffer = new byte[2*1024];
            int len;
            while ((len = is.read(buffer)) != -1){
                os.write(buffer, 0, len);
            }
            os.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            closeInputStream(is);
            closeOutputStream(os);
        }

    }


    /**====================================檔案普通操作=================================================**/

	private List<File> fList = new ArrayList<>();

    public void clearFlist(){
        fList.clear();
    }
   
    /**
     * 給檔案重新命名
     * @param oldPath 原檔案
     * @param newPath 新檔案
     * @return
     */
    public boolean renameFile(String oldPath,String newPath){
        File oldFile = new File(oldPath);
        File newFile = new File(newPath);
        return oldFile.renameTo(newFile);
    }

    /**
     * 遍歷目錄
     * @param path 資料夾目錄
     * @return
     */
    public List<File> listFile(String path){

        File file = new File(path);
        if (!file.exists()) {
            return null;
        }
        File[] data = file.listFiles();
        if(data == null) return null;

        for(int i=0; i<data.length; i++){
            File child = data[i];
            if (child.isFile()) {
                fList.add(child);
            } else {
                listFile(child.getAbsolutePath());
            }
        }
        return fList;
    }

    /**
     *
     * @param path
     */
    public void delFile(String path){

        List<File> file = listFile(path);
        if(file == null)return;
        for(int i=0; i<file.size(); i++){
            file.get(i).delete();
        }
    }

    /**
     * 獲取檔案的檔名(不包括副檔名)
     */
    public String getFileNameWithoutExtension(String path) {
        if(path == null) {
            return null;
        }
        int separatorIndex = path.lastIndexOf(File.separator);
        if(separatorIndex < 0) {
            separatorIndex = 0;
        }
        int dotIndex = path.lastIndexOf(".");
        if(dotIndex < 0) {
            dotIndex = path.length();
        } else if(dotIndex < separatorIndex) {
            dotIndex = path.length();
        }
        return path.substring(separatorIndex + 1, dotIndex);
    }

    /**
     * 獲取檔名
     */
    public String getFileName(String path) {
        if(path == null) {
            return null;
        }
        int separatorIndex = path.lastIndexOf(File.separator);
        return (separatorIndex < 0) ? path : path.substring(separatorIndex + 1, path.length());
    }


    public void closeInputStream(InputStream... is){

        for (int i=0; i<is.length; i++){
            if (is[i] != null) {
                try {
                    is[i].close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    public void closeOutputStream(OutputStream... os){

        for (int i=0; i<os.length; i++){
            if (os[i] != null) {
                try {
                    os[i].close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    /**
     * 判斷sd卡是否處於就緒狀態 可讀可寫
     * MEDIA_UNKNOWN:未知狀態
     * MEDIA_REMOVED:移除狀態(外部儲存不存在)
     * MEDIA_UNMOUNTED:未裝載狀態(外部儲存存在但是沒有裝載)
     * MEDIA_CHECKING:磁碟檢測狀態
     * MEDIA_NOFS:外部儲存存在,但是磁碟為空或使用了不支援的檔案系統
     * MEDIA_MOUNTED:就緒狀態(可讀、可寫)
     * MEDIA_MOUNTED_READ_ONLY:只讀狀態
     * MEDIA_SHARED:共享狀態(外部儲存存在且正通過USB共享資料)
     * MEDIA_BAD_REMOVAL:異常移除狀態(外部儲存還沒有正確解除安裝就被移除了)
     * MEDIA_UNMOUNTABLE:不可裝載狀態(外部儲存存在但是無法被裝載,一般是磁碟的檔案系統損壞造成的)
     * @return
     */
    public boolean isSdCardMount(){
        return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
    }


    public String reviseFileSize(long size){
        String str="KB";
        float reviseSize = 0f;
        if(size>1024){
            reviseSize = size/1024f;
            if(reviseSize>1024){
                str="M";
                reviseSize = reviseSize/1024f;
                if (reviseSize>1024) {
                    str="G";
                    reviseSize = reviseSize/1024f;
                }
            }
        }

        DecimalFormat formatter=new DecimalFormat();
        formatter.setGroupingSize(3);
        String result = formatter.format(reviseSize) + str;
        return result;
    }

}

後續檔案其它操作再陸續補充

終於可以告一段落了

在這裡插入圖片描述