編輯:關於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解決方法。
以下是獲取存儲器的部分代碼:
public static class MyStorageVolume{ public int mStorageId; public String mPath; public String mDescription; public boolean mPrimary; public boolean mRemovable; public boolean mEmulated; public int mMtpReserveSpace; public boolean mAllowMassStorage; public long mMaxFileSize; //最大文件大小。(0表示無限制) public String mState; //返回null public MyStorageVolume(Context context, Object reflectItem){ try { Method fmStorageId = reflectItem.getClass().getDeclaredMethod("getStorageId"); fmStorageId.setAccessible(true); mStorageId = (Integer) fmStorageId.invoke(reflectItem); } catch (Exception e) { } try { Method fmPath = reflectItem.getClass().getDeclaredMethod("getPath"); fmPath.setAccessible(true); mPath = (String) fmPath.invoke(reflectItem); } catch (Exception e) { } try { Method fmDescriptionId = reflectItem.getClass().getDeclaredMethod("getDescription"); fmDescriptionId.setAccessible(true); mDescription = (String) fmDescriptionId.invoke(reflectItem); } catch (Exception e) { } if(mDescription == null || TextUtils.isEmpty(mDescription)){ try { Method fmDescriptionId = reflectItem.getClass().getDeclaredMethod("getDescription"); fmDescriptionId.setAccessible(true); mDescription = (String) fmDescriptionId.invoke(reflectItem, context); } catch (Exception e) { } } if(mDescription == null || TextUtils.isEmpty(mDescription)){ try { Method fmDescriptionId = reflectItem.getClass().getDeclaredMethod("getDescriptionId"); fmDescriptionId.setAccessible(true); int mDescriptionId = (Integer) fmDescriptionId.invoke(reflectItem); if(mDescriptionId != 0){ mDescription = context.getResources().getString(mDescriptionId); } } catch (Exception e) { } } try { Method fmPrimary = reflectItem.getClass().getDeclaredMethod("isPrimary"); fmPrimary.setAccessible(true); mPrimary = (Boolean) fmPrimary.invoke(reflectItem); } catch (Exception e) { } try { Method fisRemovable = reflectItem.getClass().getDeclaredMethod("isRemovable"); fisRemovable.setAccessible(true); mRemovable = (Boolean) fisRemovable.invoke(reflectItem); } catch (Exception e) { } try { Method fisEmulated = reflectItem.getClass().getDeclaredMethod("isEmulated"); fisEmulated.setAccessible(true); mEmulated = (Boolean) fisEmulated.invoke(reflectItem); } catch (Exception e) { } try { Method fmMtpReserveSpace = reflectItem.getClass().getDeclaredMethod("getMtpReserveSpace"); fmMtpReserveSpace.setAccessible(true); mMtpReserveSpace = (Integer) fmMtpReserveSpace.invoke(reflectItem); } catch (Exception e) { } try { Method fAllowMassStorage = reflectItem.getClass().getDeclaredMethod("allowMassStorage"); fAllowMassStorage.setAccessible(true); mAllowMassStorage = (Boolean) fAllowMassStorage.invoke(reflectItem); } catch (Exception e) { } try { Method fMaxFileSize = reflectItem.getClass().getDeclaredMethod("getMaxFileSize"); fMaxFileSize.setAccessible(true); mMaxFileSize = (Long) fMaxFileSize.invoke(reflectItem); } catch (Exception e) { } try { Method fState = reflectItem.getClass().getDeclaredMethod("getState"); fState.setAccessible(true); mState = (String) fState.invoke(reflectItem); } catch (Exception e) { } } /** * 獲取Volume掛載狀態, 例如Environment.MEDIA_MOUNTED */ public String getVolumeState(Context context){ return StorageVolumeUtil.getVolumeState(context, mPath); } public boolean isMounted(Context context){ return getVolumeState(context).equals(Environment.MEDIA_MOUNTED); } public String getDescription(){ return mDescription; } /** * 獲取存儲設備的唯一標識 */ public String getUniqueFlag(){ return "" + mStorageId; } /*public boolean isUsbStorage(){ return mDescriptionId == android.R.string.storage_usb; }*/ /** * 獲取目錄可用空間大小 */ public long getAvailableSize(){ return StorageVolumeUtil.getAvailableSize(mPath); } /** * 獲取目錄總存儲空間 */ public long getTotalSize(){ return StorageVolumeUtil.getTotalSize(mPath); } @Override public String toString() { return "MyStorageVolume{" + "\nmStorageId=" + mStorageId + "\n, mPath='" + mPath + '\'' + "\n, mDescription=" + mDescription + "\n, mPrimary=" + mPrimary + "\n, mRemovable=" + mRemovable + "\n, mEmulated=" + mEmulated + "\n, mMtpReserveSpace=" + mMtpReserveSpace + "\n, mAllowMassStorage=" + mAllowMassStorage + "\n, mMaxFileSize=" + mMaxFileSize + "\n, mState='" + mState + '\'' + '}' + "\n"; } } 存儲信息MyStorageVolume
public static ListgetVolumeList(Context context){ List svList = new ArrayList (3); StorageManager mStorageManager = (StorageManager)context .getSystemService(Activity.STORAGE_SERVICE); try { Method mMethodGetPaths = mStorageManager.getClass().getMethod("getVolumeList"); Object[] list = (Object[]) mMethodGetPaths.invoke(mStorageManager); for(Object item : list){ svList.add(new MyStorageVolume(context, item)); } } catch (Exception e) { e.printStackTrace(); } return svList; } 獲取所有存儲器
github上的測試例子:
https://github.com/John-Chen/BlogSamples/tree/master/StorageTest
如果還有什麼地方沒有考慮到的,歡迎討論。
其他精彩文章文章
android學習筆記(41)android選項菜單和子菜單(SubMenu )
android學習筆記(40)Notification的功能與用法
android學習筆記(42)android使用監聽器來監聽菜單事件
android學習筆記(43)android創建單選菜單和復選菜單
jQuery教程(12)-ajax操作之基於請求加載數據
jQuery教程(13)-ajax操作之追加 HTML
更多關於android開發文章
前言:本來我是做電視應用的,但是因為公司要出手機,人員緊張,所以就抽調我去支援一下,誰叫俺是雷鋒呢!我做的一個功能就是處理手機中的應用ICON,處理無非就是美化一下,重新
一、引子 學過Android的同學都知道,對話框的重要程度非常高,任何一款 app幾乎都離不開對話框,值得慶幸的是,對話框的運用在Android中還是相對比較容易的。雖然
正文 1 Started Service介紹 Started Service,即被啟動的服務。它是2種常見服務之一,另一種是Bo
一,效果圖。二,工程圖。 三,代碼。RootViewController.h#import <UIKit/UIKit.h>@interface Ro