look321 发表于 2015-10-14 09:43:23

Service: 监听外部存储设备


[*]登录 | 注册
[*]
[*]
CodingMan
--- what is most important?

[*]目录视图
[*]摘要视图
[*]订阅
有奖征资源,博文分享有内涵      4月推荐博文汇总      CSDN博客支持Windows Live Writer离线写博客啦Service: 监听外部存储设备
分类: Android 技术笔记 2012-05-25 23:50 2495人阅读 评论(0)收藏 举报service存储actionstringpath测试  博客声明:
  

  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 写自己的逻辑代码了。如:
  

  
view plaincopyprint?
[*]       @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 看看:
  

  
view plaincopyprint?
[*]@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 方法里面得到这个值
  

  finalString path = intent.getData().getPath()

  

  其实,就是 "/sdcard" (即 sd卡路径)。
  

  这个信息很有用!!!
  

  比如你的手机可以外括除了 sd 卡的其它外部设备(如u 盘、map 卡)
  那麽这个返回的路径就不一样,可以根据返回的路径判断你当前操作的是哪个设备了!
  

  耶耶,酷比嘞!
  

  在 MountService.java里面还有一个与众不同的地方:
  

  
view plaincopyprint?
[*]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
  

  
view plaincopyprint?
[*]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;
[*]      }
[*]    }
[*]
[*]}


  

  

  

  

  

  
  

更多0
页: [1]
查看完整版本: Service: 监听外部存储设备