1. 帶你徹底搞懂 Android 存儲(chǔ)?。ńㄗh收藏)

        共 18167字,需瀏覽 37分鐘

         ·

        2021-08-28 00:18

        微信改了推動(dòng)機(jī)制,真愛(ài)請(qǐng)星標(biāo)本公號(hào)
        公眾號(hào)回復(fù)加入BATcoder技術(shù)群BAT

        作者:小魚(yú)人愛(ài)編程  
        鏈接:https://www.jianshu.com/p/93c9f5e2d2a7

        在持久化數(shù)據(jù)的時(shí)候,一般都是選擇存入到文件里,本篇將著重分析Android 存儲(chǔ)相關(guān)的知識(shí), 通過(guò)本篇文章,你將了解到:

        1. 存儲(chǔ)劃分
        2. 內(nèi)部存儲(chǔ)
        3. 外部存儲(chǔ)
        4. 易混淆點(diǎn)說(shuō)明

        1. 存儲(chǔ)劃分

        1.1 Android 4.4 之前

        在A(yíng)ndroid 4.4 之前,由于硬件發(fā)展受限,手機(jī)自身的存儲(chǔ)空間有限,需要通過(guò)外置SD卡來(lái)擴(kuò)展存儲(chǔ)空間。

        如上圖,手機(jī)自身的存儲(chǔ)空間,稱(chēng)之為機(jī)身存儲(chǔ),在A(yíng)ndroid 4.4 之前作為內(nèi)部存儲(chǔ)使用。當(dāng)然內(nèi)部存儲(chǔ)空間一般是不夠用的,所以需要通過(guò)插入外置SD卡來(lái)擴(kuò)充存儲(chǔ)空間,這當(dāng)做外部存儲(chǔ)。

        1.2 Android 4.4之后

        在A(yíng)ndroid 4.4 之后(含),手機(jī)機(jī)身存儲(chǔ)擴(kuò)大了:

        如上圖,機(jī)身存儲(chǔ)劃分為兩部分:內(nèi)部存儲(chǔ)外部存儲(chǔ)

        當(dāng)然,依然可以插入SD卡來(lái)擴(kuò)充存儲(chǔ)空間,這部分的存儲(chǔ)空間稱(chēng)為擴(kuò)展的外部存儲(chǔ)空間。只是現(xiàn)在機(jī)身存儲(chǔ)都比較大,很少插入SD卡了。

        接下來(lái)將以Android 4.4 之后的存儲(chǔ)劃分來(lái)分析具體的存儲(chǔ)方案。

        2. 內(nèi)部存儲(chǔ)

        2.1 存儲(chǔ)位置

        回想一下平時(shí)使用的持久化方案:

        • SharedPreferences:適用于存儲(chǔ)小文件
        • 數(shù)據(jù)庫(kù):存儲(chǔ)結(jié)構(gòu)比較復(fù)雜的大文件

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

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

        如上圖,新增了兩個(gè)用戶(hù),生成的目錄分別是:"11"、"12"。目前來(lái)說(shuō),很少開(kāi)啟多用戶(hù)的。一般來(lái)說(shuō),adb shell 里是沒(méi)有權(quán)限查看/data目錄的。若要查看內(nèi)部存儲(chǔ),通常是通過(guò) Android Studio側(cè)邊欄Device File Explorer選擇對(duì)應(yīng)的目標(biāo)設(shè)備查看。

        同樣的,如果包名為:com.fish.myapplication,則對(duì)應(yīng)的內(nèi)部存儲(chǔ)目錄為:/data/data/com.fish.myapplication//data/user/0/com.fish.myapplication/ 會(huì)將值轉(zhuǎn)換到/data/data/com.fish.myapplication/路徑下。每個(gè)App的內(nèi)部存儲(chǔ)空間僅允許自己訪(fǎng)問(wèn)(除非有更高的權(quán)限,如root),程序卸載后,該目錄也會(huì)被刪除。

        2.2 存儲(chǔ)內(nèi)容

        除了SharedPreferences、數(shù)據(jù)庫(kù)文件,內(nèi)部存儲(chǔ)還存放了哪些文件呢?為方便起見(jiàn),只查看/data/data/目錄下的。

        剛開(kāi)始有只有兩個(gè)空目錄。當(dāng)進(jìn)行寫(xiě)入SharedPreferences,創(chuàng)建數(shù)據(jù)庫(kù)、寫(xiě)入文件等操作后新增了幾個(gè)目錄:

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

        目錄 用途
        cache 存放緩存文件
        code_cache 存放運(yùn)行時(shí)代碼優(yōu)化等產(chǎn)生的緩存
        databases 存放數(shù)據(jù)庫(kù)文件
        files 存放一般文件
        shared_prefs 存放 SharedPreferences 文件
        lib 存放App依賴(lài)的so庫(kù) 是軟鏈接,指向/data/app/ 某個(gè)子目錄下

        2.3 訪(fǎng)問(wèn)方式

        既然知道了各類(lèi)文件存儲(chǔ)的目錄,那么如何讀寫(xiě)這些文件呢?我們知道在Java 的世界里,操作文件有兩種方式:字符流和字節(jié)流

        以字節(jié)流為為例,一個(gè)簡(jiǎn)單的讀取寫(xiě)入文件Demo:

            //寫(xiě)入文件
            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) {

                }
            }

        可以看出,通過(guò) FileInputStream/FileOutputStream 構(gòu)造函數(shù)傳入 File 對(duì)象即可實(shí)現(xiàn)文件讀寫(xiě),而 File 對(duì)象的構(gòu)造依賴(lài)于文件的存放路徑,因此重點(diǎn)在于如何獲取文件的路徑。分別說(shuō)明各個(gè)目錄下文件的讀寫(xiě):

        2.3.1 讀寫(xiě)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/

        拿到對(duì)應(yīng)文件的File對(duì)象后,構(gòu)造相應(yīng)的輸入輸出流即可實(shí)現(xiàn)對(duì)該文件的讀寫(xiě)??梢钥闯?,過(guò)程雖然簡(jiǎn)單但是有點(diǎn)枯燥,因此Google將這些步驟封裝好了,直接返回對(duì)應(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表示訪(fǎng)問(wèn)權(quán)限。

        2.3.2 讀寫(xiě)cache目錄下文件

        與讀取files目錄相似:

        #Context.java
        public abstract File getCacheDir()
        ;

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

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

        2.3.3 讀寫(xiě)shared_prefs目錄下文件

        SharedPreferences 提供了簡(jiǎn)易的快速持久化數(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);

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

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

        其內(nèi)部也是使用了輸入輸出流,以寫(xiě)入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();
                ...
            }
            

        2.3.4 讀寫(xiě)數(shù)據(jù)庫(kù)目錄下文件

        創(chuàng)建數(shù)據(jù)庫(kù):

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

        myDB是數(shù)據(jù)庫(kù)文件名。打開(kāi)數(shù)據(jù)庫(kù)的相應(yīng)表,即可讀寫(xiě)數(shù)據(jù)。

        獲取數(shù)據(jù)庫(kù)文件路徑:

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

        獲取結(jié)果如下:

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

        2.3.5 讀寫(xiě)code_cache目錄下文件

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

        獲取結(jié)果如下:

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

        以上是分別列舉了各個(gè)子目錄/文件的獲取方式,如果想獲?。?code style="">/data/user/0/com.fish.myapplication/,可通過(guò):

        #Context.java
        public abstract File getDataDir()
        ;

        該方法需要 API>=24。

        3. 外部存儲(chǔ)

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

        3.1 自帶外部存儲(chǔ)存儲(chǔ)

        3.1.1 存儲(chǔ)位置

        自帶外部存儲(chǔ)的根目錄是:"/"。

        根目錄下幾個(gè)需要關(guān)注的目錄:

        • /data/
        • /sdcard/
        • /storage/

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

        /sdcard/是軟鏈接,指向/storage/self/primary

        /storage/下有幾個(gè)目錄:

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

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

        3.1.2 存儲(chǔ)內(nèi)容

        自帶外部存儲(chǔ)主要有以下內(nèi)容:

        如上圖所示,/sdcard/目錄下的子目錄看起來(lái)都比較眼熟。這些子目錄分為分為三部分:

        第一部分:共享存儲(chǔ)空間

        也就是所有App共享的部分,比如相冊(cè)、音樂(lè)、鈴聲、文檔等。共享存儲(chǔ)空間按文件類(lèi)型又分為兩部分:

        1. 媒體文件
        目錄 用途
        DCIM/ 和 Pictures/ 存儲(chǔ)圖片
        DCIM/、Movies/ 和 Pictures 存儲(chǔ)視頻
        Alarms/、Audiobooks/、Music/、Notifications/、Podcasts/ 和 Ringtones/ 存儲(chǔ)音頻文件
        Download/ 下載的文件
        1. 文檔和其它文件
        目錄 用途
        Documents 存儲(chǔ)如.pdf類(lèi)型等文件

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

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

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

        第三部分:其它目錄

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

        3.1.3 訪(fǎng)問(wèn)方式

        與訪(fǎng)問(wèn)內(nèi)部存儲(chǔ)文件類(lèi)似,外部存儲(chǔ)也可以通過(guò)構(gòu)造輸入輸出流訪(fǎng)問(wèn)文件。

        讀寫(xiě)共享存儲(chǔ)空間

        視頻、圖片等可能分散存儲(chǔ)在各個(gè)不同的目錄里,如果想要獲取所有的圖片地址,那么得需要遍歷不同的目錄尋找,效率顯而易見(jiàn)的低。Android 將視頻、圖片等信息存儲(chǔ)在數(shù)據(jù)庫(kù)里,每當(dāng)某個(gè)App想要訪(fǎng)問(wèn)這些共享的媒體文件時(shí)只需要查找數(shù)據(jù)庫(kù)對(duì)應(yīng)的表,讀取符合條件的行,找出每個(gè)媒體的文件路徑等信息。

        App查詢(xún)共享存儲(chǔ)空間的媒體方式是:通過(guò)ContentProvider訪(fǎng)問(wèn)。

        • 訪(fǎng)問(wèn)媒體文件

        以查詢(xún)圖片為例:

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

        查詢(xún)到圖片的地址,當(dāng)然就可以展示圖片了。

        • 訪(fǎng)問(wèn)文檔和其它文件Storage Access Framework 簡(jiǎn)稱(chēng)SAF:存儲(chǔ)訪(fǎng)問(wèn)框架

        以查看.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實(shí)際上就是調(diào)用系統(tǒng)提供的選擇器,選中后在 onActivityResult(xx) 里接收結(jié)果,拿到Uri 后當(dāng)然就可以讀寫(xiě)對(duì)應(yīng)的文件了。

        讀寫(xiě)App外部私有目錄

        剛開(kāi)始并沒(méi)有自己App的包名。

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

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

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

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

        再查看目錄樹(shù):

        可以看出再/sdcard/Android/data/目錄下生成了com.fish.myapplication/目錄,該目錄下有兩個(gè)子目錄分別是:files/cache/。當(dāng)然也可以選擇創(chuàng)建其它目錄。

        App卸載的時(shí)候,兩者都會(huì)被清除。

        讀寫(xiě)其它目錄

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

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

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

        3.2 擴(kuò)展外部存儲(chǔ)(外置SD卡)

        3.2.1 存儲(chǔ)位置

        當(dāng)給設(shè)備插入SD卡后,查看其目錄:

        /sdcard/ 依然指向 /storage/self/primary,繼續(xù)來(lái)看/storage/:

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

        3.2.2 存儲(chǔ)內(nèi)容

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

        3.2.3 訪(fǎng)問(wèn)方式

        還記得上面獲取外部存儲(chǔ)-App私有目錄方式嗎?

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

        返回File對(duì)象數(shù)組,當(dāng)有多個(gè)外部存儲(chǔ)時(shí)候,存儲(chǔ)在數(shù)組里。

        返回的數(shù)組有兩個(gè)元素,一個(gè)是自帶外部存儲(chǔ)存儲(chǔ),另一個(gè)是剛插入的SD卡。拿到路徑后,當(dāng)然就可以訪(fǎng)問(wèn)相應(yīng)的文件了。

        4. 易混淆點(diǎn)說(shuō)明

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

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

        4.1 不同點(diǎn)

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

        4.2 相同點(diǎn)

        1. 屬于A(yíng)pp專(zhuān)屬,App自身訪(fǎng)問(wèn)兩者無(wú)需任何權(quán)限。
        2. App卸載后,兩者皆被刪除。
        3. 兩者目錄下增加的文件最終會(huì)被統(tǒng)計(jì)到"設(shè)置->存儲(chǔ)和緩存"里。

        另外,常見(jiàn)的在設(shè)置里的"存儲(chǔ)與緩存"項(xiàng):

        當(dāng)點(diǎn)擊"Clear cache" 時(shí):

        • 內(nèi)部存儲(chǔ)/data/data/com.fish.myapplication/cache//data/data/com.fish.myapplication/code_cache/目錄會(huì)被清空
        • 外部存儲(chǔ)/sdcard/Android/data/com.fish.myapplication/cache/ 會(huì)被清空

        當(dāng)點(diǎn)擊"Clear storage" 時(shí):

        • 內(nèi)部存儲(chǔ)/data/data/com.fish.myapplication/下除了lib/,其余子目錄皆被刪除
        • 外部存儲(chǔ)/sdcard/Android/data/com.fish.myapplication/被清空

        注:該功能慎用,因?yàn)闀?huì)刪除用戶(hù)數(shù)據(jù)庫(kù),SP文件等,相當(dāng)于重置了App




        ·················END·················

        推薦閱讀

        ? 耗時(shí)2年,Android進(jìn)階三部曲第三部《Android進(jìn)階指北》出版!

        ? 『BATcoder』做了多年安卓還沒(méi)編譯過(guò)源碼?一個(gè)視頻帶你玩轉(zhuǎn)!

        ? 『BATcoder』我去!安裝Ubuntu還有坑?

        ? 重生!進(jìn)階三部曲第一部《Android進(jìn)階之光》第2版 出版!

        BATcoder技術(shù)群,讓一部分人先進(jìn)大廠(chǎng)

        大家,我是劉望舒,騰訊TVP,著有三本業(yè)內(nèi)知名暢銷(xiāo)書(shū),連續(xù)四年蟬聯(lián)電子工業(yè)出版社年度優(yōu)秀作者,谷歌開(kāi)發(fā)者社區(qū)特邀講師,百度百科收錄的高級(jí)技術(shù)專(zhuān)家。

        前華為技術(shù)專(zhuān)家,現(xiàn)大廠(chǎng)技術(shù)負(fù)責(zé)人。

        想要加入 BATcoder技術(shù)群,公號(hào)回復(fù)BAT 即可。

        為了防止失聯(lián),歡迎關(guān)注我的小號(hào)

                  
          微信改了推送機(jī)制,真愛(ài)請(qǐng)星標(biāo)本公號(hào)
        瀏覽 67
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 亚州不卡| 色狗av影院 | 国产精品操逼网站 | 26uuu国产电影一区二区 | mm131亚洲精品一区二区 |