Service: 监听外部存储设备

本文详细介绍了通过Service和Broadcast在Android 2.1系统中监听外部存储设备状态的方法。包括不同操作(如拔插SD卡、USB共享)下触发的各种Intent Action,并解释了ACTION_MEDIA_EJECT、ACTION_MEDIA_REMOVED等Action的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

博客声明:


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;
		}
	}

}









SD女佣 SD Maid Pro将会以完善的方式帮您整理、清洁android设。”浏览器”是一个完全成熟的文件管理器,用它来浏览管理你的android文件夹。”搜索器”可以使用来打来/删除 或者重命名文件(搜索文件内部,支持通配符)。”查找冗余”可以搜索你设的空文件夹目录,并且比较那些已安装的应用程序列表。”应用控制”可以冻结、重置以及删除应用程序(甚至是系统应用程序)。”清理系统”可以清理设中已知的不必要的目录。”优化数据库”检测数据库((System/Apps),并可以整理压缩数据库,以加快访问,以及腾出更多的可用空间。 这个来自您Android设的女佣,会让你的储器恢复到整洁状态并保持下去。 这个程序最好安装在已经ROOT的Android设 ! 没有ROOT的设,SD女佣对必要的目录没有访问权限。 这是一个强大的工具 !但是使用它会有相对而言的风险 ! 最好不要安装非官方版本的SD女佣,它们“签证包”是错误,会带来不安全因素。 金无足赤人无完人, Android也是如此。 被你删除的应用程序有时候会留下的残留数据。 android系统也会不断创建日志、 崩溃报告和普通用户用不上调试文件。 在您使用“谷歌应用市场”下载应用程序之后,它会产生临时文件(如同浏览器访问网站一样)。 让我们来搞定这些无用的垃圾 … … 为什么不使用“SD女佣”来一次全面的清洁呢?SD 女佣将会以完善的方式帮您整理、清洁android设 (^.^)! 选择一个标签卡项,点击”检查”,然后点击”全部清理”或者根据需求单个的选择。它就这么的容易。 “浏览器”是一个完全成熟的文件管理器,用它来浏览管理你的android文件夹。 “搜索器”可以使用来打来/删除 或者重命名文件(搜索文件内部,支持通配符)。 “查找冗余”可以搜索你设的空文件夹目录,并且比较那些已安装的应用程序列表。 “应用控制”可以冻结、重置以及删除应用程序(甚至是系统应用程序)。 “清理系统”可以清理设中已知的不必要的目录。 “优化数据库”检测数据库((System/Apps),并可以整理压缩数据库,以加快访问,以及腾出更多的可用空间。 此外,你也可以查看”最大文件”,或者查看上次修改的文件。 免费版可以使用10个主要功能,另外2个高级功能,需要购买“解锁器”来解锁。 SD女佣正在精益求精的不断的研发和设计中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值