Android中數據存儲與文件存儲

前言

Android提供了多種選項來保存永久性應用數據。用戶可以選擇的數據存儲選項有:共享首選項、內部存儲、外部存儲、SQLite數據庫、網絡存儲。

文章開始前用一張圖表示共享首選項,內部存儲,外部存儲

微信截圖_20190305114614.png

一、使用共享首選項

1、關鍵類:SharedPreferences類

可以使用ShatedPreferences來保存任何原始數據:布爾值、浮點值、整型值、長整型、字符串。
這些數據將會跨多個用戶會話永久保留。

2、介紹如何獲得SharedPreferences對象

2.1 、(1)getSharedPreferences(String name,int mode);

通過Context調用,參數一:首選項文件名,如果此名稱的首選項文件不存在,那么會在再檢索編輯器SharedPreferences.edit()并提交更改(Editor.commit())時創建這個文件。
參數二默認填寫0或者是MODE_PRIVATE(創建的文件只能由調用應用程序訪問),別的模式MODE_WORLD_READABLE(創建全都可讀文件,容易造成安全漏洞,已在17棄用)與MODE_WORLD_WRITEABLE(創建全部課讀寫文件,容易造成安全漏洞,已在17棄用)與MODE_MULTI_PROCESS(在某些版本的Android中無法可靠運行,不提供任何機制來協調跨進程的并發修改,已在23棄用)

2.2 、getPreferences(int mode);

在Activity中調用,參數參照上面,一般用0就可以。
調用SharedPreferences.edit()方法并最后提交更改(Editor.commit())時候創建文件,文件名就是以當前Activity的類名,比較簡單的基于當前Activity創建其相應的SharedPreferences文件。

3、詳細介紹

其應該是一個interface,放在android.content.SharedPreferences包下。
其嵌套了接口Editor與接口 OnShatedPreferenceChangeListener。
通過SharedPreferences對象可以調用如下方法:
3.1 ->>>

contains(String key)
返回值:boolean
檢驗當前SharedPreferences文件中是否包含某個key 

3.2->>>

getAll()
返回值:Map<String,?>
返回當前SharedPreferences文件中所有的key以及相應的value值

示例:

        Map<String,?> map = mSharedPreferences.getAll();
        這樣此SharedPreferences中所有的值都在Map集合中

3.3->>>

getBoolean(String key,boolean defValue)
返回值:boolean
得到此SharedPreferences中boolean類型的Key對應的value,其中參數二表示默認值。
需要說明的是,如果此key 對應的值不是布爾值,那么就會拋出ClassCastException異常,如果不處理,程序會崩潰,所以在使用查詢的時候:

示例

 try {
            int i = mSharedPreferences.getInt("age", 1);
            mTextViewa.setText("" + i);
        } catch (ClassCastException e) {
             mTextViewa.setText("出現異常了");
        }

3.4->>>

getFloat(String key,float defValue)
返回值:float
與getBoolean方法一樣,不同的是得到的是float值,針對的是float類型的數據查詢,如果傳入的Key對應的值不是vlaue,那么也會拋出ClassCastException異常。

3.5->>>

getInt(String key,int defValue)
返回值:int
與getBoolean方法一樣,不同的是得到的是int值,如果傳入的key查詢出來不是int類型的,那么也會拋出ClassCastException異常。

3.6->>>

getLong(String key ,long defValue)
返回值:long
與getBoolean方法一樣,不同的是得到的是long值,如果傳入的key查詢出來不是int類型的,那么也會拋出ClassCastException異常。

3.7->>>

getString(String key,String defValue)
與getBoolean方法一樣,不同的是得到的是String值,如果傳入的key查詢出來不是int類型的,那么也會拋出ClassCastException異常,defVlue也可以是null

3.8->>>

getStringSet(String key,Set<String> defValues)
返回值:Set<String>
defValues可以是null,也會拋出ClassCastException,另外,存入的是Set<String>,也就是一組String數據。

3.9->>>

registerOnSharedPreferenceChangeListener(SharedPreferences.OnSharedPreferenceChangeListener listener)
警告:首選項管理器當前不存儲對偵聽器的強引用。 您必須存儲對偵聽器的強引用,否則它將容易被垃圾收集。 只要您需要偵聽器
,我們建議您在對象的實例數據中保留對偵聽器的引用。
此監聽發生在此SharedPreferences文件內容發生更改的時候。

3.10->>>

unregisterOnSharedPreferencesChangeListener(SharedPreferences.OnSharedPreferenceChangeListener listener)
取消監聽

3.11->>>

edit()
返回值:SharedPreferences.Editor
具體作用參照下面Editor中方法具體介紹。

4介紹SharedPreferences中的Editor中方法

操作當前SharedPreferences中文件中的值,只有通過commit或者apply之后,才會建立相應的SharedPreferences文件與完成相應的修改。
4.1 ->>>

putBoolean(String key,boolean value)
返回值:SharedPreferences.Editor
寫入到當前SharedPreferences文件中一對key-value或者是修改已經存在key對應的value的值。

4.2 ->>>

putFloat(String key,float value)
返回值:SharedPreferences.Editor
寫入到當前SharedPreferences文件中一對key-value或者是修改已經存在key對應的value的值。

4.3 ->>>

putInt(String key,int value)
返回值:SharedPreferences.Editor
寫入到當前SharedPreferences文件中一對key-value或者是修改已經存在key對應的value的值。

4.4 ->>>

putLong(String key,long value)
返回值:SharedPreferences.Editor
寫入到當前SharedPreferences文件中一對key-value或者是修改已經存在key對應的value的值。

4.5 ->>>

putString(String key,String value)
返回值:SharedPreferences.Editor
寫入到當前SharedPreferences文件中一對key-value或者是修改已經存在key對應的value的值。

4.6 ->>>

putStringSet(String key,Set<String> values)
返回值:SharedPreferences.Editor
寫入到當前SharedPreferences文件中一對key-value或者是修改已經存在key對應的value的值。
對此參數傳遞null等價于使用此鍵調用remove(String)

4.7 ->>>

remove(String key)
返回值:SharedPreferences.Editor
移除當前SharedPreferences中某個Key以及其所代表的值

4.8 ->>>

clear()
返回值:SharedPreferences.Editor
移除所有的key以及value從當前SharedPreferences文件中。

4.9 ->>>

commit()
返回值:boolean
如果新值成功寫入當前SharedPreferences,則返回true;
會自動執行請求的參數,替換SharedPreferences中的修改,如果沒有的話,就會創建此文件
當兩個編輯器同時修改時,最后一個調用commit的成功
如果你不關心返回值,并且你在應用程序的主線程中使用它,請使用apply();

4.10 ->>>

apply()
返回值:void
1、如果先后apply()了幾次,那么會以最后一次apply()的為準。 
2、commit()是把內容同步提交到硬盤的。而apply()先立即把修改提交到內存,然后開啟一個異步的線程提交到硬盤,
并且如果提交失敗,你不會收到任何通知。 
也就是說,commit有返回值,apply沒有返回值,commit寫入文件操作是在主線程中,會消耗資源,apply寫入文件的操作是異步的,
會把Runnable放到線程池中執行
3、如果當一個apply()的異步提交還在進行的時候,執行commit()操作,那么commit()是會阻塞的。
而如果commit()的時候,前面的commit()還未結束,這個commit()還是會阻塞的。(所以引起commit阻塞會有這兩種原因) 
4、由于SharePreferences在一個程序中的實例一般都是單例的,所以如果你不是很在意返回值的話,你使用apply()代替commit()是無所謂的。

5、SP例子

(1)初始一個應用在data/data/應用包名下:


初始未進行操作時候應用的樣子.png

當你使用了SharedPreferences時候

public static final String FILE_NAME = "MySharedPreferences";
 mSharedPreferences = getSharedPreferences(FILE_NAME, 0);
 //執行完上述話,會在應用程序中建立shared_prefs文件夾,里面將要存放sharedPreferences文件,但是目前沒有內容

(2)當你完成一些查詢操作,但是沒有完成使用Editor

mSharedPreferences = getSharedPreferences(FILE_NAME, 0);  
String name = mSharedPreferences.getString("name","xlj");
 //也沒有在shared_prefs文件夾下建立相應的FILE_NAME文件,另外這樣用也不會報錯,返回name = xlj

(3)通過操作Editor

mSharedPreferences = getSharedPreferences(FILE_NAME, 0);  
SharedPreferences.Editor editor = mSharedPreferences.edit();
        editor.putString("name", "xljs");
        editor.commit();
建立相應的SharedPreferences.png

(1)演示第二種得到SharedPerferences方法:

       mSharedPreferences = getPreferences(0);
        SharedPreferences.Editor editor = mSharedPreferences.edit();
        editor.putString("age", "10");
        editor.putBoolean("is", true);
        editor.commit();

        Map<String,?> map = mSharedPreferences.getAll();

        mTextViewa.setText(map.toString());
        try {
            int i = mSharedPreferences.getInt("age", 1);
            mTextViewa.setText("" + i);
        } catch (ClassCastException e) {
             mTextViewa.setText("出現異常了");
        }

(2)當前Activity名字:SharedPActivity

使用第二種方法.png

在手機上使用應用的內部存儲

直接在設備的內部存儲中保存文件。默認情況下,保存到內部存儲的文件是應用的私有文件,其他應用(和用戶)都不能訪問這些文件。當用戶卸載應用的時候,這些文件也會被移除。并且這些文件理論上是不可見的

簡單來講,就是給我們自己的應用,在內部存儲分配的空間,默認的有files文件夾、cache文件夾、
(其實共享首選項也是提供的默認的shared_prefs文件夾)
當然了,用戶也可以不用這些已經提供的files 與 cache文件夾,自己建立自己的的文件夾,并放文件.

一、讀寫files文件夾(根文件夾)下文件

1、openFileOutput()方法

屬于android.content.Context;
返回值:FileOutputStram一個文件輸出流
參數一:name,你要打開的文件的名字,這里的name只能是名字,不能使用"/name"此類的。
參數二:MODE_PRIVATE,別的模式處于安全都摒棄了
不同于常見getxxx方法,字面翻譯就是:打開files文件夾下某個文件的輸出流
簡單的方式得到一個文件輸出流

1.1例子

初始默認的應用程序內部存儲如下圖:


初始.png

shared_prefs文件夾是因為操作過SharedPreferences才有的,默認的初始就有一個空的cache文件夾。

                  private String FILE_NAME = "hello_file";
                  private String FILE_NAME_NEW = "test.txt";

                  String string = "怎么都是寫不會的啊";
                try {
                    FileOutputStream fileOutputStream = openFileOutput(FILE_NAME, Context.MODE_PRIVATE);

                    fileOutputStream.write(string.getBytes());

                    fileOutputStream.close();
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                    mTextView2.setText("出現文件找不到異常");
                } catch (IOException e) {
                    e.printStackTrace();
                    mTextView2.setText("出現了IO異常");
                }

結果如圖:


結果

出現了files文件夾以及相應的文件。

           try {
                    FileOutputStream fileOutputStream = openFileOutput(FILE_NAME_NEW, Context.MODE_PRIVATE);

                    fileOutputStream.write(string.getBytes());

                    fileOutputStream.close();
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                    mTextView2.setText("出現文件找不到異常");
                } catch (IOException e) {
                    e.printStackTrace();
                    mTextView2.setText("出現了IO異常");
                }

結果:


結果

如果給文件名字帶上具體的文件格式,也沒有什么影響。

說明:
自Api17以來,常量MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE已經被棄用。
從Android N開始,使用這些常量會引發securityException。
這意味著,面向Android N 和更高版本的應用無法按名稱共享私有文件,嘗試共享file://URL將會導致引發FileUriExposedException。
如果應用需要與其他應用共享私有文件,則可以將FileProvider與FLAG_GRANT_READ_URI_PERMISSION配合使用。
2、openFileInput

屬于android.content.Context;
參數一:傳入你想讀取的文件的名字,這里的name只能是名字,不能使用"/name"此類的。
返回值:返回一個FileInputStream。
不用于常見的getxxx方法,字面翻譯:打開文件輸入流
方便直接得到一個流

2.1例子
             try {
                    FileInputStream fileInputStream = openFileInput(FILE_NAME);

                    byte[] buf = new byte[1024];

                    int hasRead = 0;

                    StringBuffer sb  = new StringBuffer("");

                    //讀取文件部分
                    while((hasRead = fileInputStream.read(buf))>0){
                        sb.append(new String(buf,0,hasRead));
                    }

                    fileInputStream.close();

                    mTextView.setText(sb.toString());

                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                    mTextView.setText("出現文件找不到異常了");
                } catch (IOException e) {
                    e.printStackTrace();
                    mTextView.setText("出現了IO異常了");
                }
            }
GIF.gif

通過動圖可以看出來,是可以的讀取出來數據。
另外需要注意的是,如果你開始讀的文件不存在就會報FileNotFoundException異常,當時會建立files文件夾,如果此文件夾不存在的話。

3、getFilesDir()

也是在android.content.context中提供的屬性
執行此方法的到的是:
如果不想用openFileInput與openFileOutput兩個方法,想使用get系列的方法也是可以的.
直接得到的是文件的路徑,而不是得到一個流,這樣可以自己用.

/data/user/0/example.xlj.savecundemo/files
應用程序內部存儲空間中files的目錄
獲取在其中存儲內部文件的文件系統目錄的絕對路徑。
例子

這只是一種簡單的例子,延時了某一個用法

               File file = new File(getFilesDir(),"infos.txt");//返回值是File對象

               try {
                   FileOutputStream outputStream =  new FileOutputStream(file);

                   String s = "這是一個測試的例子,往info里面寫入數據";
                   outputStream.write(s.getBytes());

                   outputStream.close();

                   mTextView4.setText("文件寫入成功");
               } catch (FileNotFoundException e) {
                   e.printStackTrace();
                   mTextView4.setText("文件未找到異常");
               } catch (IOException e) {
                   e.printStackTrace();
                   mTextView4.setText("出現了IO異常");
               }

結果如圖:


結果

也是在files文件夾下創建了infos.txt文件。完成了存儲

3其他的常用方法,針對于自己應用內部存儲中 files (強調的是files)文件夾下的操作
3.1fileList()

返回保存在內部存儲空間一系列文件。

      String [] list = fileList();
        for (int i = 0; i < list.length; i++) {
            Log.d("xljxlj",list[i]);
        }

得到的結果如下:
         05-05 03:43:48.829 7396-7396/example.xlj.savecundemo D/xljxlj: hello_file
         05-05 03:43:48.829 7396-7396/example.xlj.savecundemo D/xljxlj: test.txt
         05-05 03:43:48.829 7396-7396/example.xlj.savecundemo D/xljxlj: infos.txt
3.2deleteFile(String name)

刪除保存在內部存儲的文件,其返回值是一個Boolean,表示是否刪除成功。
這里的name只能是名字,不能使用"/name"此類的。

說明

如果在一讀一寫的操作中,出現了亂碼,那么上面的代碼需要做如下調整
寫入的時候

 try {
                    FileOutputStream fileOutputStream = GudleActivity.this.openFileOutput(ApiTools.MAIN_INFO_FILES, Context.MODE_PRIVATE);
                    OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream,"UTF-8");
                    outputStreamWriter.write(response.toString());
                    outputStreamWriter.flush();
                    outputStreamWriter.close();
                } catch (IOException e) {
                    //出現異常緩存失敗
                    e.printStackTrace();
                }

讀取的時候

try {
            FileInputStream fileInputStream = openFileInput(ApiTools.MAIN_INFO_FILES);
            InputStreamReader reader = new InputStreamReader(fileInputStream,"UTF-8"); //最后的"GBK"根據文件屬性而定,如果不行,改成"UTF-8"試試
            BufferedReader br = new BufferedReader(reader);
            String line;
            while ((line = br.readLine()) != null) {
                LogUtils.debugInfo("maininfo","得到數據:"+line);
            }
            br.close();
            reader.close();

        } catch (IOException e) {
            e.printStackTrace();
        }

二、讀寫cache文件夾中文件

如果您想要緩存一些數據,而不是永久的存儲這些數據,可以考慮Cache文件夾下存儲。
當設備的內部存儲空間不足的時候,Android可能會刪除這些緩存文件以回收空間。但您不應該依賴系統來為你清理這些文件,而應該是中自行維護緩存文件,使其占用的控件保持在合理的限制范圍內例如1MB,當用戶卸載您的應用的時候,這些文件也會被移除。

1、通過getCacheDir()
例子
                File  file = new File(getCacheDir(),"infos.txt"); //返回值是File對象
                try {
                    FileInputStream fileInputStream = new FileInputStream(file);

                    byte[] bytes = new byte[1024];

                    int hascode = 0;

                    StringBuffer stringBuffer = new StringBuffer("");

                    while ((hascode = fileInputStream.read(bytes))>0){
                        stringBuffer.append(new String(bytes,0,hascode));
                    }

                    mTextView5.setText(stringBuffer.toString());

                    fileInputStream.close();
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                    mTextView5.setText("出現了文件未找到異常");
                } catch (IOException e) {
                    e.printStackTrace();

                    mTextView5.setText("出現了IO異常了");
                }
演示cache.png

忽略info,這是之前做的測試,目前只貼了一個infos.txt的代碼

2、讀寫內存存儲空間中自定義文件夾中文件

如果你不想放在files與cache文件夾下,可以通過getDir()方法,其返回值是:File
如果執行getDir("test",MODE_PRIVATE)
這樣在執行代碼,會先建立這個文件夾.,不過都會自動添加app_前綴,這個不需要自己去考慮

/data/user/0/example.xlj.savecundemo/app_test
如果存在則返回先關文件夾的路徑,如果沒有的話就創建并且返回相關的FIle。

那么相關的操作就類似了,

       File file = new File(getDir("test",MODE_PRIVATE),"who.txt");
        try {
            FileOutputStream fileOutputStream = new FileOutputStream(file);
            String ss = "自己新建立了一個文件夾";
            fileOutputStream.write(ss.getBytes());
            fileOutputStream.close();
            Toast.makeText(FileActivity.this,"寫入成功",Toast.LENGTH_SHORT).show();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
結果.png

如圖中app_test就是通過執行上述代碼完成的。這里只演示了文件寫入,對于讀取文件通上述,不在過多代碼演示。

讀取資源文件(不屬于內部存儲內容)這部分是引申

如果在編譯時想要保存應用中的靜態文件,請在項目的res/raw/目錄中保存該文件??梢酝ㄟ^openRawResource()打開該資源文件并傳遞R.raw.xxx資源ID,此方法返回一個InputStream(注意不是FileInputStream),您可以使用該流傳輸讀取文件(但是不能寫入到原始文件,只能讀?。?br> 也是得到一個資源文件流,注意不是應用存儲內部存儲中
下面通過例子進行演示:
準備文件:


WTISQN`{LBKB234VF2$812T.png

演示讀取activity.file.xml文件

            try {
                    InputStream fileInputStream =  FileActivity.this.getResources().openRawResource(R.raw.activity_file);

                    byte[] buf = new byte[1024];

                    int hasRead = 0;

                    StringBuffer sb  = new StringBuffer("");

                    //讀取文件部分
                    while((hasRead = fileInputStream.read(buf))>0){
                        sb.append(new String(buf,0,hasRead));
                    }

                    fileInputStream.close();

                    mTextView3.setText(sb.toString());

                } catch (Resources.NotFoundException e) {
                    e.printStackTrace();
                    mTextView3.setText("出現文件找不到異常了");
                } catch (IOException e) {
                    e.printStackTrace();
                    mTextView3.setText("出現了IO異常了");
                }
結果

演示如何讀取一個圖片

              //演示怎么讀取一張圖片
              InputStream inputStream = getResources().openRawResource(R.raw.my_my_name);


              //方式一
              Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
             mImageView.setImageBitmap(bitmap);

                //方式二
                BitmapDrawable bitmapDrawable = new BitmapDrawable(inputStream);

                Bitmap bitmap = bitmapDrawable.getBitmap();

                mImageView.setImageBitmap(bitmap);

注意:如果出現了亂碼,建議首先檢查源文件,也就是raw目錄下的文件是否是亂碼。

使用手機的外部存儲

每個兼容Android的設備都支持可用于保存文件的共享“外部存儲”。該存儲可能是可移除的存儲介質(如SD卡,次要卷),也可能是不可移除的存儲(主要卷)。現在大部分手機,都自帶不可移除的存儲.
保存到外部存儲的文件,是全局可讀的,用戶可以通過USB鏈接上進行修改.

共享,意味著所有的應用都可以使用,并不是前面提到的每個應用中的內部存儲.
簡單來講,外部存儲其實分為了兩種文件類型,一種是公共的存儲文件夾,一種是給具體應用的存儲文件夾(也分為file 與 cache )
區別:這部分外部存儲的就是getExtralxxx開頭的了,表明是外部存儲.
Ram:手機的運行內存。
Rom:手機內存,表現為內部存儲空間,可以理解為電腦本身的硬盤。
SD:外部存儲空間,可以理解為電腦的移動硬盤。
其實外部存儲系統給開發者使用的地方可以分為公共文件夾與應用私有文件夾區域:
前言圖.png

一般來講就是在Storage/sdcard目錄下,如圖所示,DCIM、Download、Movies等等都是屬于公共文件夾,這部分文件夾對于當前手機上所有的應用都是公開的。Android/data目錄下,有一個個以應用名字命名的文件夾,里面可以存放當前應用的私有文件與緩存。

1、檢查介質可用性

在使用外部存儲執行任何工作之前,應始終調用Environment.getExternalStorageState()方法以檢查介質是否可用,介質可能處于缺失、只讀或者其他狀態。

/* Checks if external storage is available for read and write */
public boolean isExternalStorageWritable() {
    String state = Environment.getExternalStorageState();
    if (Environment.MEDIA_MOUNTED.equals(state)) {
        return true;
    }
    return false;
}

/* Checks if external storage is available to at least read */
public boolean isExternalStorageReadable() {
    String state = Environment.getExternalStorageState();
    if (Environment.MEDIA_MOUNTED.equals(state) ||
        Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
        return true;
    }
    return false;
}

關于state可能值介紹:

MEDIA_UNKNOWN:  未知存儲狀態,例如路徑未被已知存儲介質支持時。
MEDIA_REMOVED: 存儲介質不存在的存儲狀態。
MEDIA_UNMOUNTED:  存儲狀態,如果介質存在但未安裝。
MEDIA_CHECKING:  如果介質存在并正在進行磁盤檢查,則為存儲狀態。
MEDIA_NOFS:  存儲狀態,如果介質存在但空白或正在使用不受支持的文件系統。
MEDIA_MOUNTED:  存儲狀態,如果介質存在并且以讀/寫訪問的方式安裝在其安裝點。
MEDIA_MOUNTED_READ_ONLY:  存儲狀態,如果介質存在并且以只讀訪問權限掛載到它的掛載點。
MEDIA_SHARED:  存儲狀態,如果介質未安裝,并通過USB海量存儲共享。
MEDIA_BAD_REMOVAL:  存儲狀態,如果介質在卸載之前被刪除。
MEDIA_UNMOUNTABLE:   存儲狀態,如果介質存在但無法安裝。 通常,如果介質上的文件系統損壞,就會發生這種情況。
MEDIA_EJECTING:  存儲狀態,如果媒體正在被彈出的過程中。

getExternalStorageState()方法將會返回可能需要檢查的其他狀態,當應用需要訪問介質時,可以使用這些狀態向用戶通知更多信息。
更多的關于Environment的使用介紹;

2、檢查權限
<user-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<user-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

兩點說明:
(1)android 6.0提出了運行時權限,需要開發過程中注意,可以參考文章
(2)從Android4.4開始,如果應用僅僅讀取或者寫入應用的私有文件,則不需要這些權限,只有使用外部存儲的公共文件夾時需要。

3、在外部存儲公共文件位置存儲文件。

一般而言,應該將用戶可通過您的應用獲取的新文件保存到設備上的“公共”位置,以便其他應用能夠在其中訪問這些文件,并且用戶也能輕松地從該設備復制這些文件。 執行此操作時,應使用共享的公共目錄之一,例如 Music/、Pictures/ 和 Ringtones/ 等。。

4、getExternalStoragePublicDirectory()方法,可與其他應用共享

返回值:File
參數:type
type可以有如下幾種選擇,其表示頂級公共目錄,當做是標準目錄,卻沒有強制選擇使用相應的:

DIRECTORY_ALARMS:用于放置任何音頻文件的標準目錄,該文件應位于用戶可以選擇的鬧鈴文件列表(而不是普通音樂),的特定音頻文件。
DIRECTORY_DCIM:將設備安裝為相機時傳統照片和視頻位置。
DIRECTORY_DOCUMENTS:放置用戶創建的文檔的標準目錄。
DIRECTORY_DOWNLOADS:用于放置用戶下載文件的標準目錄,這是頂級公共目錄的約定。
DIRECTORY_MOVIES:用戶放置用戶電影的標準目錄,但是媒體掃描器會在任何目錄中查找和收集電影。
DIRECTORY_MUSIC:用于放置任何音頻文件的標準目錄。
DIRECTORY_NOTIFICATIONS:放置任何音頻文件的標準目錄,一般是用戶可以選擇的通知列表(而不是普通音樂)
DIRECTORY_PICIURES:放置可供用戶使用的圖片的標準目錄。但是媒體掃描器將在任何目錄中查找和收集圖片。
DIRECTORY_PODCASTS:放置任何音頻的標準目錄,是可供選擇的播客列表中(而不是普通音樂)。
DIRECTORY_RINGTONES:用于放置任何音頻的標準目錄,該文件可供用戶選擇鈴聲列表。

Google官方給出的例子:

void createExternalStoragePublicPicture() {
    //創建了一個公共的放置圖片的路徑。
    // Context.getExternalMediaDir() ,下面會進行解釋
   //上述方法的路徑:/storage/sdcard/Android/media/example.xlj.savecundemo
    File path = Environment.getExternalStoragePublicDirectory(
            Environment.DIRECTORY_PICTURES);
  //在5.1上測試:/storage/sdcard/Pictures
  //在7.0上測試:/storage/emulated/0/Pictures
    File file = new File(path, "DemoPicture.jpg");
  //在5.1上測試: /storage/sdcard/Pictures/DemoPicture.jpg
  //在7.0上測試;/storage/emulated/0/Pictures/DemoPicture.jpg

    try {
        // Make sure the Pictures directory exists.確保文件夾存在,下面的方法表示創建相應的文件夾。具體的文件就要靠流的操作來完成了。
        path.mkdirs();

        // Very simple code to copy a picture from the application's
        // resource into the external file.  Note that this code does
        // no error checking, and assumes the picture is small (does not
        // try to copy it in chunks).  Note that if external storage is
        // not currently mounted this will silently fail.
        InputStream is = getResources().openRawResource(R.drawable.balloons);
        OutputStream os = new FileOutputStream(file);
        byte[] data = new byte[is.available()];
        is.read(data);
        os.write(data);
        is.close();
        os.close();

        // Tell the media scanner about the new file so that it is
        // immediately available to the user.
        //告訴媒體掃描器有關新文件的信息,立即告訴用戶。
       //下面代碼表示演示了手機多媒體掃描的流程。
        MediaScannerConnection.scanFile(this,
                new String[] { file.toString() }, null,
                new MediaScannerConnection.OnScanCompletedListener() {
            public void onScanCompleted(String path, Uri uri) {
                Log.i("ExternalStorage", "Scanned " + path + ":");
                Log.i("ExternalStorage", "-> uri=" + uri);
            }
        });
    } catch (IOException e) {
        // Unable to create file, likely because external storage is
        // not currently mounted.
        Log.w("ExternalStorage", "Error writing " + file, e);
    }
}

void deleteExternalStoragePublicPicture() {
    // Create a path where we will place our picture in the user's
    // public pictures directory and delete the file.  If external
    // storage is not currently mounted this will fail.
    File path = Environment.getExternalStoragePublicDirectory(
            Environment.DIRECTORY_PICTURES);
    File file = new File(path, "DemoPicture.jpg");
    file.delete();
}

boolean hasExternalStoragePublicPicture() {
    // Create a path where we will place our picture in the user's
    // public pictures directory and check if the file exists.  If
    // external storage is not currently mounted this will think the
    // picture doesn't exist.
    File path = Environment.getExternalStoragePublicDirectory(
            Environment.DIRECTORY_PICTURES);
    File file = new File(path, "DemoPicture.jpg");
    return file.exists();
}

例子

    //檢查設備介質是否可以使用
    public boolean isExternalStorageWritable() {
        String state = Environment.getExternalStorageState();

        if (Environment.MEDIA_MOUNTED.equals(state)) {
            return true;
        }

        return false;
    }

先檢查設備介質是否可用:

                if (isExternalStorageWritable()) {
                  //這是使用的運行時權限一個注解方式動態申請權限
                    SDTestActivityPermissionsDispatcher.saveFileOneWithCheck(SDTestActivity.this);
                } else {
                    mTextView1.setText("設備不可以用");
                }

設備可用,執行相應的方法:

     //保存可以與其他應用共享的文件
    @NeedsPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
    public void saveFileOne() {
        //保在下載文件夾中
        File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "testwenjian");

     
        /**
         * File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),"testwenjian");
         * 5.1:/storage/sdcard/Download/testwenjian
         * 7.0:/storage/emulated/0/Download/testwenjian
         * 不管是file.mkdir還是file.mkdirs都是創建目錄的,不是創建新的文件
         * 創建了一次新的之后,表示已經存在了,那么就不會在創建了,會返回一個false,表示文件夾未創建成功,但是實際上文件已經存在了。
          *具體效果如下圖一
         */
        if (file.mkdir()) {
            //表示文件創建成功了
            mTextView1.setText("文件夾創建成功了");
            //開始寫入文件
            File file1 = new File(file, "xlj.txt");
            Log.d("newtest", "hah: " + file1.toString());


            /****
             * File file1 = new File(file,"xlj.txt");
             * 5.0:/storage/sdcard/Download/testwenjian/xlj.txt
             * 7.0: /storage/emulated/0/Download/testwenjian/xlj.txt
             * 通過測試不難發現,使用具體的文件通過具體的 流操作就可以完成了, 是文件輸出流,其本身就具有再具體的file路徑下創建文件的功能。
             *具體效果見圖二:
             */

            String info = "恭喜您,數據寫入成功了";
            try {
                FileOutputStream fileOutputStream = new FileOutputStream(file1);

                fileOutputStream.write(info.getBytes());

                fileOutputStream.close();

                mTextView1.setText("創建新的文件成功了");

            } catch (FileNotFoundException e) {
                e.printStackTrace();
                mTextView1.setText("創建新的文件失敗了");
            } catch (IOException e) {
                e.printStackTrace();

                mTextView1.setText("創建新的文件出現了IO異常了");
            }
        } else {
            mTextView1.setText("文件夾未創建成功");
            file.delete();
        }
    }

建立文件夾,如圖一:


圖一

最后建立相應的文件,如圖二:
在5.0系統下:


圖二

在7.0系統下:


7.0測試一外部存貯.png

另外,保存到外部存儲的公共區域上如果你想在媒體掃描程序中隱藏您的文件

在您的外部文件目錄中包含名為 `.nomedia` 的空文件(注意文件名中的點前綴)。 這將阻止媒體掃描程序讀取您的媒體文件,
并通過 MediaStore 內容提供程序將其提供給其他應用。

4、將數據保存在外部存儲上的應用私有位置上,這樣這些文件只能本應用使用,別的應用程序無法訪問.

簡單來講就是五個方法,五個方法得到想要操作的文件夾路徑,接下來就是流的操作,往你想用的文件夾路徑中創建文件或者讀取文件了,所以這里只介紹這五個方法,對于流的操作不在寫。

當用戶卸載您的應用時,此目錄及其內容將被刪除。此外,系統媒體掃描程序不回讀取這些目錄中的文件,因此不能從MediaStore內容提供程序訪問這些文件。同樣,不應將這些目錄用于最終屬于用戶的媒體,例如使用您的應用拍攝或編輯照片或者您的應用購買的音樂等,這些文件應該放在公共的目錄中。

當應用卸載時候,這部分內容也會刪除,另外,盡管MediaStore提供的程序不能訪問此部分內容,但是別的應用程序具有READ_XXX權限的仍可以獲取全部文件時,仍可以得到,所以并不是完全的保密.

(1)getExternalFilesDir(String type)

如果正在處理的文件不適合其他應用使用(比如應用使用的圖形紋理或者音效),則可以考慮使用外部存儲上的私有存儲目錄。此方法需要傳入type參數指定子目錄的類型。如果不需要特定的目錄,也可以傳遞null獲得應用私有目錄的根目錄。也可以指定具體的比如DIRECTORY_MOVIES

從Android 4.4開始,讀取或者寫入應用私有目錄中的文件不在需要READ_EXTERNAL_STORAGE或者WRITE_EXTERNAL_STORAGE權限,可以通過添加maxSdkVersion屬性來聲明,只在較低版本的Android中請求該權限:

<manifest...>
            <users-permission android:name = "android.permission.WRITE_EXTERNAL_STORAGE"
                                               andorid:maxSdkVersion = "18"/>
</manifest>

下面是一些打印的Log:進行方法的展示

(1)getExternalFilesDir(null)
    5.0:
    /storage/sdcard/Android/data/example.xlj.savecundemo/files
   7.0:
    /storage/emulated/0/Android/data/example.xlj.savecundemo/files

(2)getExternalFilesDir(Environment.DIRECTORY_MOVIES)
     5.0:
     /storage/sdcard/Android/data/example.xlj.savecundemo/files/Movies
    7.0:
    /storage/emulated/0/Android/data/example.xlj.savecundemo/files/Movies

(3)getExternalFilesDir("xlj")
    /storage/sdcard/Android/data/example.xlj.savecundemo/files/xlj

下面是測試例子:

 File file = new File(getExternalFilesDir(null), "DemoFile.jpg");

        try {
            // Very simple code to copy a picture from the application's
            // resource into the external file.  Note that this code does
            // no error checking, and assumes the picture is small (does not
            // try to copy it in chunks).  Note that if external storage is
            // not currently mounted this will silently fail.
            InputStream is = getResources().openRawResource(R.raw.my_my_name);
            OutputStream os = new FileOutputStream(file);
            byte[] data = new byte[is.available()];
            is.read(data);
            os.write(data);
            is.close();
            os.close();
        } catch (IOException e) {
            // Unable to create file, likely because external storage is
            // not currently mounted.
            Log.w("ExternalStorage", "Error writing " + file, e);
        }

在5.0系統下;


5.0系統之下.png

在7.0系統下:


7.0測試隱私文件存儲.png
(2)getExternalFilesDirs(String type)

與前面getExternalFilesDir方法一樣,不同的是此方法得到的是一個file[],一般取file[0]使用即可

有時,已分配某個內部存儲器分區用作外部存儲的設備可能提供了SD卡槽。在使用運行 Android 4.3和更低版本的這類設備時,getExternalFilesDir()方法將僅提供內部分區的訪問權限,而您的應用無法讀取或者寫入SD卡。

從Android 4.4開始,可以通過調用getEx ternalFilesDirs()來同時訪問這兩個位置,該方法將會返回包含各個位置條目的File數組。數組中的第一個條目被視為外部主存儲,除非該位置已滿或者不可用,否則應該使用該位置。

如果希望支持Android 4.3和更低版本的同時訪問兩個可能的位置,請使用支持庫中的靜態方法
ContextCompat.getExternalFilesDirs();在Android 4.3或者更低的版本中,此方法也會返回一個File數組,但其中始終包含一個條目,只能在使用的時候使用File[0];

盡管MediaStore內容提供程序不能訪問getExternalFilesDir()和getExternalFilesDirs()所提供的目錄,但其他具有 READ_EXTERNAL_STORAGE權限的用用仍可訪問外部存儲上的所有文件,如上述文件,如果您想要完全限制您的文件訪問權限,則應該講您的文件寫入到內部存儲。

(3)getExternalCacherDir();
         5.0
         /storage/sdcard/Android/data/example.xlj.savecundemo/cache
         7.0
         /storage/emulated/0/Android/data/example.xlj.savecundemo/cache
    

與前面敘述的ContextCompat.getExternalFilesDirs()相似,您也可以通過調用ContextCompat.getExternalCacheDirs()來訪問外部存儲上的緩存目錄。

為了節省文件空間并保持應用性能,您應該在應用的整個生命周期內仔細管理您的緩存文件并移除其中不再需要的文件。

調用此方法,得到的文件路徑如圖所示:
5.0系統之下:


測試存儲緩存隱私文件.png

7.0系統之下;


7.0測試緩存存儲.png
(4)getExternalCacherDirs();

此方法與前面提到的getExternalCacherDirs()一樣。其返回值也是Files[];

(5)getExternalMediaDir()

執行此方法:

/storage/emulated/0/Android/media/example.xlj.savecundemo

7.0系統之下:


結果

發現此方法是在Android文件夾下建立了media文件夾,在其中建立了你自己應用包名的文件夾,你可以放數據。前面提到的四個方法都是放在了Android/data/應用包名/下

數據庫存儲

Android 提供了對于SQlite數據庫的完全支持,應用中的任何類(不包括應用外部的類)均可按名稱訪問您所創建的任何數據庫。

數據庫存儲涉及到兩個關鍵類:

SQLiteDatabase

此類SQLiteDatabase公開了管理SQLite數據庫的方法。SQLiteDatabase具有創建、刪除、執行SQL命令以及執行其他常見數據庫管理任務的方法。

SQLiteOpenHelper

實際操作是寫一個類繼承此類
SQLiteOpenHelper是管理數據庫創建和版本管理的助手類。

數據庫名稱在應用中必須是唯一的,而不是跨所有應用程序。

比如自己寫的一個例子:

public class MySQLiteOpenHelper extends SQLiteOpenHelper {

    //構造方法一
    public MySQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version, DatabaseErrorHandler errorHandler) {
        super(context, name, factory, version, errorHandler);
    }

    //構造方法二
    public MySQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
//        super(context, name, factory, version);
        //可以通過外界參數傳入創建,這里直接賦值了
        super(context,"db_openhelper",null,1);

    }

    //重寫的onCreate方法
    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
             sqLiteDatabase.execSQL("create table if not exists tb_my(_id integer primary key autoincrement,title text)");
    }

    //用于更新數據庫版本
    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
        //參數一表示以前的版本,oldVersion
        //參數二表示新的版本,newVersion
        if (i1 > i){
            sqLiteDatabase.execSQL("drop table if exists tb_my");
            onCreate(sqLiteDatabase);
        }
    }
}

在應用中使用:

       //使用自定義的SQLiteOpenHelper
        MySQLiteOpenHelper mySQLiteOpenHelper = new MySQLiteOpenHelper(this,"",null,1);
        //執行完上局話得到。。什么反應也沒有
        SQLiteDatabase sqLiteDatabase = mySQLiteOpenHelper.getReadableDatabase();
        //通過幫助類得到管理數據庫的方法。便會得到在內部存儲中建立相應的數據庫

        sqLiteDatabase.insert("tb_my","title",null);

結果:


O5{@J1246`E4CL(S5%7NRWC.png

寫給自己的備注:

關于數據庫這邊簡單記錄一下,上面涉及到的兩個鏈接都是Google官方提供的資料,計劃在整理新的文檔,因為其涉及的還是比較多的。整理完成后,將Google提供的鏈接替換成自己文章的鏈接。

網絡存儲

所謂的網絡存儲簡單來講,就是將數據資料放在網絡服務器上。
由于市面上的網絡框架比較多,就不做詳細介紹了。下面提供兩個Google推薦的兩個參考類、

java.net.*
android.net.*

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,517評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,087評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 177,521評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,493評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,207評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,603評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,624評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,813評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,364評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,110評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,305評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,874評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,532評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,953評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,209評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,033評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,268評論 2 375

推薦閱讀更多精彩內容