前言
Android提供了多種選項來保存永久性應(yīng)用數(shù)據(jù)欣硼。用戶可以選擇的數(shù)據(jù)存儲選項有:共享首選項骑冗、內(nèi)部存儲秉宿、外部存儲疙剑、SQLite數(shù)據(jù)庫氯迂、網(wǎng)絡(luò)存儲。
文章開始前用一張圖表示共享首選項,內(nèi)部存儲,外部存儲
一言缤、使用共享首選項
1嚼蚀、關(guān)鍵類:SharedPreferences類
可以使用ShatedPreferences來保存任何原始數(shù)據(jù):布爾值、浮點(diǎn)值管挟、整型值轿曙、長整型、字符串。
這些數(shù)據(jù)將會跨多個用戶會話永久保留导帝。
2守谓、介紹如何獲得SharedPreferences對象
2.1 、(1)getSharedPreferences(String name,int mode);
通過Context調(diào)用舟扎,參數(shù)一:首選項文件名分飞,如果此名稱的首選項文件不存在,那么會在再檢索編輯器SharedPreferences.edit()并提交更改(Editor.commit())時創(chuàng)建這個文件睹限。
參數(shù)二默認(rèn)填寫0或者是MODE_PRIVATE(創(chuàng)建的文件只能由調(diào)用應(yīng)用程序訪問)譬猫,別的模式MODE_WORLD_READABLE(創(chuàng)建全都可讀文件,容易造成安全漏洞羡疗,已在17棄用)與MODE_WORLD_WRITEABLE(創(chuàng)建全部課讀寫文件染服,容易造成安全漏洞,已在17棄用)與MODE_MULTI_PROCESS(在某些版本的Android中無法可靠運(yùn)行叨恨,不提供任何機(jī)制來協(xié)調(diào)跨進(jìn)程的并發(fā)修改柳刮,已在23棄用)
2.2 、getPreferences(int mode);
在Activity中調(diào)用痒钝,參數(shù)參照上面秉颗,一般用0就可以。
調(diào)用SharedPreferences.edit()方法并最后提交更改(Editor.commit())時候創(chuàng)建文件送矩,文件名就是以當(dāng)前Activity的類名蚕甥,比較簡單的基于當(dāng)前Activity創(chuàng)建其相應(yīng)的SharedPreferences文件。
3栋荸、詳細(xì)介紹
其應(yīng)該是一個interface,放在android.content.SharedPreferences包下菇怀。
其嵌套了接口Editor與接口 OnShatedPreferenceChangeListener。
通過SharedPreferences對象可以調(diào)用如下方法:
3.1 ->>>
contains(String key)
返回值:boolean
檢驗當(dāng)前SharedPreferences文件中是否包含某個key
3.2->>>
getAll()
返回值:Map<String,?>
返回當(dāng)前SharedPreferences文件中所有的key以及相應(yīng)的value值
示例:
Map<String,?> map = mSharedPreferences.getAll();
這樣此SharedPreferences中所有的值都在Map集合中
3.3->>>
getBoolean(String key,boolean defValue)
返回值:boolean
得到此SharedPreferences中boolean類型的Key對應(yīng)的value晌块,其中參數(shù)二表示默認(rèn)值爱沟。
需要說明的是,如果此key 對應(yīng)的值不是布爾值匆背,那么就會拋出ClassCastException異常呼伸,如果不處理,程序會崩潰靠汁,所以在使用查詢的時候:
示例
try {
int i = mSharedPreferences.getInt("age", 1);
mTextViewa.setText("" + i);
} catch (ClassCastException e) {
mTextViewa.setText("出現(xiàn)異常了");
}
3.4->>>
getFloat(String key,float defValue)
返回值:float
與getBoolean方法一樣蜂大,不同的是得到的是float值,針對的是float類型的數(shù)據(jù)查詢蝶怔,如果傳入的Key對應(yīng)的值不是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數(shù)據(jù)淆储。
3.9->>>
registerOnSharedPreferenceChangeListener(SharedPreferences.OnSharedPreferenceChangeListener listener)
警告:首選項管理器當(dāng)前不存儲對偵聽器的強(qiáng)引用冠场。 您必須存儲對偵聽器的強(qiáng)引用,否則它將容易被垃圾收集本砰。 只要您需要偵聽器
碴裙,我們建議您在對象的實(shí)例數(shù)據(jù)中保留對偵聽器的引用。
此監(jiān)聽發(fā)生在此SharedPreferences文件內(nèi)容發(fā)生更改的時候点额。
3.10->>>
unregisterOnSharedPreferencesChangeListener(SharedPreferences.OnSharedPreferenceChangeListener listener)
取消監(jiān)聽
3.11->>>
edit()
返回值:SharedPreferences.Editor
具體作用參照下面Editor中方法具體介紹舔株。
4介紹SharedPreferences中的Editor中方法
操作當(dāng)前SharedPreferences中文件中的值,只有通過commit或者apply之后还棱,才會建立相應(yīng)的SharedPreferences文件與完成相應(yīng)的修改载慈。
4.1 ->>>
putBoolean(String key,boolean value)
返回值:SharedPreferences.Editor
寫入到當(dāng)前SharedPreferences文件中一對key-value或者是修改已經(jīng)存在key對應(yīng)的value的值。
4.2 ->>>
putFloat(String key,float value)
返回值:SharedPreferences.Editor
寫入到當(dāng)前SharedPreferences文件中一對key-value或者是修改已經(jīng)存在key對應(yīng)的value的值诱贿。
4.3 ->>>
putInt(String key,int value)
返回值:SharedPreferences.Editor
寫入到當(dāng)前SharedPreferences文件中一對key-value或者是修改已經(jīng)存在key對應(yīng)的value的值娃肿。
4.4 ->>>
putLong(String key,long value)
返回值:SharedPreferences.Editor
寫入到當(dāng)前SharedPreferences文件中一對key-value或者是修改已經(jīng)存在key對應(yīng)的value的值。
4.5 ->>>
putString(String key,String value)
返回值:SharedPreferences.Editor
寫入到當(dāng)前SharedPreferences文件中一對key-value或者是修改已經(jīng)存在key對應(yīng)的value的值珠十。
4.6 ->>>
putStringSet(String key,Set<String> values)
返回值:SharedPreferences.Editor
寫入到當(dāng)前SharedPreferences文件中一對key-value或者是修改已經(jīng)存在key對應(yīng)的value的值料扰。
對此參數(shù)傳遞null等價于使用此鍵調(diào)用remove(String)
4.7 ->>>
remove(String key)
返回值:SharedPreferences.Editor
移除當(dāng)前SharedPreferences中某個Key以及其所代表的值
4.8 ->>>
clear()
返回值:SharedPreferences.Editor
移除所有的key以及value從當(dāng)前SharedPreferences文件中。
4.9 ->>>
commit()
返回值:boolean
如果新值成功寫入當(dāng)前SharedPreferences焙蹭,則返回true;
會自動執(zhí)行請求的參數(shù)晒杈,替換SharedPreferences中的修改,如果沒有的話孔厉,就會創(chuàng)建此文件
當(dāng)兩個編輯器同時修改時拯钻,最后一個調(diào)用commit的成功
如果你不關(guān)心返回值,并且你在應(yīng)用程序的主線程中使用它撰豺,請使用apply();
4.10 ->>>
apply()
返回值:void
1粪般、如果先后apply()了幾次,那么會以最后一次apply()的為準(zhǔn)污桦。
2亩歹、commit()是把內(nèi)容同步提交到硬盤的。而apply()先立即把修改提交到內(nèi)存,然后開啟一個異步的線程提交到硬盤小作,
并且如果提交失敗亭姥,你不會收到任何通知。
也就是說顾稀,commit有返回值达罗,apply沒有返回值,commit寫入文件操作是在主線程中静秆,會消耗資源粮揉,apply寫入文件的操作是異步的,
會把Runnable放到線程池中執(zhí)行
3诡宗、如果當(dāng)一個apply()的異步提交還在進(jìn)行的時候滔蝉,執(zhí)行commit()操作,那么commit()是會阻塞的塔沃。
而如果commit()的時候蝠引,前面的commit()還未結(jié)束,這個commit()還是會阻塞的蛀柴。(所以引起commit阻塞會有這兩種原因)
4螃概、由于SharePreferences在一個程序中的實(shí)例一般都是單例的,所以如果你不是很在意返回值的話鸽疾,你使用apply()代替commit()是無所謂的吊洼。
5、SP例子
(1)初始一個應(yīng)用在data/data/應(yīng)用包名下:
當(dāng)你使用了SharedPreferences時候
public static final String FILE_NAME = "MySharedPreferences";
mSharedPreferences = getSharedPreferences(FILE_NAME, 0);
//執(zhí)行完上述話制肮,會在應(yīng)用程序中建立shared_prefs文件夾冒窍,里面將要存放sharedPreferences文件,但是目前沒有內(nèi)容
(2)當(dāng)你完成一些查詢操作豺鼻,但是沒有完成使用Editor
mSharedPreferences = getSharedPreferences(FILE_NAME, 0);
String name = mSharedPreferences.getString("name","xlj");
//也沒有在shared_prefs文件夾下建立相應(yīng)的FILE_NAME文件综液,另外這樣用也不會報錯,返回name = xlj
(3)通過操作Editor
mSharedPreferences = getSharedPreferences(FILE_NAME, 0);
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putString("name", "xljs");
editor.commit();
(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("出現(xiàn)異常了");
}
(2)當(dāng)前Activity名字:SharedPActivity
在手機(jī)上使用應(yīng)用的內(nèi)部存儲
直接在設(shè)備的內(nèi)部存儲中保存文件儒飒。默認(rèn)情況下谬莹,保存到內(nèi)部存儲的文件是應(yīng)用的私有文件,其他應(yīng)用(和用戶)都不能訪問這些文件桩了。當(dāng)用戶卸載應(yīng)用的時候附帽,這些文件也會被移除。并且這些文件理論上是不可見的
簡單來講,就是給我們自己的應(yīng)用,在內(nèi)部存儲分配的空間,默認(rèn)的有files文件夾井誉、cache文件夾蕉扮、
(其實(shí)共享首選項也是提供的默認(rèn)的shared_prefs文件夾)
當(dāng)然了,用戶也可以不用這些已經(jīng)提供的files 與 cache文件夾,自己建立自己的的文件夾,并放文件.
一、讀寫files文件夾(根文件夾)下文件
1颗圣、openFileOutput()方法
屬于android.content.Context;
返回值:FileOutputStram一個文件輸出流
參數(shù)一:name,你要打開的文件的名字慢显,這里的name只能是名字爪模,不能使用"/name"此類的欠啤。
參數(shù)二:MODE_PRIVATE荚藻,別的模式處于安全都摒棄了
不同于常見getxxx方法,字面翻譯就是:打開files文件夾下某個文件的輸出流
簡單的方式得到一個文件輸出流
1.1例子
初始默認(rèn)的應(yīng)用程序內(nèi)部存儲如下圖:
shared_prefs文件夾是因為操作過SharedPreferences才有的,默認(rèn)的初始就有一個空的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("出現(xiàn)文件找不到異常");
} catch (IOException e) {
e.printStackTrace();
mTextView2.setText("出現(xiàn)了IO異常");
}
結(jié)果如圖:
出現(xiàn)了files文件夾以及相應(yīng)的文件应狱。
try {
FileOutputStream fileOutputStream = openFileOutput(FILE_NAME_NEW, Context.MODE_PRIVATE);
fileOutputStream.write(string.getBytes());
fileOutputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
mTextView2.setText("出現(xiàn)文件找不到異常");
} catch (IOException e) {
e.printStackTrace();
mTextView2.setText("出現(xiàn)了IO異常");
}
結(jié)果:
如果給文件名字帶上具體的文件格式,也沒有什么影響祠丝。
說明:
自Api17以來疾呻,常量MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE已經(jīng)被棄用。
從Android N開始写半,使用這些常量會引發(fā)securityException岸蜗。
這意味著,面向Android N 和更高版本的應(yīng)用無法按名稱共享私有文件叠蝇,嘗試共享file://URL將會導(dǎo)致引發(fā)FileUriExposedException璃岳。
如果應(yīng)用需要與其他應(yīng)用共享私有文件,則可以將FileProvider與FLAG_GRANT_READ_URI_PERMISSION配合使用悔捶。
2铃慷、openFileInput
屬于android.content.Context;
參數(shù)一:傳入你想讀取的文件的名字,這里的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("出現(xiàn)文件找不到異常了");
} catch (IOException e) {
e.printStackTrace();
mTextView.setText("出現(xiàn)了IO異常了");
}
}
通過動圖可以看出來堂淡,是可以的讀取出來數(shù)據(jù)馋缅。
另外需要注意的是,如果你開始讀的文件不存在就會報FileNotFoundException異常绢淀,當(dāng)時會建立files文件夾萤悴,如果此文件夾不存在的話。
3更啄、getFilesDir()
也是在android.content.context中提供的屬性
執(zhí)行此方法的到的是:
如果不想用openFileInput與openFileOutput兩個方法,想使用get系列的方法也是可以的.
直接得到的是文件的路徑,而不是得到一個流,這樣可以自己用.
/data/user/0/example.xlj.savecundemo/files
應(yīng)用程序內(nèi)部存儲空間中files的目錄
獲取在其中存儲內(nèi)部文件的文件系統(tǒng)目錄的絕對路徑稚疹。
例子
這只是一種簡單的例子,延時了某一個用法
File file = new File(getFilesDir(),"infos.txt");//返回值是File對象
try {
FileOutputStream outputStream = new FileOutputStream(file);
String s = "這是一個測試的例子祭务,往info里面寫入數(shù)據(jù)";
outputStream.write(s.getBytes());
outputStream.close();
mTextView4.setText("文件寫入成功");
} catch (FileNotFoundException e) {
e.printStackTrace();
mTextView4.setText("文件未找到異常");
} catch (IOException e) {
e.printStackTrace();
mTextView4.setText("出現(xiàn)了IO異常");
}
結(jié)果如圖:
也是在files文件夾下創(chuàng)建了infos.txt文件内狗。完成了存儲
3其他的常用方法,針對于自己應(yīng)用內(nèi)部存儲中 files (強(qiáng)調(diào)的是files)文件夾下的操作
3.1fileList()
返回保存在內(nèi)部存儲空間一系列文件。
String [] list = fileList();
for (int i = 0; i < list.length; i++) {
Log.d("xljxlj",list[i]);
}
得到的結(jié)果如下:
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)
刪除保存在內(nèi)部存儲的文件义锥,其返回值是一個Boolean,表示是否刪除成功柳沙。
這里的name只能是名字,不能使用"/name"此類的拌倍。
說明
如果在一讀一寫的操作中赂鲤,出現(xiàn)了亂碼噪径,那么上面的代碼需要做如下調(diào)整
寫入的時候
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) {
//出現(xiàn)異常緩存失敗
e.printStackTrace();
}
讀取的時候
try {
FileInputStream fileInputStream = openFileInput(ApiTools.MAIN_INFO_FILES);
InputStreamReader reader = new InputStreamReader(fileInputStream,"UTF-8"); //最后的"GBK"根據(jù)文件屬性而定,如果不行数初,改成"UTF-8"試試
BufferedReader br = new BufferedReader(reader);
String line;
while ((line = br.readLine()) != null) {
LogUtils.debugInfo("maininfo","得到數(shù)據(jù):"+line);
}
br.close();
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
二找爱、讀寫cache文件夾中文件
如果您想要緩存一些數(shù)據(jù),而不是永久的存儲這些數(shù)據(jù)泡孩,可以考慮Cache文件夾下存儲车摄。
當(dāng)設(shè)備的內(nèi)部存儲空間不足的時候,Android可能會刪除這些緩存文件以回收空間仑鸥。但您不應(yīng)該依賴系統(tǒng)來為你清理這些文件吮播,而應(yīng)該是中自行維護(hù)緩存文件,使其占用的控件保持在合理的限制范圍內(nèi)例如1MB眼俊,當(dāng)用戶卸載您的應(yīng)用的時候意狠,這些文件也會被移除。
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("出現(xiàn)了文件未找到異常");
} catch (IOException e) {
e.printStackTrace();
mTextView5.setText("出現(xiàn)了IO異常了");
}
忽略info环戈,這是之前做的測試,目前只貼了一個infos.txt的代碼
2获列、讀寫內(nèi)存存儲空間中自定義文件夾中文件
如果你不想放在files與cache文件夾下谷市,可以通過getDir()方法,其返回值是:File
如果執(zhí)行g(shù)etDir("test",MODE_PRIVATE)
這樣在執(zhí)行代碼,會先建立這個文件夾.,不過都會自動添加app_前綴,這個不需要自己去考慮
/data/user/0/example.xlj.savecundemo/app_test
如果存在則返回先關(guān)文件夾的路徑击孩,如果沒有的話就創(chuàng)建并且返回相關(guān)的FIle迫悠。
那么相關(guān)的操作就類似了,
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();
}
如圖中app_test就是通過執(zhí)行上述代碼完成的巩梢。這里只演示了文件寫入创泄,對于讀取文件通上述,不在過多代碼演示括蝠。
讀取資源文件(不屬于內(nèi)部存儲內(nèi)容)這部分是引申
如果在編譯時想要保存應(yīng)用中的靜態(tài)文件鞠抑,請在項目的res/raw/目錄中保存該文件〖删可以通過openRawResource()打開該資源文件并傳遞R.raw.xxx資源ID,此方法返回一個InputStream(注意不是FileInputStream)搁拙,您可以使用該流傳輸讀取文件(但是不能寫入到原始文件,只能讀确唷)
也是得到一個資源文件流,注意不是應(yīng)用存儲內(nèi)部存儲中
下面通過例子進(jìn)行演示:
準(zhǔn)備文件:
演示讀取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("出現(xiàn)文件找不到異常了");
} catch (IOException e) {
e.printStackTrace();
mTextView3.setText("出現(xiàn)了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);
注意:如果出現(xiàn)了亂碼箕速,建議首先檢查源文件,也就是raw目錄下的文件是否是亂碼朋譬。
使用手機(jī)的外部存儲
每個兼容Android的設(shè)備都支持可用于保存文件的共享“外部存儲”盐茎。該存儲可能是可移除的存儲介質(zhì)(如SD卡,次要卷),也可能是不可移除的存儲(主要卷)♂阌現(xiàn)在大部分手機(jī),都自帶不可移除的存儲.
保存到外部存儲的文件,是全局可讀的,用戶可以通過USB鏈接上進(jìn)行修改.
共享,意味著所有的應(yīng)用都可以使用,并不是前面提到的每個應(yīng)用中的內(nèi)部存儲.
簡單來講,外部存儲其實(shí)分為了兩種文件類型,一種是公共的存儲文件夾,一種是給具體應(yīng)用的存儲文件夾(也分為file 與 cache )
區(qū)別:這部分外部存儲的就是getExtralxxx開頭的了,表明是外部存儲.
Ram:手機(jī)的運(yùn)行內(nèi)存字柠。
Rom:手機(jī)內(nèi)存探越,表現(xiàn)為內(nèi)部存儲空間,可以理解為電腦本身的硬盤窑业。
SD:外部存儲空間钦幔,可以理解為電腦的移動硬盤。
其實(shí)外部存儲系統(tǒng)給開發(fā)者使用的地方可以分為公共文件夾與應(yīng)用私有文件夾區(qū)域:
一般來講就是在Storage/sdcard目錄下数冬,如圖所示节槐,DCIM、Download拐纱、Movies等等都是屬于公共文件夾,這部分文件夾對于當(dāng)前手機(jī)上所有的應(yīng)用都是公開的哥倔。Android/data目錄下秸架,有一個個以應(yīng)用名字命名的文件夾,里面可以存放當(dāng)前應(yīng)用的私有文件與緩存咆蒿。
1东抹、檢查介質(zhì)可用性
在使用外部存儲執(zhí)行任何工作之前,應(yīng)始終調(diào)用Environment.getExternalStorageState()方法以檢查介質(zhì)是否可用沃测,介質(zhì)可能處于缺失缭黔、只讀或者其他狀態(tài)。
/* 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;
}
關(guān)于state可能值介紹:
MEDIA_UNKNOWN: 未知存儲狀態(tài)蒂破,例如路徑未被已知存儲介質(zhì)支持時馏谨。
MEDIA_REMOVED: 存儲介質(zhì)不存在的存儲狀態(tài)。
MEDIA_UNMOUNTED: 存儲狀態(tài)附迷,如果介質(zhì)存在但未安裝惧互。
MEDIA_CHECKING: 如果介質(zhì)存在并正在進(jìn)行磁盤檢查,則為存儲狀態(tài)喇伯。
MEDIA_NOFS: 存儲狀態(tài)喊儡,如果介質(zhì)存在但空白或正在使用不受支持的文件系統(tǒng)。
MEDIA_MOUNTED: 存儲狀態(tài)稻据,如果介質(zhì)存在并且以讀/寫訪問的方式安裝在其安裝點(diǎn)艾猜。
MEDIA_MOUNTED_READ_ONLY: 存儲狀態(tài),如果介質(zhì)存在并且以只讀訪問權(quán)限掛載到它的掛載點(diǎn)捻悯。
MEDIA_SHARED: 存儲狀態(tài)匆赃,如果介質(zhì)未安裝,并通過USB海量存儲共享秋度。
MEDIA_BAD_REMOVAL: 存儲狀態(tài)炸庞,如果介質(zhì)在卸載之前被刪除。
MEDIA_UNMOUNTABLE: 存儲狀態(tài)荚斯,如果介質(zhì)存在但無法安裝埠居。 通常查牌,如果介質(zhì)上的文件系統(tǒng)損壞,就會發(fā)生這種情況滥壕。
MEDIA_EJECTING: 存儲狀態(tài)纸颜,如果媒體正在被彈出的過程中。
getExternalStorageState()方法將會返回可能需要檢查的其他狀態(tài)绎橘,當(dāng)應(yīng)用需要訪問介質(zhì)時胁孙,可以使用這些狀態(tài)向用戶通知更多信息。
更多的關(guān)于Environment的使用介紹;
2称鳞、檢查權(quán)限
<user-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<user-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
兩點(diǎn)說明:
(1)android 6.0提出了運(yùn)行時權(quán)限涮较,需要開發(fā)過程中注意,可以參考文章
(2)從Android4.4開始冈止,如果應(yīng)用僅僅讀取或者寫入應(yīng)用的私有文件狂票,則不需要這些權(quán)限,只有使用外部存儲的公共文件夾時需要熙暴。
3闺属、在外部存儲公共文件位置存儲文件。
一般而言周霉,應(yīng)該將用戶可通過您的應(yīng)用獲取的新文件保存到設(shè)備上的“公共”位置掂器,以便其他應(yīng)用能夠在其中訪問這些文件,并且用戶也能輕松地從該設(shè)備復(fù)制這些文件俱箱。 執(zhí)行此操作時国瓮,應(yīng)使用共享的公共目錄之一,例如 Music/匠楚、Pictures/ 和 Ringtones/ 等巍膘。。
4芋簿、getExternalStoragePublicDirectory()方法,可與其他應(yīng)用共享
返回值:File
參數(shù):type
type可以有如下幾種選擇峡懈,其表示頂級公共目錄,當(dāng)做是標(biāo)準(zhǔn)目錄与斤,卻沒有強(qiáng)制選擇使用相應(yīng)的:
DIRECTORY_ALARMS:用于放置任何音頻文件的標(biāo)準(zhǔn)目錄肪康,該文件應(yīng)位于用戶可以選擇的鬧鈴文件列表(而不是普通音樂),的特定音頻文件撩穿。
DIRECTORY_DCIM:將設(shè)備安裝為相機(jī)時傳統(tǒng)照片和視頻位置磷支。
DIRECTORY_DOCUMENTS:放置用戶創(chuàng)建的文檔的標(biāo)準(zhǔn)目錄。
DIRECTORY_DOWNLOADS:用于放置用戶下載文件的標(biāo)準(zhǔn)目錄食寡,這是頂級公共目錄的約定雾狈。
DIRECTORY_MOVIES:用戶放置用戶電影的標(biāo)準(zhǔn)目錄,但是媒體掃描器會在任何目錄中查找和收集電影抵皱。
DIRECTORY_MUSIC:用于放置任何音頻文件的標(biāo)準(zhǔn)目錄善榛。
DIRECTORY_NOTIFICATIONS:放置任何音頻文件的標(biāo)準(zhǔn)目錄辩蛋,一般是用戶可以選擇的通知列表(而不是普通音樂)
DIRECTORY_PICIURES:放置可供用戶使用的圖片的標(biāo)準(zhǔn)目錄。但是媒體掃描器將在任何目錄中查找和收集圖片移盆。
DIRECTORY_PODCASTS:放置任何音頻的標(biāo)準(zhǔn)目錄悼院,是可供選擇的播客列表中(而不是普通音樂)。
DIRECTORY_RINGTONES:用于放置任何音頻的標(biāo)準(zhǔn)目錄咒循,該文件可供用戶選擇鈴聲列表据途。
Google官方給出的例子:
void createExternalStoragePublicPicture() {
//創(chuàng)建了一個公共的放置圖片的路徑。
// Context.getExternalMediaDir() 叙甸,下面會進(jìn)行解釋
//上述方法的路徑:/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.確保文件夾存在,下面的方法表示創(chuàng)建相應(yīng)的文件夾蚁署。具體的文件就要靠流的操作來完成了便脊。
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.
//告訴媒體掃描器有關(guān)新文件的信息,立即告訴用戶光戈。
//下面代碼表示演示了手機(jī)多媒體掃描的流程。
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();
}
例子
//檢查設(shè)備介質(zhì)是否可以使用
public boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
}
先檢查設(shè)備介質(zhì)是否可用:
if (isExternalStorageWritable()) {
//這是使用的運(yùn)行時權(quán)限一個注解方式動態(tài)申請權(quán)限
SDTestActivityPermissionsDispatcher.saveFileOneWithCheck(SDTestActivity.this);
} else {
mTextView1.setText("設(shè)備不可以用");
}
設(shè)備可用遂赠,執(zhí)行相應(yīng)的方法:
//保存可以與其他應(yīng)用共享的文件
@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都是創(chuàng)建目錄的久妆,不是創(chuàng)建新的文件
* 創(chuàng)建了一次新的之后,表示已經(jīng)存在了跷睦,那么就不會在創(chuàng)建了筷弦,會返回一個false,表示文件夾未創(chuàng)建成功抑诸,但是實(shí)際上文件已經(jīng)存在了烂琴。
*具體效果如下圖一
*/
if (file.mkdir()) {
//表示文件創(chuàng)建成功了
mTextView1.setText("文件夾創(chuàng)建成功了");
//開始寫入文件
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
* 通過測試不難發(fā)現(xiàn),使用具體的文件通過具體的 流操作就可以完成了蜕乡, 是文件輸出流奸绷,其本身就具有再具體的file路徑下創(chuàng)建文件的功能。
*具體效果見圖二:
*/
String info = "恭喜您层玲,數(shù)據(jù)寫入成功了";
try {
FileOutputStream fileOutputStream = new FileOutputStream(file1);
fileOutputStream.write(info.getBytes());
fileOutputStream.close();
mTextView1.setText("創(chuàng)建新的文件成功了");
} catch (FileNotFoundException e) {
e.printStackTrace();
mTextView1.setText("創(chuàng)建新的文件失敗了");
} catch (IOException e) {
e.printStackTrace();
mTextView1.setText("創(chuàng)建新的文件出現(xiàn)了IO異常了");
}
} else {
mTextView1.setText("文件夾未創(chuàng)建成功");
file.delete();
}
}
建立文件夾号醉,如圖一:
最后建立相應(yīng)的文件,如圖二:
在5.0系統(tǒng)下:
在7.0系統(tǒng)下:
另外,保存到外部存儲的公共區(qū)域上如果你想在媒體掃描程序中隱藏您的文件
在您的外部文件目錄中包含名為 `.nomedia` 的空文件(注意文件名中的點(diǎn)前綴)辛块。 這將阻止媒體掃描程序讀取您的媒體文件畔派,
并通過 MediaStore 內(nèi)容提供程序?qū)⑵涮峁┙o其他應(yīng)用。
4润绵、將數(shù)據(jù)保存在外部存儲上的應(yīng)用私有位置上,這樣這些文件只能本應(yīng)用使用,別的應(yīng)用程序無法訪問.
簡單來講就是五個方法线椰,五個方法得到想要操作的文件夾路徑,接下來就是流的操作尘盼,往你想用的文件夾路徑中創(chuàng)建文件或者讀取文件了憨愉,所以這里只介紹這五個方法烦绳,對于流的操作不在寫。
當(dāng)用戶卸載您的應(yīng)用時莱衩,此目錄及其內(nèi)容將被刪除爵嗅。此外,系統(tǒng)媒體掃描程序不回讀取這些目錄中的文件笨蚁,因此不能從MediaStore內(nèi)容提供程序訪問這些文件睹晒。同樣,不應(yīng)將這些目錄用于最終屬于用戶的媒體括细,例如使用您的應(yīng)用拍攝或編輯照片或者您的應(yīng)用購買的音樂等伪很,這些文件應(yīng)該放在公共的目錄中。
當(dāng)應(yīng)用卸載時候,這部分內(nèi)容也會刪除,另外,盡管MediaStore提供的程序不能訪問此部分內(nèi)容,但是別的應(yīng)用程序具有READ_XXX權(quán)限的仍可以獲取全部文件時,仍可以得到,所以并不是完全的保密.
(1)getExternalFilesDir(String type)
如果正在處理的文件不適合其他應(yīng)用使用(比如應(yīng)用使用的圖形紋理或者音效)奋单,則可以考慮使用外部存儲上的私有存儲目錄锉试。此方法需要傳入type參數(shù)指定子目錄的類型。如果不需要特定的目錄览濒,也可以傳遞null獲得應(yīng)用私有目錄的根目錄呆盖。也可以指定具體的比如DIRECTORY_MOVIES
從Android 4.4開始,讀取或者寫入應(yīng)用私有目錄中的文件不在需要READ_EXTERNAL_STORAGE或者WRITE_EXTERNAL_STORAGE權(quán)限贷笛,可以通過添加maxSdkVersion屬性來聲明应又,只在較低版本的Android中請求該權(quán)限:
<manifest...>
<users-permission android:name = "android.permission.WRITE_EXTERNAL_STORAGE"
andorid:maxSdkVersion = "18"/>
</manifest>
下面是一些打印的Log:進(jìn)行方法的展示
(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
下面是測試?yán)樱?/p>
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系統(tǒng)下;
在7.0系統(tǒng)下:
(2)getExternalFilesDirs(String type)
與前面getExternalFilesDir方法一樣乏苦,不同的是此方法得到的是一個file[],一般取file[0]使用即可
有時株扛,已分配某個內(nèi)部存儲器分區(qū)用作外部存儲的設(shè)備可能提供了SD卡槽。在使用運(yùn)行 Android 4.3和更低版本的這類設(shè)備時汇荐,getExternalFilesDir()方法將僅提供內(nèi)部分區(qū)的訪問權(quán)限洞就,而您的應(yīng)用無法讀取或者寫入SD卡。
從Android 4.4開始掀淘,可以通過調(diào)用getEx ternalFilesDirs()來同時訪問這兩個位置旬蟋,該方法將會返回包含各個位置條目的File數(shù)組疗疟。數(shù)組中的第一個條目被視為外部主存儲栈雳,除非該位置已滿或者不可用嵌削,否則應(yīng)該使用該位置骑晶。
如果希望支持Android 4.3和更低版本的同時訪問兩個可能的位置耻煤,請使用支持庫中的靜態(tài)方法
ContextCompat.getExternalFilesDirs();在Android 4.3或者更低的版本中隐砸,此方法也會返回一個File數(shù)組膝捞,但其中始終包含一個條目鹰溜,只能在使用的時候使用File[0];
盡管MediaStore內(nèi)容提供程序不能訪問getExternalFilesDir()和getExternalFilesDirs()所提供的目錄架忌,但其他具有 READ_EXTERNAL_STORAGE權(quán)限的用用仍可訪問外部存儲上的所有文件吞彤,如上述文件,如果您想要完全限制您的文件訪問權(quán)限,則應(yīng)該講您的文件寫入到內(nèi)部存儲饰恕。
(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()相似挠羔,您也可以通過調(diào)用ContextCompat.getExternalCacheDirs()來訪問外部存儲上的緩存目錄。
為了節(jié)省文件空間并保持應(yīng)用性能埋嵌,您應(yīng)該在應(yīng)用的整個生命周期內(nèi)仔細(xì)管理您的緩存文件并移除其中不再需要的文件破加。
調(diào)用此方法,得到的文件路徑如圖所示:
5.0系統(tǒng)之下:
7.0系統(tǒng)之下雹嗦;
(4)getExternalCacherDirs();
此方法與前面提到的getExternalCacherDirs()一樣范舀。其返回值也是Files[];
(5)getExternalMediaDir()
執(zhí)行此方法:
/storage/emulated/0/Android/media/example.xlj.savecundemo
7.0系統(tǒng)之下:
發(fā)現(xiàn)此方法是在Android文件夾下建立了media文件夾,在其中建立了你自己應(yīng)用包名的文件夾了罪,你可以放數(shù)據(jù)锭环。前面提到的四個方法都是放在了Android/data/應(yīng)用包名/下
數(shù)據(jù)庫存儲
Android 提供了對于SQlite數(shù)據(jù)庫的完全支持,應(yīng)用中的任何類(不包括應(yīng)用外部的類)均可按名稱訪問您所創(chuàng)建的任何數(shù)據(jù)庫泊藕。
數(shù)據(jù)庫存儲涉及到兩個關(guān)鍵類:
SQLiteDatabase
此類SQLiteDatabase公開了管理SQLite數(shù)據(jù)庫的方法辅辩。SQLiteDatabase具有創(chuàng)建、刪除娃圆、執(zhí)行SQL命令以及執(zhí)行其他常見數(shù)據(jù)庫管理任務(wù)的方法玫锋。
SQLiteOpenHelper
實(shí)際操作是寫一個類繼承此類
SQLiteOpenHelper是管理數(shù)據(jù)庫創(chuàng)建和版本管理的助手類。
數(shù)據(jù)庫名稱在應(yīng)用中必須是唯一的讼呢,而不是跨所有應(yīng)用程序景醇。
比如自己寫的一個例子:
public class MySQLiteOpenHelper extends SQLiteOpenHelper {
//構(gòu)造方法一
public MySQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version, DatabaseErrorHandler errorHandler) {
super(context, name, factory, version, errorHandler);
}
//構(gòu)造方法二
public MySQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
// super(context, name, factory, version);
//可以通過外界參數(shù)傳入創(chuàng)建,這里直接賦值了
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)");
}
//用于更新數(shù)據(jù)庫版本
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
//參數(shù)一表示以前的版本吝岭,oldVersion
//參數(shù)二表示新的版本,newVersion
if (i1 > i){
sqLiteDatabase.execSQL("drop table if exists tb_my");
onCreate(sqLiteDatabase);
}
}
}
在應(yīng)用中使用:
//使用自定義的SQLiteOpenHelper
MySQLiteOpenHelper mySQLiteOpenHelper = new MySQLiteOpenHelper(this,"",null,1);
//執(zhí)行完上局話得到吧寺。窜管。什么反應(yīng)也沒有
SQLiteDatabase sqLiteDatabase = mySQLiteOpenHelper.getReadableDatabase();
//通過幫助類得到管理數(shù)據(jù)庫的方法。便會得到在內(nèi)部存儲中建立相應(yīng)的數(shù)據(jù)庫
sqLiteDatabase.insert("tb_my","title",null);
結(jié)果:
寫給自己的備注:
關(guān)于數(shù)據(jù)庫這邊簡單記錄一下稚机,上面涉及到的兩個鏈接都是Google官方提供的資料幕帆,計劃在整理新的文檔,因為其涉及的還是比較多的赖条。整理完成后失乾,將Google提供的鏈接替換成自己文章的鏈接。
網(wǎng)絡(luò)存儲
所謂的網(wǎng)絡(luò)存儲簡單來講纬乍,就是將數(shù)據(jù)資料放在網(wǎng)絡(luò)服務(wù)器上碱茁。
由于市面上的網(wǎng)絡(luò)框架比較多,就不做詳細(xì)介紹了仿贬。下面提供兩個Google推薦的兩個參考類纽竣、