亞寵展、全球寵物產業風向標——亞洲寵物展覽會深度解析
1026
2022-05-29
一、數據存儲概念
Android系統提供了提供了多種保存應用數據的選項:
文件存儲:
應用程序專屬文件存儲:
內部存儲(保存其他應用不應訪問的敏感信息)
外部存儲
共享文件存儲:存儲你的應用打算與其他應用共享的文件,包括媒體、文檔和其他文件。
一、數據存儲概念
Android系統提供了提供了多種保存應用數據的選項:
文件存儲:
應用程序專屬文件存儲:
內部存儲(保存其他應用不應訪問的敏感信息)
外部存儲
應用程序專屬文件存儲:
內部存儲(保存其他應用不應訪問的敏感信息)
外部存儲
內部存儲(保存其他應用不應訪問的敏感信息)
外部存儲
共享文件存儲:存儲你的應用打算與其他應用共享的文件,包括媒體、文檔和其他文件。
Preferences:默認情況下,Preferences 使用 SharedPreferences 來保存值。
數據庫:使用 Room 持久性庫將結構化數據存儲在私有數據庫中。
瞬時數據:是指那些存儲在內存當中,有可能會因為程序關閉或其他原因導致內存被回收而丟失的數據。
數據持久化:是指將那些內存中的瞬時數據保存到存儲設備中,保證即使在手機或電腦關機的情況下,這些數據仍然不會丟失。持久化技術則提供了一種機制可以讓數據在順勢狀態和持久狀態之間進行轉換。
二、應用程序專屬文件存儲
文件存儲是Android中最基本的一種數據存儲方式,它不對存儲的內容進行任何的格式化處理,所有數據都是原封不動地保存到文件當中的,因而它比較適合用于存儲一些簡單的文本數據或二進制數據。
訪問方法:
內部存儲(Internal Storage),
getFilesDir()
getCacheDir()
內部存儲(Internal Storage),
getFilesDir()
getCacheDir()
外部存儲(External Storage)
getExternalFilesDir()
getExternalCacheDir()
外部存儲(External Storage)
getExternalFilesDir()
getExternalCacheDir()
注意:卸載應用時,所以目錄下的文件將被移除。并且其他應用無法訪問這些專屬文件。
所需權限:
內部存儲永遠不需要
內部存儲永遠不需要
當你的應用在運行 Android 4.4(API 級別 >=19)時,外部存儲不需要(目前應用兼容基本5.0起步了)。
當你的應用在運行 Android 4.4(API 級別 >=19)時,外部存儲不需要(目前應用兼容基本5.0起步了)。
2.2 訪問持久文件
你可以使用Context對象的 filesDir 屬性訪問該目錄。
//From internal storage, getFilesDir() or getCacheDir() File filesDir = getFilesDir();//持久文件目錄 //FilesDir:/data/user/0/com.scc.datastorage/files Log.e("File","FilesDir:"+filesDir.getAbsolutePath()); File cacheDir = getCacheDir();//緩存文件目錄 //CacheDir:/data/user/0/com.scc.datastorage/cache Log.e("File","CacheDir:"+cacheDir.getAbsolutePath());
com.scc.datastorage:代表你的包名。
這里僅使用到內部存儲獲取文件目錄,未使用外部存儲 getExternalFilesDir() or getExternalCacheDir()。原因:
沒有外置存儲設備
現在使用外置存儲的少(畢竟現在的移動設備內存畢竟大),而且不安全,很容易造成數據丟失。
當然如果你想自己使用也是可以的,這里就不過多描述了。
注意:為幫助保持應用程序的性能,請勿多次打開和關閉同一個文件。
2.3 將數據存儲到文件
Context類中提供了一個openFileOutput()方法,可以用于將數據存儲到指定的文本中。
這個方法接受兩個參數:
第一個參數是文件名,在文本創建的時候使用的就是這個名稱,注意這里指定的文件名不可以包含路徑(因為默認存儲到/data/data/
第二個參數是文件的操作模式,主要有兩種:
MODE_PRIVATE:默認的操作模式,表示當指定同樣文件名的時候,當該文件名有內容時,再次調用會覆蓋原內容。
MODE_PRIVATE:默認的操作模式,表示當指定同樣文件名的時候,當該文件名有內容時,再次調用會覆蓋原內容。
MODE_APPEND:表示該文件如果已存在就往文件里面追加內容。
MODE_APPEND:表示該文件如果已存在就往文件里面追加內容。
調用 openFileOutput() 來獲取 FileOutputStream,得到這個對象后就可以使用Java流的方式將數據寫入到文件中了。
public void write(){ //文件名 String filename = "sccFile"; //寫入內容(shuaici) String fileContents = fileStorageBinding.etInput.getText().toString(); //內容不能為空 if (fileContents.isEmpty()) { Log.e("File","FileContents.isEmpty()"); return; } BufferedWriter writer = null; try { //獲取FileOutputStream對象 FileOutputStream fos = openFileOutput(filename, Context.MODE_PRIVATE); //通過OutputStreamWriter構建出一個BufferedWriter對象 writer = new BufferedWriter(new OutputStreamWriter(fos)); //通過BufferedWriter對象將文本內容寫入到文件中 writer.write(fileContents); }catch (IOException e){ Log.e("File",e.getMessage()); }finally { try { //不管是否拋異常,都手動關閉輸入流 if(writer != null) writer.close(); } catch (IOException e) { e.printStackTrace(); } } }
如果你想在同一個文件寫入多個內容那么需要將第二個參數改為MODE_APPEND
out = openFileOutput("data", Context.MODE_PRIVATE);
存儲了3次內容(ANDROID、shuaici、123456)如下:
2.4 從文件中讀取數據
//讀取文件 public void read(){ Log.e("File","read.start"); String filename = "sccFile"; BufferedReader reader = null; try { //獲取FileInputStream對象 FileInputStream fis = openFileInput(filename); //通過InputStreamReader構建出一個BufferedReader對象 reader = new BufferedReader(new InputStreamReader(fis)); StringBuilder sb = new StringBuilder(); String line = ""; //一行一行的讀取,當數據為空時結束循環 while ((line=reader.readLine())!=null){ sb.append(line);//將數據添加到StringBuilder } Log.e("File",sb.toString()); fileStorageBinding.etInput.setText(sb.toString()); }catch (IOException e){ Log.e("File",e.getMessage()); }finally { Log.e("File","read.finally"); try { if(reader != null) reader.close(); } catch (IOException e) { e.printStackTrace(); } } }
注意:如果你需要在安裝時以流的形式訪問文件,請將文件保存在項目的 /res/raw 目錄中。 你可以使用 openRawResource() 打開這些文件,傳入以 R.raw 為前綴的文件名作為資源 ID。此方法返回一個可用于讀取文件的 InputStream。你無法寫入原始文件。
2.5 查看文件列表
你可以通過調用 fileList() 獲取包含 filesDir 目錄中所有文件名稱的數組,如下:
//查看文件列表 public void filelist(){ String [] files = fileList(); for (String file : files) { Log.e("File","FileList:"+file); } }
2.6 刪除文件
刪除文件最簡單的方法是讓打開的文件引用本身調用delete()。
刪除文件最簡單的方法是讓打開的文件引用本身調用delete()。
如果文件保存在內部存儲中,你還可以通過調用deleteFile()刪除文件。
如果文件保存在內部存儲中,你還可以通過調用deleteFile()刪除文件。
//刪除文件 public void delete(){ String filename = fileStorageBinding.etInput.getText().toString(); Log.e("File","Delete:"+deleteFile(filename)); filelist(); }
注意:當用戶卸載你的應用程序時,Android系統會刪除保存在內部存儲和外部存儲中的所有文件。 當然你也可以定期刪除你所創建的不需要的緩存文件。
三、緩存文件(cache目錄下)
3.1 創建緩存文件
如果你只需要臨時存儲敏感數據,你應該使用應用程序在內部存儲中指定的緩存目錄來保存數據。與files目錄下存儲一樣,存儲在此目錄中的文件會在用戶卸載你的應用程序時被刪除。
注意:
此緩存目錄旨在存儲你應用的少量敏感數據。
當設備內部存儲空間不足時,Android 可能會刪除這些緩存文件以恢復空間。因此,在讀取緩存文件之前檢查它們是否存在。
//創建緩存文件 private void createCache() { try { //方法一 File timpfile = File.createTempFile("scc2022", ".txt", this.getCacheDir()); Log.e("File","是否存在:"+timpfile.exists()); Log.e("File","timpfile:"+timpfile.getAbsolutePath()); } catch (IOException e) { e.printStackTrace(); } //方法二:創建文件失敗 File file = new File(this.getCacheDir(), "file2022"); Log.e("File","是否存在:"+file.exists()); Log.e("File","file:"+file.getAbsolutePath()); }
方法一和方法二都會在應用程序緩存目錄中添加文件,但是這里我用方法二創建文件始終不成功,方法二后面找找原因。這里重點介紹方法一。
第一個參數prefix:文件名前綴
第二個參數suffix:文件名后綴(如果不設置,默認.tmp)
第三個參數directory:文件存放目錄
如上圖不管是否指定后綴都會在文件名末尾添加一個隨機數,以保持文件的唯一性。如上面文件名設置的為:scc2022.txt,但是創建出來的是:scc2022784042118208981946.txt。
由于末尾添加一個隨機數給刪除或讀取文件帶來麻煩,所以可能需要調用file.getName()進行文件名存儲。否則不好找。如此下來其實我更喜歡第二種方式,但是不知道什么情況創建不成功這就比較尷尬了。
3.2 刪除文件
盡管 Android 系統有時會自行刪除緩存文件,但你可以自己刪除數據。
//刪除緩存文件 private void deleteCache() { //scc20221181329566644563700891.tmp和scc2022118都存在 //但是cache目錄下沒有,咱們調用刪除試試 File cacheFile = new File(getCacheDir(), "scc20221181329566644563700891.tmp"); //判斷文件是否存在 if(cacheFile.exists()) { //使用File對象的delete()方法 Log.e("File","delete17:"+cacheFile.delete()); } File cacheFile2 = new File(getCacheDir(), "scc2022118"); if(cacheFile2.exists()) { //應用Context的deleteFile()方法 Log.e("File","deleteFile19:"+deleteFile("scc2022118")); } }
四、外部存儲
如果內部存儲太小無法提供更大的空間那么可以使用外部存儲
外部存儲(External Storage)
getExternalFilesDir()//持久文件
getExternalCacheDir()//緩存文件
getExternalFilesDir()//持久文件
getExternalCacheDir()//緩存文件
在 Android 4.4(API 級別 >=19),你的應用無需請求任何與存儲相關的權限即可訪問外部存儲中的應用特定目錄。當你的應用程序被卸載時,存儲在這些目錄中的文件將被刪除。
注意:
1、不能保證文件可以訪問,因為可能SD卡被移除。
2、在 Android 11(API 級別 >=30),應用無法在外部存儲上創建自己的應用特定目錄。感覺外部存儲還是少用。
驗證存儲是否可用
// 檢查外部存儲是否可用于讀寫。 private boolean isExternalStorageWritable() { return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); } // 檢查外部存儲至少可讀。 private boolean isExternalStorageReadable() { return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) || Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED_READ_ONLY); }
五、共享文件存儲
存儲你的應用打算與其他應用共享的文件,包括媒體、文檔和其他文件。在這里咱們將圖片保存至圖庫(共享文件)。
需要存儲權限
private void saveBitmap() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // 檢查該權限是否已經獲取 int i = ContextCompat.checkSelfPermission(FileStorageActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE); // 權限是否已經 授權 GRANTED---授權 DINIED---拒絕 if (i != PackageManager.PERMISSION_GRANTED) { // 如果沒有授予該權限,就去提示用戶請求 startRequestPermission(); } else { resourceBitmap(); } } else { resourceBitmap(); } }
保存數據
private void resourceBitmap() { Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ceshi); boolean isSave = PictureStorageUtils.isSaveImage(this, bitmap, "sccgx"); Log.e("File","isSave:"+isSave); } /** * 功能描述:將圖片文件保存至本地 */ public class PictureStorageUtils { public static boolean isSaveImage(Context context, Bitmap bm, String name) { boolean isSave; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { //大于等于android 10 isSave = saveImageQ(context, bm, name); } else { isSave = saveImage(context, bm, name); } return isSave; } private static boolean saveImage(Context context, Bitmap outB, String name) { String imgName = name.isEmpty()?String.valueOf(System.currentTimeMillis()):name; //File.separator就是文件路徑 String fileName = Environment.getExternalStorageDirectory() + File.separator + "DCIM" + File.separator + "demo" + File.separator; try { File file = new File(fileName); if (!file.exists()) { file.mkdirs(); } Log.e("File","saveAndGetImage:" + file); File filePath = new File(file + "/" + imgName + ".png"); Log.e("File","filePath:" + filePath); FileOutputStream out = new FileOutputStream(filePath); //保存到本地,格式為JPEG if (outB.compress(Bitmap.CompressFormat.PNG, 100, out)) { out.flush(); out.close(); } Log.e("File","saveAndGetImage:END"); //刷新圖庫 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {//高于22版本要手動授權 // 檢查該權限是否已經獲取 int i = ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE); // 權限是否已經 授權 GRANTED---授權 DINIED---拒絕 if (i != PackageManager.PERMISSION_GRANTED) { // 提示用戶應該去應用設置界面手動開啟權限 } else { context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(filePath))); } } else { context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(filePath))); } return true; } catch (FileNotFoundException e) { Log.e("File","FileNotFoundException e.toString: " + e.toString()); e.printStackTrace(); return false; } catch (IOException e) { Log.e("File","IOException e.toString: " + e.toString()); e.printStackTrace(); return false; } } //功能描述:Android10及以上保存圖片到相冊 @RequiresApi(api = Build.VERSION_CODES.Q) private static boolean saveImageQ(Context context, Bitmap image, String name) { long mImageTime = System.currentTimeMillis(); String mImageFileName = name.isEmpty()?String.valueOf(mImageTime):name; final ContentValues values = new ContentValues(); values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM + File.separator + "demo"); //圖庫(DCIM)中顯示的文件夾名。 values.put(MediaStore.MediaColumns.DISPLAY_NAME, mImageFileName); values.put(MediaStore.MediaColumns.MIME_TYPE, "image/png"); values.put(MediaStore.MediaColumns.DATE_ADDED, mImageTime / 1000); values.put(MediaStore.MediaColumns.DATE_MODIFIED, mImageTime / 1000); values.put(MediaStore.MediaColumns.DATE_EXPIRES, (mImageTime + DateUtils.DAY_IN_MILLIS) / 1000); values.put(MediaStore.MediaColumns.IS_PENDING, 1); Log.e("File",values.get(MediaStore.MediaColumns.RELATIVE_PATH).toString()); ContentResolver resolver = context.getContentResolver(); final Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); try { //寫下我們文件的數據 try (OutputStream out = resolver.openOutputStream(uri)) { if (!image.compress(Bitmap.CompressFormat.PNG, 100, out)) { throw new IOException("Failed to compress"); } } //一切都很順利 values.clear(); values.put(MediaStore.MediaColumns.IS_PENDING, 0); values.putNull(MediaStore.MediaColumns.DATE_EXPIRES); resolver.update(uri, values, null, null); return true; } catch (IOException e) { Log.e("File",e.getMessage()); return false; } } }
測試機:Galaxy A8s
圖片保存路徑:Galaxy A8s\Phone\DCIM\demo
測試機:Pixel XL API 31(AS模擬器)
圖片保存路徑:/storage/emulated/0/DCIM/demo/sccgx.png
Android
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。