二、文件存儲
1簡介:文件存儲類似與JAVA的IO流技術對文件進行讀寫。需要注意的是:Android是基於Linux架構的,所以除了應用的私有目錄/data/data/其他的系統文件因爲權限問題無法進行讀寫
2存儲區域:內部存儲與外部存儲
2.1內部存儲:一般指設備自帶的非易失性存儲器,有如下特點:
永遠可用。
存儲在內部存儲區域的數據默認情況下只對你的app可用。無論是用戶或者是其他app都不能訪問你的數據。
當用戶卸載你的app時,系統會自動移除app在內部存儲上的所有文件。
2.2外部存儲:指可拆卸的存儲介質,比如微型的SD卡。有如下特點:
不一定一直可以訪問,因爲用戶可以拆卸外部存儲設備。
存儲在外部存儲的文件是全局可讀的,沒有訪問限制,不受你的控制。可以和其他apps分享數據,用戶使用電腦也可以訪問在外部存儲中的文件。
當用戶卸載你的app時,只有當你把文件存儲在以 getExternalFilesDir().獲得的路徑下時,系統纔會幫你自動移除。
2.3注意:
一些設備把永久的存儲區域分爲"internal"和"external"的分區,所以即便沒有可拆卸的存儲介質,這些設備永遠都有兩種存儲區域,並且不管外部存儲區到底是可拆卸的還是內置的,APIs的行爲是一致的。
默認情況下app是安裝在內存上的,可以通過在manifest中指定android:installLocation 屬性來安排app的安裝位置。具體見AppInstall Location.
3不同存儲方式的存儲操作
3.1內部存儲(在手機內存讀寫文件):與JAVA的IO流相同的操作,只是Android提供了兩個特殊的方法來獲得流。
// 打開文件輸入流
FileInputStream fis = openFileInput("YOU_FILE_NAME");
//打開文件輸出流
FileOutputStream fos = openFileOutput("YOU_FILE_NAME", MODE_APPEND);
如果需要緩存一些文件,你應該使用createTempFile().比如,下面的例子從一個URL中提取了文件名,然後利用該文件名創建了一個文件存儲在應用的內部緩存路徑下:public File getTempFile(Context context, String url) {
File file;
try {
String fileName = Uri.parse(url).getLastPathSegment();
file = File.createTempFile(fileName, null, context.getCacheDir());
catch (IOException e) {
// Error while creating file
}
return file;
}
3.2外部存儲(讀寫SD卡)
步驟:
1調用Environment的getExternalStorageState()方法判斷是否有SD卡且應用程序是否具有讀寫SD卡的權限。
2調用Environment的getExternalStorageDirectory()方法來獲取外部存儲器,也就是SD卡的目錄
3使用FileInputStream,FileOutputStream,FileReader,FileWriter讀寫SD卡里的文件
爲了能夠讀寫SD卡上的文件,應該在應用程序的清單文件中添加相應權限。
<!-- 在SD卡中創建與刪除文件權限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<!-- 向SD卡寫入數據權限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- 向SD卡讀取數據權限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
PS:如果你的應用包含了WRITE_EXTERNAL_STORAGE 權限,它隱式地包含了讀取權限。
因爲外部存儲很有可能不可用,所以每次使用前都需要檢查可用性。
/* 檢測是否存在SD卡並是否支持讀寫 */
public boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
}
/*檢測是否存在最少能支持讀取的SD卡 */
public boolean isExternalStorageReadable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state) ||
Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
return true;
}
return false;
}
回顧與總結:
1每個應用程序所在的包都會有一個私有的存儲數據的目錄,只有屬於這個包中的應用程序纔有寫入的權限,每個包中應用程序的私有數據目錄位於 Android系統中的絕對路徑/data/data/<package>/目錄中,除了私有目錄,應用程序還擁有/sdcard目錄即 Android設備上的SD卡的寫入權限。文件系統中其他的系統目錄,第三方應用程序都是不可寫的。
那麼問題來了:某個應用私有目錄下的文件寫入時爲可讀寫的,那麼其他應用又該如何讀寫呢?
解:獲得其他應用的上下文Context,順理成章獲得應用私有目錄的引用,順利用IO流讀寫。
<span style="white-space:pre"> </span>Context c = createPackageContext("Package_Name", Context.CONTEXT_IGNORE_SECURITY);
File test =new File( c.getFilesDir(),"Your_File");
if(test.exists()){Log.d("ReadOtherPreferences.test","true"+test.getAbsolutePath()+" and"+test.getCanonicalPath());}
//獲得File引用後,就用經典的IO流來處理就行了,不要用Android的openFileInput(name)方法,這些都只針對本身應用私有目錄的
FileInputStream f = new FileInputStream(test);
BufferedReader bf = new BufferedReader(new InputStreamReader(f));
反思:雖然以上理論上是可行的,但是想要獲得已經可以讀寫的其他應用程序的引用仍然需要其他應用的包名與文件名,但這在實際上卻很難實現,因爲我們很難獲得其他應用的包名與文件名(如果我們能輕易知道,某種意義上我們不是能夠自由讀取其他應用的文件名,不管這個應用支不支持讀寫,這個應用本身設計上就是不安全的,想想信息泄漏)所以最好使用openFIleInput或openFileOupt方法時最好把權限設爲MODE_PRIVATE模式。
2我們知道Android是基於LInux架構的,所以文件權限管理很嚴格,本身應用只有私有目錄與SD卡才能進行訪問。使用Android自有的輸入流與輸出流也一樣,只能對/data/data/<package_name>/files裏的文件進行讀寫
那麼問題來了:經典的IO流能否讀寫本身應用包下的其他目錄呢?當然毫無疑問,經典IO流也是無法訪問其他系統目錄和第三方應用文件的
解:測試:讀寫緩存文件cache,通過經典IO流(通過一個按鈕來寫入)
File cache = new File(getCacheDir(),"in.txt");
Log.d("org.crazyit.io.write", "cache路徑是否存在"+cache.exists());
FileOutputStream fos = new FileOutputStream(cache, true);
PrintStream ps = new PrintStream(fos);
ps.println(content);
Log.d("org.crazyit.io.write", "寫入成功");
ps.close();
2次測試後,顯示寫入成功,證明經典IO流能夠對應用下其他目錄進行讀寫
換個方式,用經典IO流新建新目錄新文件,即使用File cache = new File("/txt/in.txt")與File cache = new File("in.txt");File cache = new File(getCacheDir()+"/txt","in.txt");分別測試,發現找不到File,自此新建新文件夾失敗
那麼問題來了:如何在私有目錄下新建文件夾呢?
解:使用File類自帶的辦法mkdir()創建文件夾,createNewFile創建文件
//新建文件夾
File cache = new File("/data/data/org.crazyit.io/test");
if(!cache.exists()){
cache.mkdir();
}
//新建文件a
File cache = new File("/data/data/org.crazyit.io/test"+"/2.txt");
if(!cache.exists()){
cache.createNewFile();
}
測試結果如下:
反思:經典IO流對與應用本身data/data/<package_name>/file(隨意)擁有讀寫權限,也就是比系統自帶的流權限要更寬點,由於是本身應用,包名也很容易能得到,但是對於經典IO流的File的聲明,應用不能像自帶的流一樣自動定位到應用根路徑,即類似File f = new File("1.txt");這種形式就不行了,一定要全路徑File f = new File("/data/data/<package_name>/File_Name")
3外部存儲不像內部存儲一樣,有嚴格的權限限制,任何文件都可以被任何用戶任何應用訪問。但是外部存儲也有私有和公有的概念,不同於內部存儲的權限管理,本質上外部存儲的私有和公有都是可以被所有用戶訪問的,但是區別在與應用刪除的時候,私有的會隨着應用刪除而刪除,而公有會自動保留下來。例如下載時的安裝包我們會放在私有目錄下,照相應用拍攝的圖片會放在公有目錄下。如果要確定絕對的私有公有,那麼就要修改文件的權限了 PS:獲得sd卡目錄的簡單格式 File sdFile = new File("/sdcard");
那麼問題來了:如何創建私有目錄與公有目錄呢?
解:只要有路徑path,獲得File的引用,輕而易舉創建目錄,與第2點沒什麼區別,相反不用絕對路徑反而更簡單了,網上大把例子
私有目錄:getExternalFilesDir()
公有庫目錄:getExternalStoragePublicDirectory();
公有根目錄:getExternalStorageDirectory();
那麼問題來了:能創建目錄,文件,必然要有刪除,那我們要怎麼刪除呢?
解:內部存儲的私有文件:直接就通過Context.deleteFile()方法刪了
外部存儲的不管私有還是公有:獲得文件的File引用,直接調用delete()刪了
反思:注意緩存要定期清理
問題來了:如何獲得空間容量
解:經測試,以下兩個辦法得出的容量大小跟網上的方法測試出來的是一樣的
SD卡:獲得SD卡路徑,剩餘空間 getFreeSpace() 和總空間getTotalSpace()
手機:獲得Data路徑,剩餘空間 getFreeSpace() 和總空間getTotalSpace()
引用:http://vinny-w.iteye.com/blog/1339827
http://blog.csdn.net/zoe6553/article/details/6119698
http://www.cnblogs.com/hanyonglu/archive/2012/03/01/2374894.html
http://www.cnblogs.com/mengdd/archive/2013/05/02/3055465.html