最近碰到一個項目要用到Android文件的存取昼牛,之前也一直沒有完全搞清楚统台。最近整理了一下給大家一起分享,也希望大家如果遇到這方面的問題少走一些彎路冰更。
前面兩小節(jié)主要是一些java的基礎(chǔ)如果基礎(chǔ)比較好的同學(xué)可以直接跳過看后面兩節(jié)产徊。
Demo下載地址
關(guān)于Java I/O流
為什么講這個是因為我覺得會有很多和我一樣Java基礎(chǔ)不扎實的小伙伴在看到j(luò)ava.io下面這么多輸入輸出流類實在是沒有頭緒不知道該用哪個,所以我這里通過查閱的資料給大家大概講一講蜀细。
Java中的流是對字節(jié)序列的抽象舟铜,可作為一個輸入源,也可作為一個輸出的目的地奠衔。主要有兩種:字節(jié)流和字符流谆刨。
- 字節(jié)流處理的基本單位為單個字節(jié)塘娶,通常用來處理二進(jìn)制數(shù)據(jù)也就是說我們用它來處理文件。
- 字符流最基本的單元是Unicode碼元(大小2字節(jié))痊夭,通常用來處理文本數(shù)據(jù)刁岸。
JDK所提供的所有流類位于java.io包中,都分別繼承自以下四種抽象流類:
InputStream
:繼承自InputStream的流都是用于向程序中輸入數(shù)據(jù)的她我,且數(shù)據(jù)單位都是字節(jié)(8位)难捌。
OutputSteam
:繼承自O(shè)utputStream的流都是程序用于向外輸出數(shù)據(jù)的,且數(shù)據(jù)單位都是字節(jié)(8位)鸦难。
Reader
:繼承自Reader的流都是用于向程序中輸入數(shù)據(jù)的根吁,且數(shù)據(jù)單位都是字符(16位)。
Writer
:繼承自Writer的流都是程序用于向外輸出數(shù)據(jù)的合蔽,且數(shù)據(jù)單位都是字符(16位)击敌。
這樣大家應(yīng)該再看這些class的時候就大概有方向了。
File類
既然我們要進(jìn)行文件讀寫那我們就要對我們操作的類深入理解拴事。
An abstract representation of file and directory pathnames.User interfaces and operating systems use system-dependent pathname strings to name files and directories. This class presents an abstract, system-independent view of hierarchical pathnames.
這個是官方API的解釋沃斤,大概翻譯一下就是:
這個類是文件和目錄路徑名的抽象表示。它提供了一個抽象的獨立系統(tǒng)分層的路徑名視圖刃宵。用戶界面和操作系統(tǒng)使用與系統(tǒng)相關(guān)的路徑名字符串來命名文件和目錄衡瓶。
為什么說與系統(tǒng)相關(guān)呢。主要有兩個
- 一個是依賴系統(tǒng)的前綴字符串牲证,例如磁盤驅(qū)動說明符哮针,UNIX根目錄為“/”等等。
- 另一個就是分割符了這個很顯然也跟系統(tǒng)相關(guān)坦袍。
當(dāng)然了我們Android不用考慮那么多十厢,我們只需要注意分隔符,而這個分隔符File類也幫我們申請好了File.separator
我們new 一個File官方給了四個構(gòu)造方法File(String pathname)
捂齐,File(String parent,String child)
,File(File parent,String child)
,FIle(URI uri)
蛮放。這四個方法也非常好理解我這里也不詳細(xì)說明了。
FIle類里面的方法也無非是一些對文件的判斷和一些基本操作奠宜,大家可以看一下API文檔包颁,相信機(jī)智的你一定看的懂的。但是有幾點要注意:
- 文件是分區(qū)的压真,不同區(qū)域是有不同的權(quán)限娩嚼。這一點我在下一節(jié)將會詳細(xì)講解。
- File類的實例是不可變的榴都,也就是說一旦創(chuàng)建待锈,由File對象表示的抽象路徑名將永遠(yuǎn)不會改變漠其。
- 在Android上嘴高,文件名的基礎(chǔ)文件系統(tǒng)編碼始終是UTF-8竿音。
Android的內(nèi)部儲存和外部儲存
既然我們要操作文件那我們還要知道我們要把文件放在哪。那文件放在哪里那就跟Android系統(tǒng)有關(guān)了拴驮。Android把我們手機(jī)分為內(nèi)部儲存和外部儲存春瞬。我們來看一下官方的解釋:
Internal storage:
- It's always available.
- Files saved here are accessible by only your app.
- When the user uninstalls your app, the system removes all your app's files from internal storage.
External storage:
- It's not always available, because the user can mount the external storage as USB storage and in some cases remove it from the device.
- It's world-readable, so files saved here may be read outside of your control.
- When the user uninstalls your app, the system removes your app's files from here only if you save them in the directory from
getExternalFilesDir()
大家可以自行谷歌翻譯一下,我給大家提煉一下:
內(nèi)部儲存是指只有自己應(yīng)用才能訪問套啤,并且如果應(yīng)用刪除了宽气,那么這些文件也就跟著一起刪除了。
外部儲存是整個手機(jī)都可以訪問的所以在內(nèi)容上它是不穩(wěn)定的潜沦,因為只要有讀寫的權(quán)限就可以修改這些文件萄涯。(關(guān)于權(quán)限我會在最后一節(jié)給大家詳解)而在硬件上來說它也有可能是不穩(wěn)定的(比如sd卡)。
所以這就和大家以前所理解的概念不同了唆鸡,不能單純的以為不是sd卡就是內(nèi)部儲存涝影。
操作內(nèi)部儲存:
獲得內(nèi)部儲存文件夾的方法都在Cntext這個類中,主要有以下幾個方法:
-
getFilesDir()
:這個返回的是內(nèi)部儲存的根目錄的絕對路徑争占。 -
getCacheDir()
這個方法返回的是內(nèi)部儲存緩存目錄的絕對路徑燃逻。 -
getDir(String name, int mode)
:檢索,根據(jù)需要創(chuàng)建一個新目錄臂痕,應(yīng)用程序可以在其中放置自己的自定義數(shù)據(jù)文件伯襟。后面那個參數(shù)mode
一般寫0或者MODE_PRIVATE
。
給大家看一下這些目錄具體在哪里
Log.e("File","內(nèi)部緩存自定義目錄 : "+getDir("getDir",MODE_PRIVATE));
Log.e("File","內(nèi)部儲存的根目錄 : "+getFilesDir());
Log.e("File","內(nèi)部儲存緩存目錄 : "+getCacheDir());
Log.e("File" ,"內(nèi)部儲存緩存代碼目錄 : "+getCodeCacheDir());
以下是Log:
E/File: 內(nèi)部緩存自定義目錄 : /data/data/com.kachidoki.learnfiletest/app_getDir
E/File: 內(nèi)部儲存的根目錄 : /data/data/com.kachidoki.learnfiletest/files
E/File: 內(nèi)部儲存緩存目錄 : /data/data/com.kachidoki.learnfiletest/cache
E/File: 內(nèi)部儲存緩存代碼目錄 : /data/data/com.kachidoki.learnfiletest/code_cache
這些返回的目錄都是在data/data/包名
下握童,這個目錄用戶是不給用戶看的姆怪,你的手機(jī)如果沒有root通過文件管理看不到。
Android給操作內(nèi)部儲存文件提供了一個簡便的方法也在Context
這個類中澡绩,FileOutputStream openFileOutput (String name, int mode)
片效。這個方法返回一個FileOutputStream
方便我們操作。后面那個mode
參數(shù)默認(rèn)操作使用0或MODE_PRIVATE
表示會創(chuàng)建一個文件或者直接替換原有的文件英古。也可以使用MODE_APPEND
直接加到現(xiàn)有文件中淀衣。
下面是操作內(nèi)部儲存的Demo:
/** * 寫在內(nèi)部儲存Demo */private void createPrivateFile(){
String filename = "myfile.txt";
String string = "Hello world!";
FileOutputStream outputStream;
try {
outputStream = openFileOutput(filename, Context.MODE_PRIVATE);
outputStream.write(string.getBytes());
outputStream.close();
Toast.makeText(MainActivity.this,"文件位置在"+getFilesDir().getAbsolutePath(),Toast.LENGTH_LONG).show();
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(MainActivity.this,"出錯誤了哦",Toast.LENGTH_SHORT).show();
}
}
操作外部儲存
外部儲存呢分為兩個部分但是操作是一樣的,功能上有一些區(qū)別:
- 公共文件
獲得這些目錄的方法都在Environment
這個類中
getExternalStoragePublicDirectory(String type)
:這是用戶通常放置和管理自己的文件的地方召调,所以你應(yīng)該小心放在這里膨桥,以確保你不要刪除他們的文件或妨礙自己的組織的方式。這里的type
可以用DIRECTORY_MUSIC
,DIRECTORY_MOVIES
這些Environment中的常量唠叛。
Log.e("File","外部儲存的根目錄一般不用:"+Environment.getExternalStorageDirectory());
Log.e("File","外部儲存的公共文件根目錄: "+Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES));
Log.e("File","外部儲存的狀態(tài): "+Environment.getExternalStorageState());
Log.e("File","下載緩存目錄 :"+Environment.getDownloadCacheDirectory());
Log:
E/File: 外部儲存的根目錄一般不用:/storage/emulated/0
E/File: 外部儲存的公共文件根目錄: /storage/emulated/0/Pictures
E/File: 外部儲存的狀態(tài): mounted
E/File: 下載緩存目錄 :/cache
要寫在這部分需要讀寫的權(quán)限只嚣,并且官方建議用之前先判斷一下外部儲存的狀態(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;
}
- 私有部分
獲得這些目錄的方法都在Context
艺沼。怎么又回到Context
了呢册舞。因為這些文件也會在應(yīng)用刪除的時候跟隨這一起刪掉~只是和內(nèi)部儲存不同的是這個部分可以給用戶和其他應(yīng)用訪問。所以才叫外部儲存的私有部分嘛障般。
Log.e("File","外部儲存私有部分根目錄 : "+ getExternalFilesDir(null));
Log.e("File","外部儲存私有部分音樂 : "+ getExternalFilesDir(Environment.DIRECTORY_MUSIC));
Log.e("File","外部儲存私有部分緩存 : "+getExternalCacheDir());
Log:
E/File: 外部儲存私有部分根目錄 : /storage/emulated/0/Android/data/com.kachidoki.learnfiletest/files
E/File: 外部儲存私有部分音樂 : /storage/emulated/0/Android/data/com.kachidoki.learnfiletest/files/Music
E/File: 外部儲存私有部分緩存 : /storage/emulated/0/Android/data/com.kachidoki.learnfiletest/cache
以下是操作外部儲存的Demo:
/**
* 寫在外部儲存應(yīng)用專屬位置demo
*/
public void createExternalStoragePrivateFile(){
File file = new File(getExternalFilesDir(null),"demoFile.jpg");
try {
InputStream is =getResources().openRawResource(R.drawable.supreme);
OutputStream os = new FileOutputStream(file);
byte[] data = new byte[is.available()];
is.read(data);
os.write(data);
os.close();
is.close();
Toast.makeText(MainActivity.this,"圖片位置在"+getExternalFilesDir(null),Toast.LENGTH_LONG).show();
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(MainActivity.this,"出錯誤了哦",Toast.LENGTH_SHORT).show();
}
}
public void deleteExternalStoragePrivateFile() {
// Get path for the file on external storage. If external
// storage is not currently mounted this will fail.
File file = new File(getExternalFilesDir(null), "demoFile.jpg");
if (file != null) {
file.delete();
}
}
最后呢再給大家摘一段API在介紹Environment.getExternalStorageDirectory()
這個方法的時候特地寫的一段文字來加深大家對外部儲存的理解调鲸,大家自己翻譯體會一下:
Note: don't be confused by the word "external" here. This directory can better be thought as media/shared storage. It is a filesystem that can hold a relatively large amount of data and that is shared across all applications (does not enforce permissions). Traditionally this is an SD card, but it may also be implemented as built-in storage in a device that is distinct from the protected internal storage and can be mounted as a filesystem on a computer.
權(quán)限問題
涉及到操作文件肯定會有權(quán)限的問題盛杰,所以這里我是一定要講滴。
內(nèi)部儲存:無需任何權(quán)限藐石,即可在內(nèi)部存儲中保存文件即供。 您的應(yīng)用始終具有在其內(nèi)部存儲目錄中進(jìn)行讀寫的權(quán)限。
外部儲存私有部分:以
Context.getExternalFilesDir
為首的一系列方法于微,從Android4.4開始不需要WRITE_EXTERNAL_STORAGE
和/或READ_EXTERNAL_STORAGE
權(quán)限逗嫡。(之前的版本還是需要)
但是,如果要訪問其他軟件的外部儲存的私有部分那就還是需要這兩個權(quán)限(前提是你要知道別的包名株依,就是路徑嘛)驱证。外部儲存公共部分:這部分肯定是需要權(quán)限的嘛~~~~就是需要在
manifest
聲明READ_EXTERNAL_STORAGE
,WRITE_EXTERNAL_STORAGE
。
注:如果您的應(yīng)用使用WRITE_EXTERNAL_STORAGE
權(quán)限恋腕,那么它也隱含讀取外部存儲的權(quán)限雷滚。
還有一點要注意,如果你的app的targetSdkVersion
大于等于23吗坚,也就是說大于Android6.0的版本那你還要在讀寫文件之前還要動態(tài)申請Runtime權(quán)限祈远,否則你的程序默認(rèn)是沒有讀寫權(quán)限的。關(guān)于這個如果不了解的可以看一下下面這篇文章寫的比較易懂:
聊一聊Android 6.0的運行時權(quán)限
最后
放一下文章里的Demo商源,我的Demo里面還有運用Retrofit+Rxjava實現(xiàn)下載文件并顯示進(jìn)度的例子哦车份,如果也有小伙伴正在為這個苦惱的話可以參考一下~
Demo下載地址
本人水平有限,如果文章中出現(xiàn)什么問題歡迎及時指出大家一起探討一同進(jìn)步牡彻,有什么問題可以留言扫沼,可以私信。
參考文檔:
保存文件
Context.Class
Environment.Class
File.Class
android中的文件操作詳解以及內(nèi)部存儲和外部存儲