作者:鄒峰立,微博:zrunker椒功,郵箱:zrunker@yahoo.com白嘁,微信公眾號(hào):書客創(chuàng)作坑鱼,個(gè)人平臺(tái):www.ibooker.cc。
本文選自書客創(chuàng)作平臺(tái)第6篇文章絮缅。閱讀原文 鲁沥。
Android中可以在設(shè)備本身的存儲(chǔ)設(shè)備或者外接設(shè)備中創(chuàng)建用戶存儲(chǔ)的保存數(shù)據(jù)的文件。這些文件在默認(rèn)狀態(tài)下是不能在不能的程序間共享盟蚣,但是可以通過Content Provider進(jìn)行數(shù)據(jù)共享黍析。
文件存儲(chǔ)不對(duì)存儲(chǔ)的內(nèi)容進(jìn)行任何的格式化處理,所有數(shù)據(jù)都是原封不動(dòng)地保存到文件當(dāng)中屎开,因而它比較適合用于存儲(chǔ)一些簡(jiǎn)單的文本數(shù)據(jù)或二進(jìn)制數(shù)據(jù)阐枣。
文件存儲(chǔ)有兩種方式,一種是存儲(chǔ)到手機(jī)內(nèi)存中(memory)奄抽,一種是存儲(chǔ)到sd卡中蔼两。該如何實(shí)現(xiàn)這兩種方式呢?
首先要理解什么是文件的操作模式逞度?
- MODE_PRIVATE:當(dāng)指定同樣文件名時(shí)會(huì)覆蓋原文件中的內(nèi)容额划。
- MODE_APPEND:當(dāng)該文件已存在時(shí)就往文件中追加內(nèi)容,不會(huì)創(chuàng)建新文件档泽。
- 還有另外兩種(android4.2被廢棄)俊戳,MODE_ WORLD_ READABLE和MODE_WORLD _WRITEABLE揖赴,這兩種模式表示允許其他的應(yīng)用程序?qū)ξ覀兂绦蛑械奈募M(jìn)行讀寫操作。
通過案例說明該如何進(jìn)行文件存儲(chǔ):本案例中有一個(gè)EditText當(dāng)點(diǎn)擊‘保存到內(nèi)存’按鈕將會(huì)把EditText輸入內(nèi)容保存到內(nèi)存文件testmemory.json抑胎,當(dāng)點(diǎn)擊‘讀取內(nèi)存’按鈕將會(huì)把testmemory.json中的數(shù)據(jù)讀取出來燥滑,顯示到TextView上。當(dāng)點(diǎn)擊‘保存到SD卡’按鈕將會(huì)把EditText輸入內(nèi)容保存到內(nèi)存文件testsd.json阿逃,當(dāng)點(diǎn)擊‘讀取SD卡’按鈕將會(huì)把testsd.json中的數(shù)據(jù)讀取出來铭拧,顯示到TextView上。
布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="@+id/edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textMultiLine"
android:padding="15dp" />
<Button
android:id="@+id/btn_memory"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@string/save_memory" />
<Button
android:id="@+id/btn_sd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@string/save_sd" />
<Button
android:id="@+id/btn_read_memory"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@string/read_memory" />
<Button
android:id="@+id/btn_read_sd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@string/read_sd" />
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/read_text"
android:padding="10dp" />
</LinearLayout>
效果圖:
一恃锉、存儲(chǔ)到手機(jī)內(nèi)存中
理解:既然是想將數(shù)據(jù)保存到文件當(dāng)中搀菩,那么對(duì)于文件的讀寫,必然要借助于流破托。如果要將文件存儲(chǔ)到內(nèi)存中,可以借助于Content類提供的openFileOutput和openFileInput兩個(gè)方法來操作文件的寫入寫出炼团。
/**
* @param name 文件名
* @param mode 文件的操作模式
*/
FileOutputStream openFileOutput(String name, int mode);
注:openFileOutput方法是用來獲取文件輸出流瘟芝,用于寫入內(nèi)容易桃。在該方法中,參數(shù)name是指文件名不可以包含路徑锌俱,因?yàn)樗械奈募际悄J(rèn)存儲(chǔ)到/data/data/<package name>/files/目錄下。
/**
* @param name 文件名
*/
FileInputStream openFileInput(String name)造寝;
注:openFileInput方法是用來獲取文件輸入流,用來讀取內(nèi)容吭练。同樣诫龙,在該方法中鲫咽,參數(shù)name是指文件名不可以包含路徑。
實(shí)現(xiàn):
定義數(shù)據(jù)保存方法:writeMemoryData(Object obj)
/**
* 保存數(shù)據(jù)到內(nèi)存
*
* @param obj 待保存數(shù)據(jù)
* @return true/false(成功/失敗)
*/
private boolean writeMemoryData(Object obj) {
boolean bool = false;
FileOutputStream fos = null;
try {
// 構(gòu)建Properties
Properties properties = new Properties();
// Properties添加數(shù)據(jù)
properties.put(mKey, obj);
fos = this.openFileOutput("testmemory.json", Context.MODE_PRIVATE);
// 將數(shù)據(jù)寫入文件(流)
properties.store(fos, "測(cè)試文件");
bool = true;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null)
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return bool;
}
注:上面代碼中提到Properties锦聊,可以理解為屬性設(shè)置文件集箩绍,它繼承Hashtable<Object, Object>,而Hashtable繼承Map材蛛,所以可以把Properties當(dāng)中Map來使用,通過保存鍵值對(duì)相關(guān)信息芽淡。
定義數(shù)據(jù)讀取方法:readMemoryData(String key)
/**
* 讀取內(nèi)存數(shù)據(jù)
*
* @param key 數(shù)據(jù)對(duì)應(yīng)鍵值
* @return 待讀取的數(shù)據(jù)
*/
private Object readMemoryData(String key) {
Object obj = null;
FileInputStream fis = null;
try {
// 構(gòu)建Properties
Properties properties = new Properties();
fis = this.openFileInput("testmemory.json");
// 加載文件
properties.load(fis);
obj = properties.get(key);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null)
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return obj;
}
注:參數(shù)key是用來取Properties中保存的數(shù)據(jù)吐绵。
到這里就要開始寫邏輯實(shí)現(xiàn):
/**
* 文件存儲(chǔ)
* Created by 鄒峰立 on 2017/9/19 0019.
*/
public class FileActivity extends AppCompatActivity implements View.OnClickListener {
private EditText editText;
private TextView textView;
private final String mKey = "mKey";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_file);
initView();
}
// 初始化控件
private void initView() {
editText = findViewById(R.id.edittext);
textView = findViewById(R.id.text);
Button saveMemoryBtn = findViewById(R.id.btn_memory);
saveMemoryBtn.setOnClickListener(this);
Button readMemoryBtn = findViewById(R.id.btn_read_memory);
readMemoryBtn.setOnClickListener(this);
}
// 按鈕點(diǎn)擊事件監(jiān)聽
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_memory:// 保存到內(nèi)存
String text = editText.getText().toString().trim();
if (!TextUtils.isEmpty(text)) {
boolean bool = writeMemoryData(text);
if (bool) {
Toast.makeText(this, "寫入成功", Toast.LENGTH_SHORT).show();
editText.setText("");
} else
Toast.makeText(this, "寫入失敗", Toast.LENGTH_SHORT).show();
}
break;
case R.id.btn_read_memory:// 讀取內(nèi)存
String str = readMemoryData(mKey).toString();
textView.setText(str);
break;
}
}
}
因?yàn)樯厦嬉呀?jīng)提到writeMemoryData和readMemoryData己单,所以這里省略了writeMemoryData和readMemoryData耙饰,只是用來簡(jiǎn)單說明具體實(shí)現(xiàn)邏輯。
二廷痘、存儲(chǔ)到sd卡中
內(nèi)存存儲(chǔ)一般只用于存儲(chǔ)小數(shù)據(jù)件已,當(dāng)數(shù)據(jù)量較大的時(shí)候,可以將大數(shù)據(jù)保存到SD卡相關(guān)文件當(dāng)中兄猩。
Environment類簡(jiǎn)介:
Environment可以說是操作SD卡一個(gè)非常重要的類鉴未。
- Environment兩個(gè)重要常量:
- Environment.MEDIA_MOUNTED:外部存儲(chǔ)器可讀可寫。
- Environment.MEDIA_ MOUNTED_ READ_ONLY:外部存儲(chǔ)器只讀淹真。
- Environment常用方法:
- getExternalStorageDirectory():獲取SDCard的目錄连茧,/mnt/sdcard。
- getExternalStorageState():獲取外部存儲(chǔ)器的當(dāng)前狀態(tài)值纱。
在本案例當(dāng)中坯汤,需要借助于Environment判斷SD卡狀態(tài)(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)),以及獲取SD卡目錄(Environment.getExternalStorageDirectory())疆偿。
定義數(shù)據(jù)寫入SD卡文件方法:writeSdData(Object obj)
/**
* 寫入SD卡文件
*
* @param obj 待寫入對(duì)象
*/
private boolean writeSdData(Object obj) {
boolean bool = false;
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {// 判斷外部存儲(chǔ)是否可讀可寫
RandomAccessFile raf = null;
try {
// 獲取SD卡路徑
File sdDir = Environment.getExternalStorageDirectory();
// 獲取SD卡目錄 /mnt/sdcard。
String sdPath = sdDir.getAbsolutePath();
// 創(chuàng)建文件
File file = new File(sdPath, "testsd.json");
if (!file.exists()) {
boolean bool1 = file.createNewFile();
if (!bool1)
return false;
}
// FileOutputStream fos = null;
// try {
// fos = new FileOutputStream(file);
// fos.write(obj.toString().getBytes());
// } catch (FileNotFoundException e) {
// e.printStackTrace();
// } catch (IOException e) {
// e.printStackTrace();
// } finally {
// try {
// if (fos != null)
// fos.close();
// } catch (IOException e) {
// e.printStackTrace();
// }
// }
// 指定文件創(chuàng)建RandomAccessFile對(duì)象
raf = new RandomAccessFile(file, "rw");
// 將文件記錄指針移動(dòng)最后
raf.seek(file.length());
// 寫入內(nèi)容
raf.write(obj.toString().getBytes());
bool = true;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (raf != null)
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return bool;
}
注:這里有寫了兩種方式對(duì)文件進(jìn)行寫入,一種是通過FileOutputStream直接寫入处铛,另外一種通過RandomAccessFile對(duì)象對(duì)文件寫入。這里主要區(qū)別在于RandomAccessFile對(duì)象可以指定文件寫入位置奕塑,操作更加方便家肯。而FileOutputStream雖然提供了一個(gè)FileOutputStream(File file, boolean append)的構(gòu)造方法,當(dāng)append為true的時(shí)候换棚,會(huì)在文件尾部進(jìn)行寫入反镇,當(dāng)append為false的時(shí)候會(huì)覆蓋之前的文件,但是沒法制定寫入具體位置颇蜡。
定義讀取SD卡文件內(nèi)容方法:readSdData()
/**
* 讀取SD卡文件內(nèi)容
*/
private String readSdData() {
StringBuilder sb = new StringBuilder();
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {// 判斷外部存儲(chǔ)是否可讀可寫
// 獲取SD卡路徑
File sdDir = Environment.getExternalStorageDirectory();
// 獲取SD卡目錄 /mnt/sdcard辆亏。
String sdPath = sdDir.getAbsolutePath();
// 創(chuàng)建文件
File file = new File(sdPath, "testsd.json");
InputStream is = null;
try {
is = new FileInputStream(file);
int len;
byte[] buffer = new byte[1024];
while ((len = is.read(buffer)) != -1) {
sb.append(new String(buffer, 0, len));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (is != null)
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return sb.toString();
}
到這里就可以開始寫具體邏輯實(shí)現(xiàn):
/**
* 文件存儲(chǔ)
* Created by 鄒峰立 on 2017/9/19 0019.
*/
public class FileActivity extends AppCompatActivity implements View.OnClickListener {
private EditText editText;
private TextView textView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_file);
initView();
}
// 初始化控件
private void initView() {
editText = findViewById(R.id.edittext);
textView = findViewById(R.id.text);
Button sdBtn = findViewById(R.id.btn_sd);
sdBtn.setOnClickListener(this);
Button readSdBtn = findViewById(R.id.btn_read_sd);
readSdBtn.setOnClickListener(this);
}
// 按鈕點(diǎn)擊事件監(jiān)聽
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_sd:// 保存到SD卡
String text = editText.getText().toString().trim();
if (!TextUtils.isEmpty(text)) {
boolean bool = writeSdData(text);
if (bool) {
Toast.makeText(this, "寫入成功", Toast.LENGTH_SHORT).show();
editText.setText("");
} else
Toast.makeText(this, "寫入失敗", Toast.LENGTH_SHORT).show();
}
break;
case R.id.btn_read_sd:// 讀取SD卡
String str = readSdData();
textView.setText(str);
break;
}
}
}
因?yàn)樯厦嬉呀?jīng)提到writeSdData和readSdData扮叨,所以這里省略了writeSdData和readSdData彻磁,只是用來簡(jiǎn)單說明具體實(shí)現(xiàn)邏輯。
可能遇到問題:
問題1:沒有安裝SD卡衷蜓。當(dāng)手機(jī)沒有安裝SD卡情況下,是沒法進(jìn)行數(shù)據(jù)保存斋陪。
問題2、當(dāng)程序運(yùn)行的時(shí)候缔赠,會(huì)發(fā)現(xiàn)無論如何操作都無法保存數(shù)據(jù)友题,這是為什么呢?這是因?yàn)镾D卡文件的讀取和寫入需要權(quán)限度宦,所以需要在AndroidManifest.xml文件中添加如下權(quán)限:
<!-- 往sdcard中讀取數(shù)據(jù)的權(quán)限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- 在sdcard中寫入文件的權(quán)限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- 在sdcard中創(chuàng)建/刪除文件的權(quán)限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
問題3戈抄、當(dāng)再次運(yùn)行程序的時(shí)候,會(huì)發(fā)現(xiàn)在Android6.0+版本中,依然無法保存數(shù)據(jù)行贪,這又是為什么呢?這是由于Android從6.0開始對(duì)使用權(quán)限做了大的改動(dòng)崭捍,其中將【對(duì)外部存儲(chǔ)設(shè)備的讀寫權(quán)限】放到了運(yùn)行時(shí)申請(qǐng)的列表里啰脚,App的開發(fā)者必須要主動(dòng)的申請(qǐng)?jiān)L問設(shè)備的權(quán)限,這樣才能使用外部存儲(chǔ)設(shè)備粒梦。當(dāng)然系統(tǒng)軟件除外荸实。那么又該如何動(dòng)態(tài)申請(qǐng)權(quán)限呢?
這里要用到兩個(gè)方法:
/**
* @param context 上下文對(duì)象
* @param permission 待檢測(cè)權(quán)限
*/
int checkSelfPermission(@NonNull Context context, @NonNull String permission)
該方法是用來檢測(cè)權(quán)限permission泄朴,如果檢測(cè)結(jié)果等于PackageManager.PERMISSION_GRANTED說明當(dāng)前應(yīng)用程序可使用該權(quán)限露氮。
/**
* @param activity 活動(dòng)頁面
* @param permissions 權(quán)限組-請(qǐng)求權(quán)限集合
* @param requestCode 請(qǐng)求碼-一般用在請(qǐng)求結(jié)果回調(diào)方法用來進(jìn)行請(qǐng)求權(quán)限組判斷
*/
ActivityCompat.requestPermissions(final @NonNull Activity activity,
final @NonNull String[] permissions, final @IntRange(from = 0) int requestCode)
所以對(duì)于上面SD卡操作邏輯可以修改為:
/**
* 文件存儲(chǔ)
* Created by 鄒峰立 on 2017/9/19 0019.
*/
public class FileActivity extends AppCompatActivity implements View.OnClickListener {
private final int PERMISSION_OPER_EXTERNAL_STORAGE = 55;
private String[] permissions = {
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
private int sdOperType = 0;
private EditText editText;
private TextView textView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_file);
initView();
}
// 初始化控件
private void initView() {
editText = findViewById(R.id.edittext);
textView = findViewById(R.id.text);
Button sdBtn = findViewById(R.id.btn_sd);
sdBtn.setOnClickListener(this);
Button readSdBtn = findViewById(R.id.btn_read_sd);
readSdBtn.setOnClickListener(this);
}
// 按鈕點(diǎn)擊事件監(jiān)聽
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_sd:// 保存到SD卡
applyPermission();
sdOperType = 1;
break;
case R.id.btn_read_sd:// 讀取SD卡
applyPermission();
sdOperType = 2;
break;
}
}
// 判斷是否可以操作SD
private boolean isOperSd() {
return hasPermission(permissions);
}
// Android6.0 動(dòng)態(tài)申請(qǐng)文件讀寫權(quán)限
private void applyPermission() {
if (!hasPermission(permissions)) {
requestPermission(PERMISSION_OPER_EXTERNAL_STORAGE, permissions);
}
}
// 權(quán)限請(qǐng)求回調(diào)
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case PERMISSION_OPER_EXTERNAL_STORAGE:// SD卡讀寫權(quán)限成功
switch (sdOperType) {
case 1:// 保存數(shù)據(jù)到SD卡
if (isOperSd()) {
String text1 = editText.getText().toString().trim();
if (!TextUtils.isEmpty(text1)) {
boolean bool = writeSdData(text1);
if (bool) {
Toast.makeText(this, "寫入成功", Toast.LENGTH_SHORT).show();
editText.setText("");
} else
Toast.makeText(this, "寫入失敗", Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(this, "你沒有操作SD卡權(quán)限", Toast.LENGTH_SHORT).show();
}
break;
case 2:// 讀取SD卡數(shù)據(jù)
if (isOperSd()) {
String str1 = readSdData();
textView.setText(str1);
} else {
Toast.makeText(this, "你沒有操作SD卡權(quán)限", Toast.LENGTH_SHORT).show();
}
break;
}
break;
}
}
/**
* 權(quán)限檢查方法
*/
public boolean hasPermission(String... permissions) {
for (String permission : permissions) {
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
/**
* 權(quán)限請(qǐng)求方法
*/
public void requestPermission(int code, String... permissions) {
ActivityCompat.requestPermissions(this, permissions, code);
}
}
問題4:通過上面的方法完善之后局扶,大部分的機(jī)型都可以使用,而一些特殊機(jī)型如HUAWEI Mate8(Android 6.0)依舊沒法進(jìn)行數(shù)據(jù)保存延欠,這又是為什么呢沈跨?首先可以說明的是這是一些非常特殊的情況,對(duì)于這些特殊情況狞玛,可能存在的問題已經(jīng)不是應(yīng)用層可以解決的涧窒,如果非要讓HUAWEI Mate8支持,可以通過以下方法讓HUAWEI Mate8恢復(fù)SD卡和U盤的讀取權(quán)限硬鞍。
- 首先要保證設(shè)備插入一張SD卡戴已。
- 進(jìn)入【設(shè)置->高級(jí)設(shè)置->內(nèi)存和存儲(chǔ)】然后改變【默認(rèn)存儲(chǔ)位置】為“SD卡”,之后系統(tǒng)會(huì)提示要重啟手機(jī)伐坏。
- 重啟完成后握联,按照2的方法再次將【默認(rèn)存儲(chǔ)位置】改回“內(nèi)部存儲(chǔ)”,再次重啟手機(jī)金闽。