プログラミングノート

一からものを作ることが好きなエンジニアの開発ブログです。

MediaScanがKitKat(4.4)で動作しない件について

Androidではギャラリーアプリに出てくる画像や動画は全てContentProviderで管理されているため、写真アプリなどでファイル操作を行った場合、ContentProviderのデータについても正しくアップデートをかける必要があります。

4.3まではファイル変更後、下記のようにACTION_MEDIA_MOUNTEDを投げるだけでオールオッケーだったのですが、これを4.4で実行するとExceptionが発生するようになり利用出来なくなってしまいました。

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

どうしたもんかなーと悩んでいたのですが、結局のところは全ての変更を一つ一つ通知するか、直接ContentProviderのデータベースをアップデートするしか方法がないようでしたので、ファイル、フォルダ変更に対応できる処理を作りました。

全ての画像と動画をログに出力する

まずはContentProviderにある全ての画像と動画を出力するデバッグ用のメソッド。各種操作の前後で実行するとよいです。

public static void printAllFiles(Context context) {
    String selection
        = MediaStore.Files.FileColumns.MEDIA_TYPE + "="
        + MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE
        + " OR "
        + MediaStore.Files.FileColumns.MEDIA_TYPE + "="
        + MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO;

    Cursor cursor = context.getContentResolver().query(
        MediaStore.Files.getContentUri("external"),
        null,
        selection,
        null,
        null);

    if (cursor != null && cursor.moveToFirst()) {
        for (int k = 0; k < cursor.getCount(); k++) {
            String path = cursor.getString(cursor.getColumnIndex(MediaStore.Files.FileColumns.DATA));
            Logger.d(Logger.TAG_MEDIA, path);
            cursor.moveToNext();
        }
        cursor.close();
    }
}

ファイルを登録する

filesPathにContentProviderに登録したいファイルのフルパスを指定します。複数一括登録OK。

public static void addFiles(Context context, String[] filesPath) {
    MediaScannerConnection.scanFile(
        context,
        filesPath,
        null,
        new MediaScannerConnection.OnScanCompletedListener() {
            @Override
            public void onScanCompleted(String path, Uri uri) {
            }
        });
}

ファイルを削除する

ContentProviderから任意のファイルを削除します。

public static void deleteFile(Context context, String filePath){
    context.getContentResolver().delete(
        MediaStore.Files.getContentUri("external"),
        MediaStore.Files.FileColumns.DATA + "=?",
        new String[]{ filePath }
    );
}

フォルダを削除する

ContentProviderから任意のフォルダを削除します。実際にはフォルダがContentProviderに登録されているわけではないため、該当フォルダ配下のファイルを全削除しています。

public static void deleteDirectory(Context context, String filePath){
    File f = new File(filePath);
    if(!f.isDirectory()){
        return;
    }
    context.getContentResolver().delete(
        MediaStore.Files.getContentUri("external"),
        MediaStore.Files.FileColumns.DATA + " like ?",
        new String[]{ filePath + "%" }
    );
}

フォルダをリネームする

元ファイルを削除して、変更後ファイルを登録し直します。

public static void renameDirectory(Context context, String fromPath, String toPath){
    ArrayList<String> pathToAdd = new ArrayList<String>();

    Cursor cursor = context.getContentResolver().query(
        filesUri(),
        null,
        MediaStore.Files.FileColumns.DATA + " like ?",
        new String[]{fromPath + "%"},
        null);

    if (cursor != null && cursor.moveToFirst()) {
        for (int k = 0; k < cursor.getCount(); k++) {
            String path = cursor.getString(cursor.getColumnIndex(MediaStore.Files.FileColumns.DATA));
            String newPath = path.replaceAll(fromPath, toPath);
            pathToAdd.add(newPath);
            cursor.moveToNext();
        }
        cursor.close();
    }

    String[] files = pathToAdd.toArray(new String[pathToAdd.size()]);
    addFiles(context, files);
    removeDirectory(context, fromPath);
}

このようなメソッドで色々とContentProviderをいじったり破壊したりしつつ、元に戻したくなった場合はこちらのアプリを使って根本からさくっと再構築すればOKです。
Kitkat対応の高速メディアスキャンアプリ - Google Play の Android アプリ