陸志平, 胡晨駿
(南京中醫(yī)藥大學信息技術學院, 南京 210046)
Android開發(fā)中利用反射獲取存儲路徑的研究①
陸志平, 胡晨駿
(南京中醫(yī)藥大學信息技術學院, 南京 210046)
Android應用的開發(fā)中對存儲的訪問非常頻繁, 但是Android各個版本對存儲的支持比較混亂, 部分版本甚至沒有公開的API支持對擴展存儲訪問. 對Android的內置、外置存儲設備進行研究后, 提出將存儲分為內部存儲、外部存儲及擴展存儲三個類型. 分析了各個存儲的類型的特點及訪問方式, 重點討論了擴展存儲的訪問方式, 提出利用JAVA的反射機制來獲取Android平臺的擴展存儲目錄, 解決了Android不同版本對存儲進行訪問的兼容性問題.通過分析工具分析了反射機制在此應用中的效率問題, 并在不同Android版本的設備上進行了測試.
Android; 反射; 內部存儲; 外部存儲; 擴展存儲; 路徑
Android是一種基于Linux的開放源代碼的操作系統, 被大量移動設備所采用. 自2008年發(fā)布第一個版本以來, 歷經多年, 目前已經發(fā)展到6.0版本. 在早期的2.x版本中, Android設備都是單存儲設置, 訪問存儲設備的路徑比較統一, 也有API支持存儲設備的訪問. 而到了4.0版本之后, Android將存儲分為了primary storage和secondary external storage, 一些設備生產商又會擴展多個存儲設備. 例如現在很多手機不僅支持外置SD卡,還支持外置U盤, 導致存儲管理相當混亂. 而Android也提倡少用外置的擴展存儲, 所以將訪問外置存儲的API隱藏了起來, 不作為公開的API. 目前在項目的開發(fā)中,訪問外置存儲會經常用到. 項目開發(fā)必須充分考慮程序的健壯性, 且要能兼容多個版本的Android系統, 所以有必要對存儲路徑的獲取做一個深入的研究, 找出一個合適的方法.
獲取擴展存儲路徑的有多種方法, 例如如下幾種方法; 過濾Android系統中“mount”命令的結果, 但是“mount”命令的結果會把一些具有特殊作用目錄的掛載信息也包含進去, 這樣就很難區(qū)分存儲設備, 容易判斷錯誤. 也可以利用Android系統中“vold.fstab”文件內的信息來判斷存儲信息, 但是“vold.fstab”文件在不同的設備上, 存儲的目錄也不盡相同, 查找起來相當麻煩.在大部分設備上也可以通過過濾Android的環(huán)境變量“SECONDARY_STORAGE”來找到擴展存儲的目錄[1],但環(huán)境變量“SECONDARY_STORAGE”在某些設備上沒有, 所以上述方法雖然在大部分設備上能夠訪問到擴展存儲目錄, 但也存在一些缺陷.
基于Android并沒有將訪問擴展存儲的API廢棄,而是將其隱藏起來, 所以本文研究采用Java的反射機制來讀取Android中隱藏的API, 通過隱藏的API來訪問擴展存儲的目錄, 并采用市場上不同Android版本的常用設備進行測試驗證.
1982年Brian Cantwell Smith在他的博士論文中提出了反射的概念, 提出反射是計算機程序在運行時可以訪問、檢測和修改它本身狀態(tài)或行為的一種能力[2].這一概念的提出引發(fā)了反射在程序設計方面應用的研究, 很快被很多程序設計語言所采用, 例如Java、C#、Ruby、PHP、Perl等語言都支持反射機制.
在Java中, 程序在運行狀態(tài)下, 通過反射機制可以動態(tài)地獲取任意一個類的所有屬性和方法, 能調用任意一個對象的方法與屬性[3].
Java程序若要正常運行, 需要Java虛擬機加載相應的Java類[4]. 一般情況下, 程序運行時所使用的相關類在編譯期就已經加載了. 但是在程序運行期間, 若是需要一個新的類來完成某些操作時, 就需要重新編譯. 而采用反射后, Java就可以在編譯期間不知道類或接口的名稱、成員變量、方法的情況下, 在運行時檢查類、接口的成員變量和方法, 并允許實例化此類的對象并調用其方法[5].
Java中使用反射機制通常所需要的的類有: Class、Field、Constructor、Method等[6], 其中, Class類是Java反射機制的基礎.
在Java中, 某個將要操作的類被加載后, Java虛擬機(JVM)就會自動生成一個Class類型的對象, 此Class對象描述了加載到虛擬機中的類對應的成員變量、構造方法以及其他方法等相關信息[7]. Field、Constructor、Method類則分別對應為加載類的成員變量、構造函數以及方法的抽象.
由于Class類的構造方法的訪問屬性是private, 因此不能直接通過構造方法來新建一個Class對象, 若需要獲取操作類的Class對象, 一般通過如下3種常用方式[8].
1) 使用對象的getClass方法.
2) 使用類的.class語法.
3) 使用Class類的靜態(tài)方法forName().
通過上述方法獲得Class對象之后即可以訪問操作類自身的信息, 例如此操作類隸屬的Package、類別的訪問屬性、繼承類、實現接口、訪問此操作類的成員變量及方法等.
利用Java中提供的reflection API在程序運行的時候獲取類信息的方法如下.
1) 獲取類的構造方法
Constructor Constructor 上述兩個方法通過指定參數列表獲得特定的Constructor對象, 前者返回訪問屬性為公共的構造方法, 后者返回公共、保護、默認和私有這幾種訪問類型的構造方法. Constructor>[] getConstructors() Constructor>[] getDeclaredConstructors() 上述兩個方法返回一個包含Constructor對象的數組, 同理, 前者返回數組中的構造函數對象的訪問屬性只能為public. 2) 獲取類的其他方法 Method getMethod(String name, Class>...parameterTypes) Method getDeclaredMethod(String name,Class>... parameterTypes) Method[] getMethods() Method[] getDeclaredMethods() 與獲得類的構造方法類似, 上述四個方法可以獲得類的其他方法. 3) 獲取類的成員變量 Field getField(String name) Field getDeclaredField(String name) Field[] getFields() Field[] getDeclaredFields() 同理, 上述四個方法用于獲得類的成員變量, 前兩個返回單個成員變量, 后兩個返回成員變量對象構成的數組. 從上面三類方法可以看到, 返回的類的各種信息分別以Constructor、Method以及Field對象的形式存放.在程序中, 可以通過調用這些對象中相應的方法來動態(tài)的創(chuàng)建對象、調用方法以及獲取成員變量. 如今Android設備都會內置一塊大容量的存儲卡,例如eMMC卡(Embedded Multi Media Card). 一些手機的16 G/32 G版本, 指的就是此手機內置卡的容量. 除了此內置存儲卡, 很多Android設備還額外配置了擴展存儲設備, 例如Micro SD卡(Micro Secure Digital Memory Card)、USB設備等[9]. 如圖1所示. 圖1 Android設備存儲結構圖 2.1 內部存儲 Android設備的內部存儲并不是傳統意義上的內存, 內存指的是設備運行時所用數據的存儲區(qū), 與計算機中的內存一樣. 而Android的內部存儲是用來安裝各類應用程序, 或者用來保存應用程序的相關數據, 是一個數據的長久存儲區(qū). 內置存儲卡的一部分存儲空間被劃分出來存放系統文件, 此分區(qū)由于安全等方面的考慮, 一般是隱藏的,用戶通過普通的文件管理軟件是不能訪問的, 此分區(qū)存放著“/system”、“/cache”等系統目錄. 一部分存儲空間被劃分出來用來安裝各類應用程序與保存相關數據,此存儲區(qū)稱為內部存儲, 也是隱藏的, 目錄為“/data”[10]. 在內部存儲中, 應用程序創(chuàng)建后, 在/data/data目錄下會創(chuàng)建一個與應用程序包名相同的一個目錄, 此目錄用來存放同名應用程序的相關文件, 且默認只能被此應用程序訪問. 此機制保證了各個應用程序之間數據的獨立性與安全. 若是應用程序被卸載, 則內部存儲中與應用程序包名相同的目錄及目錄內的數據也會被刪除. 2.2 外部存儲 內置存儲卡中剩下存儲空間的用來保存各類用戶數據, 稱為為外部存儲. 外部存儲區(qū)是公共的, 可以通過普通的文件管理軟件進行訪問與管理[11]. 相對于內部存儲, 外部存儲可以用來保存用戶的各種數據, 且本存儲區(qū)的數據為可訪問數據. Android設備中的某些文件需要被各類應用程序調用, 所以此類文件通常為共享類型. 內部存儲一般不存儲此類文件, 此時可以將此類文件存放在外部存儲中, 例如照相機拍攝的相片、錄制的視頻和音頻等文件. 用戶也可以在外部存儲中建立新的文件、目錄, 也可以對外部存儲中的文件或文件夾做各種修改. 應用程序也可以在外部存儲存儲數據. 安裝了應用程序后, 在內部存儲“/data/data”目錄下相應的包名目錄下存放了此應用的相關數據; 而在外部存儲中, 也有一個“/Android/data/ Package_Name”的目錄, 此目錄可以存儲與應用相關的數據, 如在線視頻點播、在線音樂等軟件的緩沖數據或者下載的文件就可以存放在此目錄下. 例如在華為手機的大部分型號上, 應用安裝后,外部存儲的“storage/emulated/0/Android/data/Package_Name” 目錄中存放了應用的相關信息, 且應用程序刪除后, 此目錄的所有文件也會被自動刪除, 這一特性與內部存儲類似. 所以, 從文件管理角度來看,若是應用程序需要在外部存儲進行數據存儲, 建議將數據都放在此目錄中. 2.3 擴展存儲 內部存儲一般用于安裝應用軟件, 而外部存儲空間有限, 面對如今越來越大的應用程序與大量的數據,擴展Android設備的存儲空間是一個必須面對的問題.擴展的SD卡與USB設備是一個較為理想的方案. 早期的Android2.x版本中由于沒有內部存儲與外部存儲的區(qū)別, 用戶的數據都需要存儲在擴展卡中, 所以訪問方式比較統一, 但是文件的組織管理卻比較混亂. 而蘋果公司的移動電話與平板電腦是不支持外置擴展卡的. 出于安全性與文件管理等方面考慮, 谷歌在某些Android版本中也考慮采用此種模式, 在Android4.x版本中, 系統沒有支持直接訪問擴展SD卡, 例如此版本的谷歌Nexus手機就不支持SD卡擴展[12], 并且在Android系統中將訪問擴展存儲的方式設置成為隱藏函數. 目前有一些Android平臺的設備也在嘗試此類模式, 僅支持內置的存儲卡, 但絕大部分Android設備提供了外置存儲卡的擴展口. 所以, 如何讀取Android設備內置存儲卡與擴展存儲卡的目錄將是本文要研究的一個重要內容. 3.1 內部存儲的目錄及訪問 內部存儲中存放著應用程序的相關數據, 其目錄為“/data”, 可以通過Context類和Enviroment類提供的方法來進行訪問. 例如下面列舉了部分常用方法. Context類提供的方法: ◇ File getDir(String name, int mode):返回“/data/data/Package_Name/”目錄下的名稱為name的File對象, 如果該文件夾不存在, 則創(chuàng)建一個新的名為name的文件夾. ◇ File getCacheDir():返回路徑為“/data/data/Package_Name/cache”的File對象. ◇ File getFilesDir():返回路徑為“/data/data/Package_Name/files”的File對象. ◇ String[] fileList(): 返回“/data/data/ Package_Name/files”目錄下所有文件的文件名. Environment類提供的方法: ◇ File getDataDirectory(): 返回路徑為“/data”的File對象. ◇ File getDownloadCacheDirectory(): 返回Android下載/緩存內容目錄. 應用部署到三款不同的設備上, 具體型號如圖2所示, 可以看到通過上述方法都可以順利的訪問到內部存儲的主目錄及其子目錄. 由于應用程序訪問的數據都大都在目錄“/data/data/ Package_Name/files”下, 若每次訪問都要通過上述函數得到目錄文件, 然后再構建輸入輸出流, 顯然是比較麻煩的, 而Context類中提供了兩個方法可以便捷的得到此目錄的輸入輸出流. Context.openFileInput(String fileName) 和Context.openFileOutput(String fileName, int mode)這兩個方法分別用于得到“/data/data/Package_Name/files”目錄下文件名為fileName的輸入流(FileInputStream)與輸出流(FileOutputStream)[13]. 圖2 內部存儲目錄訪問測試 3.2 外部存儲的目錄及訪問 與內部存儲類似, Context類和Enviroment類也提供了相應的方法來訪問外部存儲. 例如下面列舉的部分常用方法. Context類提供的部分方法: ◇ File getExternalFilesDir(String type): 返回的File對象為外部存儲中某個應用程序存放私有文件的絕對路徑, 比如上面提到的華為手機, 通過本函數得到路徑為“storage/emulated/0/Android/data/Package_Name/files”的File對象. 由于目錄存放于外部存儲中, 所以即使是應用程序的私有文件, 它也能被其他應用程序訪問.且目錄中的文件在應用程序卸載后也會被自動刪除. ◇ File getExternalCacheDir(): 與getExternal-FilesDir類似, 返回的File對象為外部存儲中某個應用程序緩存文件的絕對路徑. Environment類提供的部分方法: ◇ File getExternalStorageDirectory(): 返回外部存儲根目錄的File對象. ◇ File getExternalStoragePublicDirectory(String type): 返回外部存儲中public文件夾下的File對象. 如圖3所示為華為、三星等手機通過上述方法得到的外部存儲的目錄. 由于Android操作系統是一個開源的操作系統, 允許其他廠商對其進行深度定制, 所以導致android設備的外部存儲的目錄有多種版本; 例如一些圖3中HTC的外部存儲目錄為“/mmt/sdcard”, 而Android4.0開始的外部存儲目錄為“/storage/emulated/0”, 另外還有其他一些品牌設備的外部存儲目錄也不一樣, 這里就不一一列舉. 所以外部存儲的目錄不可以使用固定的字符串, 而應該通過上述方法取得. 圖3 外部存儲目錄訪問測試 這里需要注意的是Android4.0版本之后, 才將存儲分為了primary storage和secondary external storage, 所以對于4.0以前的版本來說, 外置的SD卡即外部存儲.所以上述方法即可讀取到所有的存儲路徑, 不需要采用反射技術讀取隱藏的API. 3.3 擴展存儲的目錄及訪問 在Android平臺上, 擴展存儲的訪問相對于內部存儲與外部存儲來說復雜的多. 考慮到Android開發(fā)中需要兼容各個版本, 所以, 設計一個兼容性好的訪問方式是非常重要的. 通過JAVA反射機制來獲取Android擴展存儲目錄的流程如下. (1) 通過Android的API提供的getSystemService方法取得StorageManager類, 此類用于管理Android設備的擴展存儲器. (2) 通過反射機制獲得StorageManager中的getVolumeList方法, 因為此方法為隱藏方法, 所以需通過Java的反射機制才能獲取. (3) 通過反射機制獲得StorageVolume類, 因為StorageVolume類為隱藏類, 所以需通過反射才能得到StorageVolume的Class對象. (4) 通過反射機制執(zhí)行getVolumeList方法, 執(zhí)行此方法后得到一個StorageVolume數組對象, Storage Volume數組對象中封裝了設備的存儲信息, 例如存儲的掛載路徑, 掛載狀態(tài)及是否可以移除等. (5) 通過反射得到StorageVolume類的getPath等方法, 并對StorageVolume數組對象執(zhí)行. 例如執(zhí)行getPath方法獲得String類型的擴展存儲目錄、getPathFile方法返回File類型的擴展存儲目錄. 實施上述步驟的關鍵代碼如下所示: //通過Context類的getSystemService方法取得StorageManager對象 StorageManager mStorageManager = (Storage Manager) mContext.getSystemService (Context.STORAGE_SERVICE); Object[] storageVolumeobj[i]=null; //通過反射機制得到getVolumeList方法 Method getVolumeList = mStorageManager.getClass().getMethod(“getVolumeList”); //通過反射機制執(zhí)行getVolumeList方法得到StorageVolume數組storageVolumeobj storageVolumeobj=(Object[])getVolumeList.invoke(mStorageManager); //通過反射得到StorageVolume的Class對象 storageVolumeClass = Class.forName(“android.os.storage.StorageVolume”); //通過反射得到StorageVolume類的getPath方法, 執(zhí)行本方法即可得到存儲的目錄 Method getPath = storageVolumeClass.get Method(“getPath”); //通過反射執(zhí)行getPath方法獲取外置卡路徑 String path=(String) getPath.invoke(storage Volumeobj[i]); //通過反射得到StorageVolume類的isRemovable方法 Method isRemovable=storageVolumeClass.getMethod(“isRemovable”); //執(zhí)行isRemovable方法, 判斷到存儲設備是否可移除. Boolean is_removale = (Boolean) isRemovable.invoke(storageVolumeobj[i]); 上述代碼中的isRemovable方法用來判斷存儲設備是否可移除, 內置卡不可移除, 而擴展存儲著是可移除的, 從而判定擴展存儲的目錄. 從圖4可以看到, 兩款設備不僅插入了SD卡, 還通過連接線連接了USB設備. 通過上面介紹的反射機制,均可以順利的獲取設備的內置卡的根目錄, 即上文所說的外部存儲, SD卡的目錄以及USB設備的目錄. 圖4 反射獲取外部存儲和擴展存儲的目錄 鑒于目前Android設備型號復雜, 所采用的Android版本也各不相同, 并且由于Android平臺開放的特性,眾多廠商會對其進行各種深度定制, 從而導致支持的存儲設備也種類繁多. 所以如引言部分所述, 開發(fā)中對程序的健壯性與兼容性需要重點考慮. 本文提出的利用反射技術獲取存儲路徑的方法,并按此方法編寫相應的程序在多臺Android設備與Android版本上進行了詳細的測試. 測試的部分結果如表1所示. 表1 讀取存儲路徑測試結果表 表1僅列舉了目前市場上較為常見的幾種Android設備與Android版本的測試結果, 實際還在更多品牌的Android手機與平板電腦上進行了測試, 均能順利運行,并獲取到了設備的各類存儲目錄. 從測試結果可以看到, 由于Android4.0版本之前為單存儲設置, 所以采用系統提供的API即可解決存儲的訪問問題. 4.0版本之后, Android將訪問擴展存儲卡的API設置為隱藏屬性, 但均沒有取消掉, 可以通過反射機制獲取隱藏的類、方法等信息, 所以采用反射機制獲取存儲路徑的方法在各個Android版本及設備上均可獲得支持, 在健壯性及兼容性方面均表現良好. 由于反射需要將內存中的對象進行解析, 包括了一些動態(tài)類型, 所以JVM無法對這些代碼進行優(yōu)化. 因此, 反射操作的效率要比非反射操作低. 而本文所提出的方法為利用反射技術來獲取存儲路徑, 所以需要考慮到效率上對系統的影響, 下面通過TraceView[14]進行分析. 從圖5可以看到各個方法所花費的時間, 其中, Name列為執(zhí)行方法的名稱, InclRealTime列為方法本身執(zhí)行所花費的時間, InclRealTime%列為此方法運行時間所占的百分比. Call+RecurCalls/Total列為方法調用次數及遞歸調用占總調用次數的百分比. 從Name列可以看到, Envoronment.getxxx()是靜態(tài)獲取內部存儲目錄的方法, MainActivity.getStoragePath()為通過反射獲取擴展卡目錄的函數, 此方法耗時4.194毫秒, 在所有方法的耗時數中占了3.9%, 與部分靜態(tài)獲取路徑的方法比較起來, 執(zhí)行的效率并沒有很大的差距, Class.forName()、Class.getMethod()、Mehtod.invoke()為反射機制的應用, 從圖中可以看到, 執(zhí)行所占的時間比分別為0.2%、0.8%, 0.2%, 由于調用的次數不是很多, 所以對整個系統來說, 負載并不是特別大. 圖5 TraceView分析圖 在設計應用時, 對存儲目錄的讀取雖然頻繁, 但基本不會用于次數龐大的循環(huán)程序中. 所以, 采用反射機制讀取存儲的目錄對應用程序來說是完全可以的. 綜上所述, 采用本文所介紹的方法的應用在各個Android版本的設備上均可以正常高效運行, 順利讀取其存儲目錄, 且能正確獲取內部存儲路徑、外部存儲路徑、擴展卡與USB設備的路徑, 在健壯性、兼容性等方面均能滿足需求. 1Kim H, Agrawal N, Ungureanu C. Revisiting storage for smartphones. ACM Trans. on Storage (TOS), 2012, 8(4): 14. 2Smith BC. Reflection and semantics in a procedural language. Boston, MA: The Massachusetts Institute of Technology, 1982. 3丘志杰, 羅蕾. 嵌入式Java反射機制的設計與實現. 計算機應用, 2010, 30(2): 398–401, 422. 4張聰品, 劉超. 基于JAVA反射機制的規(guī)則引擎設計與實現. 河南師范大學學報: 自然科學版, 2010, 38(3): 36–39. 5Stefano AD, Fargetta M, Tramontana E. Computational reflection for embedded java systems. Meersman R, Tari Z.On The Move to Meaningful Internet Systems 2003: OTM 2003 Workshops: Lecture Notes in Computer Science. Berlin Heidelberg, Germany, 2003, (2889): 437–450. [doi: 10.1007/b94345] 6王萬森, 龔文. Java動態(tài)類加載機制研究及應用. 計算機工程與設計, 2011, 32(6): 2154–2158. 7Shams Z, Edwards SH. Reflection support: Java reflection made easy. The Open Software Engineering Journal, 2014,7(1): 38–52. 8邱萬彬, 張國杰, 裘鴻林. Java中的組件復用相關技術. 計算機應用, 2005, 25(1): 73–75. 9楊豐盛. Android應用開發(fā)揭秘. 北京: 機械工業(yè)出版社, 2010. 10Jeong S, Lee K, Hwang J, et al. AndroStep: Android storage performance analysis tool. Software Engineering 2013.Bonn, Germany. 2013. 327–340. 11You JM, Park IK. Android storage access control for personal information security. The Journal of The Institute of Internet, Broadcasting and Communication, 2013, 13(6):123–129. [doi: 10.7236/JIIBC.2013.13.6.123] 12Kim H, Shin D. Cross-layered view on android storage IO system. 7th International Conference on Computing and Convergence Technology. Seoul, Korea. 2012. 1102–1105. 13Lim SH, Lee S, Ahn WH. Applications IO profiling and analysis for smart devices. Journal of Systems Architecture,2013, 59(9): 740–747. [doi: 10.1016/j.sysarc.2013.02.005] 14Malony AD, Hammerslag DH, Jablonowski DJ. Traceview:A trace visualization tool. Zima HP. Parallel Computation:Lecture Notes in Computer Science. Berlin Heidelberg,Germany. 1991. 102–144. Research on Android Developing and Utilizing Reflection to Gain Storage Path LU Zhi-Ping, HU Chen-Jun In the development of Android application, the access to the storage is extremely frequent, but different versions of Android are in a muddle in supporting storage, with some versions of Android even without public API support for extended storage access. After conducting a study on Android’s built-in and external storage devices, we propose to divide storages into three types: internal storage, external storage and extended storage. What’s more, we have analyzed the characteristics and access modes of various types of storage, especially the extended storage access mode.We use JAVA reflection mechanism to obtain extended store directory of Android platform. We solve the compatibility problem that different versions of the android access the storage device. Through the analysis tool we have analyzed the efficiency of reflection mechanism in this application, and has tested it on multi-model equipment. Android; reflection; internal storage; external storage; extended storage; path 陸志平,胡晨駿.Android開發(fā)中利用反射獲取存儲路徑的研究.計算機系統應用,2017,26(7):238–244. http://www.c-s-a.org.cn/1003-3254/5836.html 國家自然科學基金(61003180); 江蘇省產學研聯合創(chuàng)新資金(BY2013063-08); 南京中醫(yī)藥大學青年自然科學基金(13XZR34) 2016-10-22; 收到修改稿時間: 2016-12-012 內部存儲、外部存儲及擴展存儲

3 存儲目錄及訪問



4 兼容性分析與效率測試


5 結語
(Information and Technology College, Nanjing University of Chinese Medicine, Nanjing 210046, China)