android檔案儲存體位置切換,android檔案儲存體
最近有個需求,助手的google衛星地圖和OpenCycleMap下載的離線地圖資料,要能夠在內建儲存和外置儲存空間之間切換,因為離線瓦片資料非常大,很多戶外使用者希望將這些檔案儲存體在外置TF卡上,不佔用內建儲存空間,所以把最近研究的整理了下,分享給大家。
需要考慮和遇到的問題(主要是不同手機、不同系統的相容性):
1.這樣擷取手機所有掛載的儲存空間?
Android是沒有提供顯式的介面的,首先肯定是要閱讀系統設定應用“儲存”部分的源碼,看儲存那裡是通過什麼方式擷取的。最後找到StorageManager和StorageVolume這2個重要的類,然後通過反射擷取StorageVolume[]列表。
2.用什麼標示一個儲存空間的唯一性?
儲存路徑?不行(有些手機不插TF卡,內建儲存路徑是/storage/sdcard0,插上TF卡後,內建儲存路徑變成/storage/sdcard1,TF卡變成/storage/sdcard0)。
儲存卡名稱?不行(可能會切換系統語言,導致名稱匹配失敗,名稱的resId也不行,較低的系統版本StorageVolume沒有mDescriptionId這一屬性)。
經過測試,發現使用mStorageId可以標示儲存空間的唯一性,儲存空間數量改變,每個儲存空間的id不會改變。
3.如何獲得儲存空間的名稱?
經測試,不同的手機主要有3種擷取儲存空間名換的方法:getDescription()、getDescription(Context context)、先獲得getDescriptionId()再通過resId擷取名稱。
4.任務檔案下載一半時,切換檔案儲存儲存空間,怎麼處理?
有2種方案:
4.1 切換時,如果新的儲存空間足夠所有檔案轉移,先停止所有下載任務,將所有下載完和下載中的檔案拷貝到新的儲存空間,然後再更新下載資料庫下載任務的儲存路徑,再恢複下載任務;
4.2 切換時,先拷貝所有下載完成的檔案到新的儲存空間,下載任務繼續下載,下載完成再移動到新的儲存空間。
5.在4.4系統上,第三方應用無法讀取外置儲存卡的問題。(參考“External Storage”)
google為了在程式卸載時,能夠完全徹底的將程式所有資料清理乾淨,應用將不能向2級儲存地區寫入檔案。
“The WRITE_EXTERNAL_STORAGE permission must only grant write access to the primary external storage on a device. Apps must not be allowed to write to secondary external storage devices, except in their package-specific directories as allowed by synthesized permissions. Restricting writes in this way ensures the system can clean up files when applications are uninstalled.”
要能夠在4.4系統上TF卡寫入檔案,必須先root,具體方法可以google。
所以4.4系統上,切換會導致檔案轉移和下載失敗,使用者如果要切換到TF卡,至少需要提醒使用者,並最好給出4.4上root解決方案。
以下是擷取儲存空間的部分代碼:
1 public static class MyStorageVolume{ 2 public int mStorageId; 3 public String mPath; 4 public String mDescription; 5 public boolean mPrimary; 6 public boolean mRemovable; 7 public boolean mEmulated; 8 public int mMtpReserveSpace; 9 public boolean mAllowMassStorage; 10 public long mMaxFileSize; //最大檔案大小。(0表示無限制) 11 public String mState; //返回null 12 13 public MyStorageVolume(Context context, Object reflectItem){ 14 try { 15 Method fmStorageId = reflectItem.getClass().getDeclaredMethod("getStorageId"); 16 fmStorageId.setAccessible(true); 17 mStorageId = (Integer) fmStorageId.invoke(reflectItem); 18 } catch (Exception e) { 19 } 20 21 try { 22 Method fmPath = reflectItem.getClass().getDeclaredMethod("getPath"); 23 fmPath.setAccessible(true); 24 mPath = (String) fmPath.invoke(reflectItem); 25 } catch (Exception e) { 26 } 27 28 try { 29 Method fmDescriptionId = reflectItem.getClass().getDeclaredMethod("getDescription"); 30 fmDescriptionId.setAccessible(true); 31 mDescription = (String) fmDescriptionId.invoke(reflectItem); 32 } catch (Exception e) { 33 } 34 if(mDescription == null || TextUtils.isEmpty(mDescription)){ 35 try { 36 Method fmDescriptionId = reflectItem.getClass().getDeclaredMethod("getDescription"); 37 fmDescriptionId.setAccessible(true); 38 mDescription = (String) fmDescriptionId.invoke(reflectItem, context); 39 } catch (Exception e) { 40 } 41 } 42 if(mDescription == null || TextUtils.isEmpty(mDescription)){ 43 try { 44 Method fmDescriptionId = reflectItem.getClass().getDeclaredMethod("getDescriptionId"); 45 fmDescriptionId.setAccessible(true); 46 int mDescriptionId = (Integer) fmDescriptionId.invoke(reflectItem); 47 if(mDescriptionId != 0){ 48 mDescription = context.getResources().getString(mDescriptionId); 49 } 50 } catch (Exception e) { 51 } 52 } 53 54 try { 55 Method fmPrimary = reflectItem.getClass().getDeclaredMethod("isPrimary"); 56 fmPrimary.setAccessible(true); 57 mPrimary = (Boolean) fmPrimary.invoke(reflectItem); 58 } catch (Exception e) { 59 } 60 61 try { 62 Method fisRemovable = reflectItem.getClass().getDeclaredMethod("isRemovable"); 63 fisRemovable.setAccessible(true); 64 mRemovable = (Boolean) fisRemovable.invoke(reflectItem); 65 } catch (Exception e) { 66 } 67 68 try { 69 Method fisEmulated = reflectItem.getClass().getDeclaredMethod("isEmulated"); 70 fisEmulated.setAccessible(true); 71 mEmulated = (Boolean) fisEmulated.invoke(reflectItem); 72 } catch (Exception e) { 73 } 74 75 try { 76 Method fmMtpReserveSpace = reflectItem.getClass().getDeclaredMethod("getMtpReserveSpace"); 77 fmMtpReserveSpace.setAccessible(true); 78 mMtpReserveSpace = (Integer) fmMtpReserveSpace.invoke(reflectItem); 79 } catch (Exception e) { 80 } 81 82 try { 83 Method fAllowMassStorage = reflectItem.getClass().getDeclaredMethod("allowMassStorage"); 84 fAllowMassStorage.setAccessible(true); 85 mAllowMassStorage = (Boolean) fAllowMassStorage.invoke(reflectItem); 86 } catch (Exception e) { 87 } 88 89 try { 90 Method fMaxFileSize = reflectItem.getClass().getDeclaredMethod("getMaxFileSize"); 91 fMaxFileSize.setAccessible(true); 92 mMaxFileSize = (Long) fMaxFileSize.invoke(reflectItem); 93 } catch (Exception e) { 94 } 95 96 try { 97 Method fState = reflectItem.getClass().getDeclaredMethod("getState"); 98 fState.setAccessible(true); 99 mState = (String) fState.invoke(reflectItem);100 } catch (Exception e) {101 }102 }103 104 /**105 * 擷取Volume掛載狀態, 例如Environment.MEDIA_MOUNTED106 */107 public String getVolumeState(Context context){108 return StorageVolumeUtil.getVolumeState(context, mPath);109 }110 111 public boolean isMounted(Context context){112 return getVolumeState(context).equals(Environment.MEDIA_MOUNTED);113 }114 115 public String getDescription(){116 return mDescription;117 }118 119 /**120 * 擷取存放裝置的唯一標識121 */122 public String getUniqueFlag(){123 return "" + mStorageId;124 }125 126 /*public boolean isUsbStorage(){127 return mDescriptionId == android.R.string.storage_usb;128 }*/129 130 /**131 * 擷取目錄可用空間大小132 */133 public long getAvailableSize(){134 return StorageVolumeUtil.getAvailableSize(mPath);135 }136 137 /**138 * 擷取目錄總儲存空間139 */140 public long getTotalSize(){141 return StorageVolumeUtil.getTotalSize(mPath);142 }143 144 @Override145 public String toString() {146 return "MyStorageVolume{" +147 "\nmStorageId=" + mStorageId +148 "\n, mPath='" + mPath + '\'' +149 "\n, mDescription=" + mDescription +150 "\n, mPrimary=" + mPrimary +151 "\n, mRemovable=" + mRemovable +152 "\n, mEmulated=" + mEmulated +153 "\n, mMtpReserveSpace=" + mMtpReserveSpace +154 "\n, mAllowMassStorage=" + mAllowMassStorage +155 "\n, mMaxFileSize=" + mMaxFileSize +156 "\n, mState='" + mState + '\'' +157 '}' + "\n";158 }159 }儲存資訊MyStorageVolume
1 public static List<MyStorageVolume> getVolumeList(Context context){ 2 List<MyStorageVolume> svList = new ArrayList<MyStorageVolume>(3); 3 StorageManager mStorageManager = (StorageManager)context 4 .getSystemService(Activity.STORAGE_SERVICE); 5 try { 6 Method mMethodGetPaths = mStorageManager.getClass().getMethod("getVolumeList"); 7 Object[] list = (Object[]) mMethodGetPaths.invoke(mStorageManager); 8 for(Object item : list){ 9 svList.add(new MyStorageVolume(context, item));10 }11 } catch (Exception e) {12 e.printStackTrace();13 }14 return svList;15 }擷取所有儲存空間
github上的測試例子:
https://github.com/John-Chen/BlogSamples/tree/master/StorageTest
如果還有什麼地方沒有考慮到的,歡迎討論。