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

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

    摘要: 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
    本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
    打赏一下: 微博打赏

  • script>