[Android4.4]Permission Denial: not allowed to send broadcast android.intent.action.MEDIA_MOUNTED

摘要: Android4.4修改了MEDIA_MOUNTED广播的权限,导致发送这个广播会出现权限问题,本文分析这个问题出现的原因,并给出解决方案.

 

0. 问题背景

在Android4.4之前,许多应用程序使用如下代码去通知更新文件数据库

sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, 
     Uri.parse(\"file://\" + Environment.getExternalStorageDirectory())));

这样做的坏处:每次发送这个广播,都会让MediaScanner重新扫描一次系统文件,很影响设备的电池寿命。当然传入的路径可以 是单个文件或者内容很少的文件夹,会减轻扫描系统文件的压力,但是还是会因为扫描频繁而浪费电。所以在Android4.4开始,只允许系统应用发送这样的intent。 所以在Android4.4系统的机器上会出现如下错误:

 Permission Denial: not allowed to send broadcast android.intent.action.MEDIA_MOUNTED

1. 解决办法:

1 . 将Intent换为 ACTION_MEDIA_SCANNER_SCAN_FILE.

sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, 
     Uri.parse(\"file://\" + Environment.getExternalStorageDirectory())));

但是这个Intent只扫描文件,不扫描文件夹。如果你的应用只操作单个文件而不是文件夹,就可以使用这个。 2. 不发广播,换为MediaScannerConnection进行通知更新

 MediaScannerConnection.scanFile(this,
          new String[] { file.toString() }, null,
          new MediaScannerConnection.OnScanCompletedListener() {
      public void onScanCompleted(String path, Uri uri) {
          Log.i(\"ExternalStorage\", \"Scanned \" + path + \":\");
          Log.i(\"ExternalStorage\", \"-> uri=\" + uri);
      }
 });

这样做就属于不发送广播,在4.4上可以正常工作.

拓展

代码跟踪,让我们看看scanFile做了什么事: 1.查看scanFile的源码:

    public static void scanFile(Context context, String[] paths, String[] mimeTypes,
            OnScanCompletedListener callback) {
        ClientProxy client = new ClientProxy(paths, mimeTypes, callback);
        MediaScannerConnection connection = new MediaScannerConnection(context, client);
        client.mConnection = connection;
        connection.connect();
    }

2.ClintProxy源码:

    static class ClientProxy implements MediaScannerConnectionClient {
        final String[] mPaths;
        final String[] mMimeTypes;
        final OnScanCompletedListener mClient;
        MediaScannerConnection mConnection;
        int mNextPath;

        ClientProxy(String[] paths, String[] mimeTypes, OnScanCompletedListener client) {
            mPaths = paths;
            mMimeTypes = mimeTypes;
            mClient = client;
        }

        public void onMediaScannerConnected() {
            scanNextPath();
        }

        public void onScanCompleted(String path, Uri uri) {
            if (mClient != null) {
                mClient.onScanCompleted(path, uri);
            }
            scanNextPath();
        }

        void scanNextPath() {
            if (mNextPath >= mPaths.length) {
                mConnection.disconnect();
                return;
            }
            String mimeType = mMimeTypes != null ? mMimeTypes[mNextPath] : null;
            mConnection.scanFile(mPaths[mNextPath], mimeType);
            mNextPath++;
        }
    }

3.MediaScannerConnection的connect方法:

    /**
     * Initiates a connection to the media scanner service.
     * {@link MediaScannerConnectionClient#onMediaScannerConnected()}
     * will be called when the connection is established.
     */
    public void connect() {
        synchronized (this) {
            if (!mConnected) {
                Intent intent = new Intent(IMediaScannerService.class.getName());
                intent.setComponent(
                        new ComponentName(\"com.android.providers.media\",
                                \"com.android.providers.media.MediaScannerService\"));
                mContext.bindService(intent, this, Context.BIND_AUTO_CREATE);
                mConnected = true;
            }
        }
    }

这里可以看到,最后是start了一个service:MediaScannerService MediaScannerService 的源码可以在这里看到:我们重点看一下scan方法:

private void scan(String[] directories, String volumeName) {
        // don\'t sleep while scanning
        mWakeLock.acquire();

        ContentValues values = new ContentValues();
        values.put(MediaStore.MEDIA_SCANNER_VOLUME, volumeName);
        Uri scanUri = getContentResolver().insert(MediaStore.getMediaScannerUri(), values);

        Uri uri = Uri.parse(\"file://\" + directories[0]);
        sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_STARTED, uri));

        try {
            if (volumeName.equals(MediaProvider.EXTERNAL_VOLUME)) {
                 openDatabase(volumeName);    
            }

            MediaScanner scanner = createMediaScanner();
            scanner.scanDirectories(directories, volumeName);
        } catch (Exception e) {
            Log.e(TAG, \"exception in MediaScanner.scan()\", e); 
        }

        getContentResolver().delete(scanUri, null, null);

        sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_FINISHED, uri));
        mWakeLock.release();
    }

可以看到最终调用了MediaScanner来进行扫描,扫描结束后,发送ACTION_MEDIA_SCANNER_FINISHED广播.应用程序可以接受此广播来更新界面之类

 

参考资料:

  1. http://stackoverflow.com/questions/18624235/android-refreshing-the-gallery-after-saving-new-images

  2. http://stackoverflow.com/questions/21469431/permission-denial-not-allowed-to-send-broadcast-in-android

  3. http://www.androideng.com/?p=1108

作者:Gracker
出处:androidperformance.com
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
打赏一下: 微博打赏

文章目录
  1. 1. 摘要: Android4.4修改了MEDIA_MOUNTED广播的权限,导致发送这个广播会出现权限问题,本文分析这个问题出现的原因,并给出解决方案.
  • 0. 问题背景
  • 1. 解决办法:
    1. 1. 拓展
  • |