Android-存儲基礎(chǔ)

前言

存儲適配系列文章:

Android-存儲基礎(chǔ)
Android-10、11-存儲完全適配(上)
Android-10、11-存儲完全適配(下)
Android-FileProvider-輕松掌握

在持久化數(shù)據(jù)的時候锁施,一般都是選擇存入到文件里克锣,本篇將著重分析Android 存儲相關(guān)的知識,也是為Android 10.0 11存儲適配打基礎(chǔ)。
通過本篇文章添诉,你將了解到:

1梢褐、存儲劃分
2旺遮、內(nèi)部存儲
3、外部存儲
4盈咳、易混淆點說明

1耿眉、存儲劃分

Android 4.4 之前

在Android 4.4 之前,由于硬件發(fā)展受限鱼响,手機自身的存儲空間有限鸣剪,需要通過外置SD卡來擴展存儲空間。


image.png

如上圖,手機自身的存儲空間筐骇,稱之為機身存儲债鸡,在Android 4.4 之前作為內(nèi)部存儲使用。當(dāng)然內(nèi)部存儲空間一般是不夠用的铛纬,所以需要通過插入外置SD卡來擴充存儲空間娘锁,這當(dāng)做外部存儲。

Android 4.4之后

在Android 4.4 之后(含)饺鹃,手機機身存儲擴大了:


image.png

如上圖莫秆,機身存儲劃分為兩部分:

1、內(nèi)部存儲
2悔详、外部存儲

當(dāng)然镊屎,依然可以插入SD卡來擴充存儲空間,這部分的存儲空間稱為擴展的外部存儲空間茄螃。只是現(xiàn)在機身存儲都比較大缝驳,很少插入SD卡了。
接下來將以Android 4.4 之后的存儲劃分來分析具體的存儲方案归苍。

2用狱、內(nèi)部存儲

存放位置

回想一下平時使用的持久化方案:

1、SharedPreferences---->適用于存儲小文件
2拼弃、數(shù)據(jù)庫---->存儲結(jié)構(gòu)比較復(fù)雜的大文件

以上這些文件都是默認放在內(nèi)部存儲里夏伊。
"/" 表示根目錄,內(nèi)部存儲里給每個應(yīng)用按照其包名各自劃分了目錄吻氧,假設(shè)App的包名為:com.fish.myapplication
那么該文件在內(nèi)部存儲里的目錄為:
/data/user/0/com.fish.myapplication/

第一個"/"表示根目錄溺忧,其后每個"/"表示目錄分割符。
"0" 表示是第一個用戶盯孙,后續(xù)添加了多用戶則生成相應(yīng)的用戶目錄:


image.png

如上圖鲁森,新增了兩個用戶,生成的目錄分別是:"11"振惰、"12"歌溉。目前來說,很少開啟多用戶的骑晶。
一般來說痛垛,adb shell里是沒有權(quán)限查看/data目錄的。若要查看內(nèi)部存儲透罢,通常是通過Android Studio側(cè)邊欄Device File Explorer選擇對應(yīng)的目標設(shè)備查看榜晦。


image.png

同樣的冠蒋,如果包名為:com.fish.myapplication羽圃,則對應(yīng)的內(nèi)部存儲目錄為:
/data/data/com.fish.myapplication/

/data/user/0/com.fish.myapplication/ 會將值轉(zhuǎn)換到/data/data/com.fish.myapplication/ 路徑下。
每個App的內(nèi)部存儲空間僅允許自己訪問(除非有更高的權(quán)限,如root)朽寞,程序卸載后,該目錄也會被刪除。

存儲內(nèi)容

除了SharedPreferences鸳玩、數(shù)據(jù)庫文件缘琅,內(nèi)部存儲還存放了哪些文件呢?
為方便起見肘迎,只查看/data/data/目錄下的甥温。


image.png

剛開始有只有兩個空目錄。
當(dāng)進行寫入SharedPreferences妓布,創(chuàng)建數(shù)據(jù)庫姻蚓、寫入文件等操作后新增了幾個目錄:


image.png

大致介紹一下以上目錄作用:

1、cache-->存放緩存文件
2匣沼、code_cache-->存放運行時代碼優(yōu)化等產(chǎn)生的緩存
3狰挡、databases-->存放數(shù)據(jù)庫文件
4、files-->存放一般文件
5释涛、shared_prefs-->存放SharedPreferences 文件
6加叁、lib-->存放App依賴的so庫 是軟鏈接,指向/data/app/ 某個子目錄下

訪問方式

既然知道了各類文件存儲的目錄唇撬,那么如何讀寫這些文件呢它匕?
我們知道在Java 的世界里,操作文件有兩種方式:

字符流和字節(jié)流

以字節(jié)流為為例窖认,一個簡單的讀取寫入文件Demo:

    //寫入文件
    private void writeFile(String filePath) {
        if (TextUtils.isEmpty(filePath))
            return;

        try {
            File file = new File(filePath);
            FileOutputStream fileOutputStream = new FileOutputStream(file);
            BufferedOutputStream bos = new BufferedOutputStream(fileOutputStream);
            String writeContent = "hello world\n";
            bos.write(writeContent.getBytes());
            bos.flush();
            bos.close();

        } catch (Exception e) {

        }
    }

    //從文件讀取
    private void readFile(String filePath) {
        if (TextUtils.isEmpty(filePath))
            return;

        try {
            File file = new File(filePath);
            FileInputStream fileInputStream = new FileInputStream(file);
            BufferedInputStream bis = new BufferedInputStream(fileInputStream);
            byte[] readContent = new byte[1024];
            int readLen = 0;
            while (readLen != -1) {
                readLen = bis.read(readContent, 0, readContent.length);
                if (readLen > 0) {
                    String content = new String(readContent);
                    Log.d("test", "read content:" + content.substring(0, readLen));
                }
            }
            fileInputStream.close();
        } catch (Exception e) {

        }
    }

可以看出超凳,通過FileInputStream/FileOutputStream構(gòu)造函數(shù)傳入File對象即可實現(xiàn)文件讀寫,而File對象的構(gòu)造依賴于文件的存放路徑耀态,因此重點在于如何獲取文件的路徑轮傍。
分別說明各個目錄下文件的讀寫:
1、讀寫files目錄下文件

#Context.java
public abstract File getFilesDir();

使用方式:

    private String getFilePath(Context context) {
        //獲取files根目錄
        File fileDir = context.getFilesDir();
        //獲取文件
        File myFile = new File(fileDir, "myFile");
        return myFile.getAbsolutePath();
    }

context.getFilesDir()的結(jié)果是返回files目錄:

/data/user/0/com.fish.myapplication/files/

拿到對應(yīng)文件的File對象后首装,構(gòu)造相應(yīng)的輸入輸出流即可實現(xiàn)對該文件的讀寫创夜。可以看出仙逻,過程雖然簡單但是有點枯燥驰吓,因此Google將這些步驟封裝好了,直接返回對應(yīng)文件的FileOutputStream/FileInputStream:

#Context.java
    public abstract FileInputStream openFileInput(String name)
        throws FileNotFoundException;

    public abstract FileOutputStream openFileOutput(String name, @FileMode int mode)
        throws FileNotFoundException;

其中name 表示文件名系奉,mode表示訪問權(quán)限檬贰。

2、讀寫cache目錄下文件
與讀取files目錄相似:

#Context.java
public abstract File getCacheDir();

context.getCacheDir()的結(jié)果是返回cache目錄:

/data/user/0/com.fish.myapplication/cache/

3缺亮、讀寫shared_prefs目錄下文件
SharedPreferences 提供了簡易的快速持久化數(shù)據(jù)的方案翁涤。

    private void testSP(String fileName, String key, String value) {
        if (TextUtils.isEmpty(fileName) || TextUtils.isEmpty(key) || TextUtils.isEmpty(value))
            return;

        //構(gòu)造SP文件
        SharedPreferences sp = getSharedPreferences(fileName, MODE_PRIVATE);

        //寫入SP
        sp.edit().putString(key, value).commit();

        //讀取SP
        String myValue = sp.getString(key, "");
    }

其內(nèi)部也是使用了輸入輸出流,以寫入SP文件為例:

#SharedPreferencesImpl.java
    private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
        ...
        //構(gòu)造輸出流
        FileOutputStream str = createFileOutputStream(mFile);
        XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
        FileUtils.sync(str);
        str.close();
        ...
    }

4、讀寫數(shù)據(jù)庫目錄下文件
創(chuàng)建數(shù)據(jù)庫:

MyDatabaseHelper myDatabaseHelper = new MyDatabaseHelper(v.getContext(), "myDB", null, 10);

myDB是數(shù)據(jù)庫文件名葵礼。打開數(shù)據(jù)庫的相應(yīng)表号阿,即可讀寫數(shù)據(jù)。
獲取數(shù)據(jù)庫文件路徑:

#Context.java
Context.public abstract File getDatabasePath(String name);

獲取結(jié)果如下:

/data/user/0/com.fish.myapplication/databases/myDB

5鸳粉、讀寫code_cache目錄下文件

#Context.java API>=21
public abstract File getCodeCacheDir();

獲取結(jié)果如下:

/data/user/0/com.fish.myapplication/code_cache/

以上是分別列舉了各個子目錄/文件的獲取方式扔涧,如果想獲取:/data/user/0/com.fish.myapplication/届谈,可通過:

#Context.java
public abstract File getDataDir();

該方法需要API>=24枯夜。

3、外部存儲

外部存儲分為兩部分:自帶外部存儲和擴展外部存儲(外置SD卡)

A艰山、自帶外部存儲存儲

存放位置

存儲的根目錄是:"/"卤档。
根目錄下幾個需要關(guān)注的目錄:

/data/
/sdcard/
/storage/

其中/data/目錄前面已經(jīng)分析過。

/sdcard/是軟鏈接程剥,指向/storage/self/primary
而/storage/下有幾個目錄:


image.png

/storage/self/primary/是軟鏈接劝枣,指向/storage/emulated/0/

也就是說/sdcard/、/storage/self/primary/ 真正指向的是/storage/emulated/0/

存儲內(nèi)容

image.png

如上圖所示织鲸,/sdcard/目錄下的子目錄看起來都比較眼熟舔腾。
這些子目錄分為分為三部分:

第一部分:共享存儲空間

也就是所有App共享的部分,比如相冊搂擦、音樂稳诚、鈴聲、文檔等瀑踢。
共享存儲空間按文件類型又分為兩部分:
1扳还、媒體文件

  • DCIM/ 和 Pictures/-->存儲圖片
  • DCIM/、Movies/ 和 Pictures-->存儲視頻
  • Alarms/橱夭、Audiobooks/氨距、Music/、Notifications/棘劣、Podcasts/ 和 Ringtones/-->存儲音頻文件
  • Download/-->下載的文件

2俏让、文檔和其它文件

Documents-->存儲如.pdf類型等文件

第二部分:App外部私有目錄

  • Android/data/--->存儲各個App的外部私有目錄
    與內(nèi)部存儲類似,命名方式是:Android/data/xx------>xx指應(yīng)用的包名茬暇。
    如:/sdcard/Android/data/com.fish.myapplication

第三部分:其它目錄

比如各個App在/sdcard/目錄下創(chuàng)建的目錄首昔,如支付寶創(chuàng)建的目錄:alipy/,微博創(chuàng)建的目錄:com.sina.weibo/糙俗,qq創(chuàng)建的目錄:com.tencent.mobileqq/等勒奇。

訪問方式

與訪問內(nèi)部存儲文件類似,外部存儲也可以通過構(gòu)造輸入輸出流訪問文件巧骚。

讀寫共享存儲空間

視頻赊颠、圖片等可能分散存儲在各個不同的目錄里格二,如果想要獲取所有的圖片地址,那么得需要遍歷不同的目錄尋找巨税,效率顯而易見的低。Android 將視頻粉臊、圖片等信息存儲在數(shù)據(jù)庫里草添,每當(dāng)某個App想要訪問這些共享的媒體文件時只需要查找數(shù)據(jù)庫對應(yīng)的表,讀取符合條件的行扼仲,找出每個媒體的文件路徑等信息远寸。
App查詢共享存儲空間的媒體方式是:通過ContentProvider訪問。

訪問媒體文件
以查詢圖片為例:

    private void getImagePath(Context context) {
        ContentResolver contentResolver = context.getContentResolver();
        Cursor cursor = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, null);
        while(cursor.moveToNext()) {
            String imagePath = cursor.getString(cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA));
        }
    }

查詢到圖片的地址屠凶,當(dāng)然就可以展示圖片了驰后。

訪問文檔和其它文件
Storage Access Framework 簡稱SAF:存儲訪問框架
以查看.pdf文件為例:

    private void startSAF() {
        Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        intent.setType("application/pdf");
        startActivityForResult(intent, 100);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == 100) {
            Uri uri = data.getData();
        }
    }

SAF實際上就是調(diào)用系統(tǒng)提供的選擇器,選中后在onActivityResult(xx)里接收結(jié)果矗愧,拿到Uri后當(dāng)然就可以讀寫對應(yīng)的文件了灶芝。

讀寫App外部私有目錄

剛開始并沒有自己App的包名。


image.png

調(diào)用如下方法后:

    private void testAppDir(Context context) {
        //4個基本方法
        File fileDir = context.getExternalFilesDir(null);
        //API>=19
        File[] fileList = context.getExternalFilesDirs(null);

        File cacheDir = context.getExternalCacheDir();
        //API>=19
        File[] cacheList = context.getExternalCacheDirs();

        //指定目錄唉韭,自動生成對應(yīng)的子目錄
        File fileDir2 = context.getExternalFilesDir(Environment.DIRECTORY_DCIM);
    }

再查看目錄樹:


image.png

可以看出再/sdcard/Android/data/目錄下生成了com.fish.myapplication/目錄夜涕,該目錄下有兩個子目錄分別是:files/、cache/属愤。當(dāng)然也可以選擇創(chuàng)建其它目錄女器。
2、App卸載的時候住诸,兩者都會被清除驾胆。

讀寫其它目錄

只要拿到根目錄,就可以遍歷尋找其它子目錄/文件贱呐。

    private void testOtherDir(Context context) {
        File rootDir = Environment.getExternalStorageDirectory();
    }

返回的rootDir路徑:/storage/emulated/0/丧诺。

B、擴展外部存儲(外置SD卡)

存儲位置

當(dāng)給設(shè)備插入SD卡后奄薇,查看其目錄:
/sdcard/ 依然指向/storage/self/primary锅必,繼續(xù)來看/storage/:


image.png

可以看出,多了sdcard1惕艳,軟鏈接指向了/storage/77E4-07E7/搞隐。

存儲內(nèi)容

取決于SD卡上裝了什么東西。

訪問方式

還記得上面獲取外部存儲-App私有目錄方式嗎远搪?

File[] fileList = context.getExternalFilesDirs(null);

返回File對象數(shù)組劣纲,當(dāng)有多個外部存儲時候,存儲在數(shù)組里谁鳍。


image.png

返回的數(shù)組有兩個元素癞季,一個是自帶外部存儲存儲劫瞳,另一個是剛插入的SD卡。
拿到路徑后绷柒,當(dāng)然就可以訪問相應(yīng)的文件了志于。

4、易混淆點說明

以上分別闡述了內(nèi)部存儲废睦、自帶外部存儲伺绽、擴展外部存儲等,這幾者關(guān)系如下:


image.png

其中比較容易混淆的是:
內(nèi)部存儲與外部存儲里的App私有目錄嗜湃,兩者命名風(fēng)格很像奈应。

不同點:

/data/data/com.fish.myapplication/ 位于內(nèi)部存儲,一般用于存儲容量較小的购披,私密性較強的文件杖挣。而/sdcard/Android/data/com.fish.myapplication/ 位于外部存儲,作為App私有目錄刚陡,一般用于存儲容量較大的文件惩妇,即使刪除了也不影響App正常功能。

相同點:

1筐乳、屬于App專屬屿附,App自身訪問兩者無需任何權(quán)限。
2哥童、App卸載后挺份,兩者皆被刪除。
3贮懈、兩者目錄下增加的文件最終會被統(tǒng)計到"設(shè)置->存儲和緩存"里匀泊。

另外,常見的在設(shè)置里的"存儲與緩存"項:


image.png

當(dāng)點擊"Clear cache" 時:

內(nèi)部存儲/data/data/com.fish.myapplication/cache/朵你、 /data/data/com.fish.myapplication/code_cache/目錄會被清空
外部存儲/sdcard/Android/data/com.fish.myapplication/cache/ 會被清空

當(dāng)點擊"Clear storage" 時:

內(nèi)部存儲/data/data/com.fish.myapplication/下除了lib/各聘,其余子目錄皆被刪除
外部存儲/sdcard/Android/data/com.fish.myapplication/被清空
\color{Red}{注:該功能慎用,因為會刪除用戶數(shù)據(jù)庫抡医,SP文件等躲因,相當(dāng)于重置了App}

接下來將分析Android 10.0 11 存儲適配。
本文基于Android 10.0忌傻。

您若喜歡大脉,請點贊、關(guān)注水孩,您的鼓勵是我前進的動力

持續(xù)更新中镰矿,和我一起步步為營系統(tǒng)、深入學(xué)習(xí)Android

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末俘种,一起剝皮案震驚了整個濱河市秤标,隨后出現(xiàn)的幾起案子绝淡,更是在濱河造成了極大的恐慌,老刑警劉巖苍姜,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件牢酵,死亡現(xiàn)場離奇詭異,居然都是意外死亡衙猪,警方通過查閱死者的電腦和手機馍乙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來屈嗤,“玉大人潘拨,你說我怎么就攤上這事吊输∪暮牛” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵季蚂,是天一觀的道長茫船。 經(jīng)常有香客問我,道長扭屁,這世上最難降的妖魔是什么算谈? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮料滥,結(jié)果婚禮上然眼,老公的妹妹穿的比我還像新娘。我一直安慰自己葵腹,他們只是感情好高每,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著践宴,像睡著了一般鲸匿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上阻肩,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天带欢,我揣著相機與錄音,去河邊找鬼烤惊。 笑死乔煞,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的柒室。 我是一名探鬼主播瘤缩,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼伦泥!你這毒婦竟也來了剥啤?” 一聲冷哼從身側(cè)響起锦溪,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎府怯,沒想到半個月后刻诊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡牺丙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年则涯,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片冲簿。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡粟判,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出峦剔,到底是詐尸還是另有隱情档礁,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布吝沫,位于F島的核電站呻澜,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏惨险。R本人自食惡果不足惜羹幸,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望辫愉。 院中可真熱鬧栅受,春花似錦、人聲如沸恭朗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冀墨。三九已至闸衫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間诽嘉,已是汗流浹背蔚出。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留虫腋,地道東北人骄酗。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像悦冀,于是被迫代替她去往敵國和親趋翻。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容