文件管理器和mediastore数据库同步过程中出现的问题

时间:2021-05-18 14:51:45
文件管理器通常具有 重命名  粘贴  删除的功能,而用这些功能去操作一个多媒体文件时,会造成存储设备上存在的文件和mediastore数据库不同步的问题。mediastore数据库在开机时会自动扫描机器上存储设备的多媒体文件,因此当存储设备上的多媒体文件发生变化时,需要同步mediastore数据库,使mediastore数据库发生同样的变化。

同步mediastore数据库大致分一下:1.    扫描整个存储设备(个人感觉没有必要,而且扫描整个设备是通过发出重新挂载广播来请求的,如果程序中有用到重新挂载广播的方法,在这个方法中会受到重新挂载的广播,造成负面影响)public void sdScan(){
sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://"
+ Environment.getExternalStorageDirectory())));
}

2.扫描单独的一个文件public void scanFileAsync(Context ctx, String filePath) {
Intent scanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
scanIntent.setData(Uri.fromFile(new File(filePath)));
ctx.sendBroadcast(scanIntent);
}
//

ilePath为图片在SD卡中的路径,当保存图片后,调用以上方法即可;

 sendBroadcast为类Context的一个方法

在有些存储设备上发生文件的变动(增加  删除)时,调用  ACTION_MEDIA_SCANNER_SCAN_FILE发送广播,mediastore数据库并不会发生 更新,因为android的mediaProvider类有个 MediaScannerReceiver触发器,我们所发送的要求mediastore数据库更新的广播就是在这个触发器的代码中接收的,在MediaScannerReceiver中根据接收到广播,会启动一个  MediaScannerService服务,在这个服务中会对mediastore数据库进行更新。下面来看为什么我传到MediaScannerReceiver中的广播并没有启动MediaScannerService服务:

android系统接收到 ACTION_MEDIA_SCANNER_SCAN_FILE广播时要执行的语句。

public class MediaScannerReceiver extends BroadcastReceiver {
private final static String TAG = "MediaScannerReceiver";

@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
final Uri uri = intent.getData();
if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
// 系统启动完毕时会发送ACTION_BOOT_COMPLETED广播,
//  MediaScannerReceiver 接收到广播后就开始扫描设备内部存储设备
scan(context, MediaProvider.INTERNAL_VOLUME, null);
} else {
if (uri.getScheme().equals("file")) {
// handle intents related to external storage
String path = uri.getPath();
/// 获取系统默认的外部存储设备路径
String externalStoragePath = Environment.getExternalStorageDirectory().getPath();

 

Log.d(TAG, "action: " + action + " path: " + path);
if (Intent.ACTION_MEDIA_MOUNTED.equals(action)) {
//开始扫描外置已经挂载上的存储设备 SD卡 U盘等
scan(context, MediaProvider.EXTERNAL_VOLUME, path);

} else if (Intent.ACTION_MEDIA_SCANNER_SCAN_FILE.equals(action) &&
//开始扫描文件 注意变量externalStoragePath是用代码获取的默认外置存储设备,
//我们传入的文件路径有可能不是默认存储设备里的,就是这里在接收到广播后会对从广播中获取的路径
//进行判断,如果不是我们的默认存储设备的路径就不会启动mediascannerservice服务,
//就不会更新mediastore数据库
path != null && path.startsWith(externalStoragePath + "/")) {

scanFile(context, path);
}
}
}
}

 

private void scan(Context context, String volume, String volumePath) {
Bundle args = new Bundle();
args.putString("volume", volume);
args.putString("volumepath", volumePath);
context.startService(
new Intent(context, MediaScannerService.class).putExtras(args));
}

 

private void scanFile(Context context, String path) {
Bundle args = new Bundle();
args.putString("filepath", path);
context.startService(
// 开始启用mediascannerservice服务更新mediastore数据库
new Intent(context, MediaScannerService.class).putExtras(args));
}
}

android系统接收到ACTION_MEDIA_SCANNER_SCAN_FILE广播后,在触发器MediaScannerReceiver 中通过传递不同的参数去开始启动MediaScannerService 服务,MediaScannerService 中的分析如下最好对照MediaScannerService 的源码:1.首先执行  public void onCreate()函数来开启一个线程,在新线程中构造了一个Looper来循环处理ServiceHandler类对象接收到的msg。2.执行 public int onStartCommand(Intent intent, int flags, int startId)函数,获取参数中的intent信息,将intent包含的信息利用第一步已经生产的ServiceHandler类对象传递到ServiceHandler类对象中去处理,因此MediaScannerService 的功能大部分是在新线程中完成的。下面主要分析这个新线程到底做了些了什么:private final class ServiceHandler extends Handler
{
@Override
public void handleMessage(Message msg)
{
// 这个arguments包含从mediascannerReceiver中传来的路径信息,内含有文件路径
Bundle arguments = (Bundle) msg.obj;
String filePath = arguments.getString("filepath");

try {
//首先判断从mediascannerReceiver中传来的路径信息是不是空的,如果不为空则说明是要扫描一个单独的文件
if (filePath != null) {
IBinder binder = arguments.getIBinder("listener");
IMediaScannerListener listener =
(binder == null ? null : IMediaScannerListener.Stub.asInterface(binder));
Uri uri = null;
try {
uri = scanFile(filePath, arguments.getString("mimetype"));
} catch (Exception e) {
Log.e(TAG, "Exception scanning file", e);
}
if (listener != null) {
listener.scanCompleted(filePath, uri);
}

//如果filePath 为空则说明是想要扫描某块目录(可能是整个外部存储系统)// ,因此需要判断到底是需要扫描那个目录,是内部存储设备或者外部存储设备,//从arguments获取的volume参数就是来判断扫描分区的(内部存储设备或者外部存储设备)
} else {
//我第一次修改的scanFile函数在启动mediascannerservice时没有传入此参数,传入的是文件的路径,// filePath 不为空,因此不会执行到这里而是执行上面的流程。
String volume = arguments.getString("volume"); 
String[] directories = null;
//MediaProvider.INTERNAL_VOLUME其实是个字符串 “internal”,//目的就是为了标明是内部存储设备还是外部存储设备
if (MediaProvider.INTERNAL_VOLUME.equals(volume)) {
// scan internal media storage
directories = new String[] {
Environment.getRootDirectory() + "/media",
};
}
// 开始扫描外部存储设备
else if (MediaProvider.EXTERNAL_VOLUME.equals(volume)) {
///这个volumepath是用来判断分区(外部存储设备)的路径的,外部存储设备可能有好多块儿存储设备
String volumePath = arguments.getString("volumepath");
if (volumePath == null)//如果传送的volumePath为空,就扫描整个外部存储设备
// 这个 volumePath 是启动这个service服务时传送的参数:
directories = mExternalStoragePaths;
else {// 否则就扫描传送过来的文件夹的位置
directories = new String[] {
volumePath,
};
}
}

if (directories != null) {
if (true) Log.d(TAG, "start scanning volume " + volume + ": "
+ Arrays.toString(directories));
scan(directories, volume);
if (true) Log.d(TAG, "done scanning volume " + volume);
}
}
} catch (Exception e) {
Log.e(TAG, "Exception in handleMessage", e);
}

stopSelf(msg.arg1);
}
};