本文轉自 http://blog.csdn.net/androidbluetooth/article/details/7603428
博客聲明:
1. 使用 android2.1 源碼說明問題
2. 使用真機,操作系統是 android-2.1
3. 分享一下學習方法,不是為了測試而測試,請大家舉一反三
結合 Service 與 Broadcast 監聽外部存儲設備的狀態,通過測試主要想知道在我們操作外部存儲設備時候發生了哪些事情、以及 Intent 幾個 Action 到底是何意?
測試代碼見 附錄,至於如何啟動這個 Service,隨您意!
主要的 Action
注冊這 13 個 action,然后運行 app ,點擊 back 服務退至后台。
now,ready!來操作 sdcard。
1. 直接拔掉 sdcard
2. 再次將 sdcard 插入卡槽
先大概 1-3 秒的 media checking,然后才是 mounted -- scanner started -- scanner finished
3. 在通知欄卸載 sdcard
緊接着,從卡槽拔出 sdcard(必須拔出,才會接收到下面的 action)
可以看出,這種情況屬於正常卸載 sdcard,不是強制拔出。不同於 1.
這個時候,你將 sdcard 插入卡槽,發生的情況與 2 一致。
4. 在通知欄選擇 "計算機與 sd 卡之間復制文件",即共享
在彈出的對話框選擇 "裝載"
然后,我們再次在通知欄選擇 "關閉 usb 存儲設備",接下來發生的與 2 一致。
從這幾個測試,我們可以發現幾個規律:
1. 不管以何種方式卸載(正常卸載拔出、正常卸載不拔出 sd 卡、直接拔出 sd 卡)
系統都會發出下面的 action 廣播
ACTION_MEDIA_EJECT
ACTION_MEDIA_UNMOUNTED
2. 不管以何種方式安裝 sd 卡,系統都會發出下面的 action 廣播
3. ACTION_MEDIA_REMOVED 與 ACTION_MEDIA_UNMOUNTED 區別
ACTION_MEDIA_REMOVED
表示 sdcard 已經從卡槽移除。
ACTION_MEDIA_UNMOUNTED
只可以說明 sd 卡沒有 mount 在文件系統上面,不可以說明其已經從卡槽移除。
從測試 4 就可以看出這個端倪。
4. ACTION_MEDIA_REMOVED 與 ACTION_MEDIA_BAD_REMOVAL 區別
ACTION_MEDIA_BAD_REMOVAL
只有在直接拔出 sd 卡時,系統才會發送這樣的 action 廣播。
ACTION_MEDIA_REMOVED
不管何種方式從卡槽拔出 sd 卡時,系統就會發送這樣的 action 廣播。
5. 選擇通過 usb 共享,系統一定會發出下面的 action 廣播
ACTION_MEDIA_SHARED
ok,明白上面的道理(你基於的開發平台是否是這樣,你還需要測試,我這里只是拋磚引玉),可以在接收到這些廣播的時候,根據 action 寫自己的邏輯代碼了。如:
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- if (Intent.ACTION_MEDIA_EJECT.equals(action)) {
- // 本人感覺 ACTION_MEDIA_EJECT 比
- // ACTION_MEDIA_UNMOUNTED 好
- // sd 卡不可用
- } else if (Intent.ACTION_MEDIA_REMOVED.equals(action)) {
- // sd 卡已經被移除卡槽
- } else if (Intent.ACTION_MEDIA_SHARED.equals(action)) {
- // 選擇通過 usb 共享
- } else if (Intent.ACTION_MEDIA_MOUNTED.equals(action)) {
- // sd 卡可用
- }
- }
但是這里提醒一下:
接收到 ACTION_MEDIA_EJECT 廣播之后,sd 卡還是可以讀寫的,
直到接收到 ACTION_MEDIA_REMOVED、ACTION_MEDIA_UNMOUNTED等廣播之后,sd 卡才不可以讀寫。
可以借助 Music 源碼 MediaPlaybackService.java 看看:
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
- saveQueue(true);
- mQueueIsSaveable = false;
- closeExternalStorageFiles(intent.getData().getPath());
- } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
- mMediaMountedCount++;
- mCardId = MusicUtils.getCardId(MediaPlaybackService.this);
- reloadQueue();
- mQueueIsSaveable = true;
- notifyChange(QUEUE_CHANGED);
- notifyChange(META_CHANGED);
- }
- }
到這個時候,我們應該搞明白是系統哪個類發出這樣的廣播?有沒有新的發現?
android2.1/frameworks/base/services/java/com/android/server/MountService.java
與其相關的類是
android2.1/frameworks/base/services/java/com/android/server/MountListener.java
繼續跟蹤 MountService.java , 我們會發現實例化 intent:
intent = new Intent(Intent.ACTION_MEDIA_SHARED, Uri.parse("file://" + path));
都包含一個 scheme 為 file 的 path,那麽這個 path 是什么呢?
可以在 onReceive 方法里面得到這個值
final String path = intent.getData().getPath()
其實,就是 "/sdcard" (即 sd 卡路徑)。
這個信息很有用!!!
比如你的手機可以外括除了 sd 卡的其它外部設備(如 u 盤、map 卡)
那麽這個返回的路徑就不一樣,可以根據返回的路徑判斷你當前操作的是哪個設備了!
耶耶,酷比嘞!
在 MountService.java 里面還有一個與眾不同的地方:
- void notifyMediaMounted(String path, boolean readOnly) {
- setMediaStorageNotification(0, 0, 0, false, false, null);
- updateUsbMassStorageNotification(false, false);
- Intent intent = new Intent(Intent.ACTION_MEDIA_MOUNTED,
- Uri.parse("file://" + path));
- intent.putExtra("read-only", readOnly);
- mMounted = true;
- mContext.sendBroadcast(intent);
- }
intent.putExtra("read-only", readOnly)
其中 readOnly 是一個 boolean 值,在 onReceive 里面 只有 action 是 ACTION_MEDIA_MOUNTED,接收到該值是 false.
------------- 附錄
PlayerService.java
- package mark.zhang;
- import android.app.Service;
- import android.content.BroadcastReceiver;
- import android.content.Context;
- import android.content.Intent;
- import android.content.IntentFilter;
- import android.os.IBinder;
- import android.util.Log;
- public class PlayerService extends Service {
- private static final String TAG = "PlayerService";
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
- @Override
- public void onCreate() {
- super.onCreate();
- registerReceivers();
- }
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- return super.onStartCommand(intent, flags, startId);
- }
- @Override
- public void onDestroy() {
- Log.d(TAG, "onDestroy------");
- super.onDestroy();
- unregisterReceivers();
- }
- private BroadcastReceiver externalStorageReceiver = null;
- /**
- * 注冊廣播
- */
- private void registerReceivers() {
- if (externalStorageReceiver == null) {
- externalStorageReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- final String path = intent.getData().getPath();
- Log.d(TAG, "receive action = " + action);
- boolean value = intent.getBooleanExtra("read-only", true);
- Log.d(TAG, "external storage path = " + path);
- Log.d(TAG, "external storage value = " + value);
- }
- };
- final IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_MEDIA_BAD_REMOVAL);
- filter.addAction(Intent.ACTION_MEDIA_BUTTON);
- filter.addAction(Intent.ACTION_MEDIA_CHECKING);
- filter.addAction(Intent.ACTION_MEDIA_EJECT);
- filter.addAction(Intent.ACTION_MEDIA_MOUNTED);
- filter.addAction(Intent.ACTION_MEDIA_NOFS);
- filter.addAction(Intent.ACTION_MEDIA_REMOVED);
- filter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
- filter.addAction(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
- filter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
- filter.addAction(Intent.ACTION_MEDIA_SHARED);
- filter.addAction(Intent.ACTION_MEDIA_UNMOUNTABLE);
- filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
- // 必須添加,否則無法接收到廣播
- filter.addDataScheme("file");
- registerReceiver(externalStorageReceiver, filter);
- }
- }
- /**
- * 取消注冊
- */
- private void unregisterReceivers() {
- if (externalStorageReceiver != null) {
- unregisterReceiver(externalStorageReceiver);
- externalStorageReceiver = null;
- }
- }
- }