MediaStore上的ContentLoader,以及自动刷新/重新加载

时间:2015-07-20 09:36:38

标签: android mediastore contentobserver

我正在查询MediaStore,joining Images and Thumbnails,并在GridView中加载。这个工作得很好,但是当MediaStore中出现新照片时(通常由相机拍摄),我无法弄清楚如何更新我的应用程序。默认的图库应用程序会自动加载新图像,但我的应用程序什么都不做。我尝试过注册内容观察员,我甚至尝试过添加手册"刷新"菜单按钮,但加载新图像的唯一方法是停止并重新启动整个应用程序。

修改 实际上,这并不完全正确。如果我拍摄一张新照片,并切换到其他图库应用程序或小部件,那么这个其他应用程序将加载新图像(缩略图),然后我的应用程序将突然刷新。这表明我正在观察一些事件,因为新缩略图最终会自动显示在我的应用中?也许我只是没有观察到正确的。我只是不明白手动刷新是如何完成的。

我发布了相关的java文件,也许有人可以看到我忘记了什么,或者我做错了什么?

我不知所措,已经在这方面工作了一段时间,所以如果帖子太模糊,请原谅我。我会尝试添加缺少的任何细节。

光碎片

package com.example.android.galleri.app;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.util.Log;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.FrameLayout;
import android.widget.GridView;
import android.widget.TextView;

import com.example.android.galleri.app.data.PhotoContract;


public class PhotoFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> {

    private final static int LOADER_ID = 87;
    private PhotoAdapter mPhotoAdapter;
    private int mPosition;
    private GridView mGridView;
    private TextView mEmptyView;
    private int count_mem = -1;
    private static final String SELECTED_KEY = "POSITION";
    private boolean mTwoPane;
    private boolean DF_hidden = false;

    // these are the data we want from MediaStore
    private final static String[] THUMBNAIL_COLUMNS = {
            PhotoContract.ThumbEntry.COLUMN_THUMB_ID,
            PhotoContract.ThumbEntry.COLUMN_DATA,
            PhotoContract.ThumbEntry.COLUMN_IMAGE_ID,
            PhotoContract.ThumbEntry.COLUMN_TITLE,
            PhotoContract.ThumbEntry.COLUMN_DESC,
            PhotoContract.ThumbEntry.COLUMN_DATE
    };

    static final int COL_THUMB_ID = 0;
    static final int COL_THUMB_DATA = 1;
    static final int COL_THUMB_IMAGE_ID = 2;
    static final int COL_TITLE = 3;
    static final int COL_DESC = 4;
    static final int COL_DATE = 5;

    public void setTwoPaneUI(boolean pTwoPane) {
        mTwoPane = pTwoPane;
    }

    // interfaces
    public interface Callback {
        public void onItemSelected(Uri photoUri);
    }
    public interface FragmentCallback {
        public void onTaskDone(int count);
    }


    public PhotoFragment() {
    }

    public int getPosition() {
       return mPosition;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        getLoaderManager().initLoader(LOADER_ID, null, this);
        super.onActivityCreated(savedInstanceState);
    }


    public void onNewCameraEvent() {
        Log.v("JOAKIM", "PhotoFragment.onNewCameraEvent() triggering a refresh...");
        restartLoader();
    }
    // https://stackoverflow.com/questions/22241705/calling-a-activity-method-from-broadcastreceiver-in-android
    BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            // internet lost alert dialog method call from here...
            Log.v("JOAKIM", "PhotoFragment.broadcastReceiver.onReceive() captured the image!");
            onNewCameraEvent();
        }
    };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);
        getActivity().registerReceiver(broadcastReceiver, new IntentFilter("TRIGGER_A_REFRESH"));
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        getActivity().unregisterReceiver(broadcastReceiver);
    }




    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.photofragment, menu);
    }



    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();

        if (id == R.id.action_refresh) {
            Log.v("JOAKIM", "PhotoFragment.onOptionsItemSelected() refresh clicked");

            // https://stackoverflow.com/questions/15271271/android-callback-asynctask-to-fragmentnot-activity
            ManualRefreshTask refreshTask = new ManualRefreshTask(
                    new FragmentCallback() {
                        @Override
                        public void onTaskDone(int count) {
                            // loader is restarted every time, since I couldn't
                            // get the logic to work 100%
                            restartLoader();
                            /*
                            // if the number of images has changed, restart loader
                            if (count != count_mem) restartLoader();
                            */
                            count_mem = count;
                        }
                    });
            refreshTask.execute(getActivity().getContentResolver());
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
    public void restartLoader() {
        getLoaderManager().restartLoader(LOADER_ID, null, this);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        if (mPosition != GridView.INVALID_POSITION) {
            outState.putInt(SELECTED_KEY, mPosition);
        }
        super.onSaveInstanceState(outState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_main, container, false);

        mPhotoAdapter = new PhotoAdapter(getActivity(), null, 0);
        GridView gridView = (GridView) rootView.findViewById(R.id.listview_photo);
        gridView.setAdapter(mPhotoAdapter);

        mEmptyView = (TextView) rootView.findViewById(R.id.list_empty);
        mGridView = gridView;

        // handle user clicking on an image
        gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView parent, View view, int position, long id) {
                Cursor cursor = (Cursor) parent.getItemAtPosition(position);

                if (cursor != null) {
                    Uri baseUri = PhotoContract.PhotoEntry.buildPhotoUriWithId(cursor.getLong(COL_THUMB_IMAGE_ID));
                    Log.v("JOAKIM", "PhotoFragment.onItemClick() image_id = " + cursor.getLong(COL_THUMB_IMAGE_ID)
                            + ", baseUri = " + baseUri.toString());

                    // make sure detail fragment is visible
                    if (mTwoPane) showDetailFragment();
                    else Utility.setPosition(position);  // else (single-pane): store position clicked!

                    ((Callback)getActivity()).onItemSelected(baseUri);
                }
                mPosition = position;
            }
        });

        if (savedInstanceState != null && savedInstanceState.containsKey(SELECTED_KEY)) {
            mPosition = savedInstanceState.getInt(SELECTED_KEY);
        }

        return rootView;
    }


    @Override
    public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {

        Uri thumbs_uri = PhotoContract.ThumbEntry.CONTENT_URI;
        return new CursorLoader(getActivity(), thumbs_uri, THUMBNAIL_COLUMNS,
                null,null,  // read everything (all thumbnails)
                PhotoContract.ThumbEntry.COLUMN_THUMB_ID + " DESC");
    }

    @Override
    public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
        if (cursor != null) {

            // register observer onto cursor
            cursor.setNotificationUri(getActivity().getContentResolver(), PhotoContract.ThumbEntry.CONTENT_URI);

            mPhotoAdapter.swapCursor(cursor);
            if (mPosition != GridView.INVALID_POSITION) {
                mGridView.setSelection(mPosition);  // scroll into view
            }
        }

        // save (set) cursor in Utility
        Utility.setCursor(cursor);

        if (cursor == null) {
            mEmptyView.setText("No Images on Device");
            mEmptyView.setVisibility(View.VISIBLE);

            if (mTwoPane) {
                mGridView.setVisibility(View.GONE);
                hideDetailFragment();
            }

        } else {
            mEmptyView.setVisibility(View.GONE);

            if (mTwoPane) {
                mGridView.setVisibility(View.VISIBLE);
                showDetailFragment();
            }
        }
    }


    public void hideDetailFragment() {
        if (!DF_hidden) {
            Log.v("JOAKIM", "PhotoFragment.onLoadFinished() remove detail framelayout");
            FrameLayout container = (FrameLayout) getActivity().findViewById(R.id.photo_detail_container);
            container.setVisibility(View.GONE);
            DF_hidden = true;

            Display display = ((WindowManager) getActivity().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
            int rotation = display.getRotation();

            int numColumns = 10;
            if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) numColumns = 8;
            else numColumns = 10;

            mGridView.setNumColumns(numColumns);
        }
    }
    public void showDetailFragment() {
        if (DF_hidden) {
            Log.v("JOAKIM", "PhotoFragment.onLoadFinished() show detail fragment");
            FrameLayout container = (FrameLayout) getActivity().findViewById(R.id.photo_detail_container);
            container.setVisibility(View.VISIBLE);
            DF_hidden = false;

            mGridView.setNumColumns(6);
        }
    }

    @Override
    public void onLoaderReset(Loader<Cursor> cursorLoader) {
        mPhotoAdapter.swapCursor(null);
    }


}

PhotoContract

package com.example.android.galleri.app.data;

import android.content.ContentResolver;
import android.content.ContentUris;
import android.net.Uri;
import android.provider.BaseColumns;
import android.provider.MediaStore;
import android.util.Log;

import java.util.HashMap;
import java.util.Map;

public class PhotoContract {

    public static final String CONTENT_AUTHORITY = "com.example.android.galleri.app";

    public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);

    public static final String PATH_THUMBS = "thumbs";
    public static final String PATH_PHOTO = "photo";
    public static final String PATH_COMMENT = "comment";
    //public static final String PATH_AUTHOR = "author";

    // define updateable fields (where to update them)
    public static final String TARGET_EXIF = "EXIF";
    public static final String TARGET_MEDIASTORE = "MediaStore.Images.Media";
    public static final String TARGET_MEDIASTORE_THUMBS = "MediaStore.Images.Thumbnails";

    public static final class ThumbEntry {

        public static final Uri CONTENT_URI =
                BASE_CONTENT_URI.buildUpon().appendPath(PATH_THUMBS).build();

        public static final String COLUMN_THUMB_ID = MediaStore.Images.Thumbnails._ID;
        public static final String COLUMN_DATA = MediaStore.Images.Thumbnails.DATA;
        public static final String COLUMN_IMAGE_ID = MediaStore.Images.Thumbnails.IMAGE_ID;

        public static final String COLUMN_IMAGE_PK = MediaStore.Images.Media._ID;
        public static final String COLUMN_TITLE = MediaStore.Images.Media.TITLE;
        public static final String COLUMN_DESC = MediaStore.Images.Media.DESCRIPTION;
        public static final String COLUMN_DATE = MediaStore.Images.Media.DATE_TAKEN;

        public static final String CONTENT_TYPE =
                ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_THUMBS;

        public static final Map<String, String> COL_TYPES;
        static {
            COL_TYPES = new HashMap<String, String>();
            COL_TYPES.put(COLUMN_THUMB_ID, TARGET_MEDIASTORE_THUMBS);
            COL_TYPES.put(COLUMN_DATA, TARGET_MEDIASTORE_THUMBS);
            COL_TYPES.put(COLUMN_IMAGE_ID, TARGET_MEDIASTORE_THUMBS);
            COL_TYPES.put(COLUMN_TITLE, TARGET_MEDIASTORE);
            COL_TYPES.put(COLUMN_DESC, TARGET_MEDIASTORE);
            COL_TYPES.put(COLUMN_DATE, TARGET_MEDIASTORE);
        }
    }

    public static final class PhotoEntry {

        public static final Uri CONTENT_URI =
                BASE_CONTENT_URI.buildUpon().appendPath(PATH_PHOTO).build();

        public static final String COLUMN_IMAGE_ID = MediaStore.Images.Media._ID;
        public static final String COLUMN_DISPLAY_NAME = MediaStore.Images.Media.DISPLAY_NAME;
        public static final String COLUMN_DATA = MediaStore.Images.Media.DATA;
        public static final String COLUMN_DESC = MediaStore.Images.Media.DESCRIPTION;
        public static final String COLUMN_DATE_TAKEN = MediaStore.Images.Media.DATE_TAKEN;
        public static final String COLUMN_DATE_ADDED = MediaStore.Images.Media.DATE_ADDED;
        public static final String COLUMN_TITLE = MediaStore.Images.Media.TITLE;
        public static final String COLUMN_SIZE = MediaStore.Images.Media.SIZE;
        public static final String COLUMN_ORIENTATION = MediaStore.Images.Media.ORIENTATION;
        public static final String COLUMN_HEIGHT = MediaStore.Images.Media.HEIGHT;
        public static final String COLUMN_WIDTH = MediaStore.Images.Media.WIDTH;

        public static final String COLUMN_EXIF_COMMENT = "UserComment";
        public static final String COLUMN_EXIF_AUTHOR = "Author";

        public static final Map<String, String> COL_TYPES;
        static {
            COL_TYPES = new HashMap<String, String>();
            COL_TYPES.put(COLUMN_IMAGE_ID, TARGET_MEDIASTORE);
            COL_TYPES.put(COLUMN_DISPLAY_NAME, TARGET_MEDIASTORE);
            COL_TYPES.put(COLUMN_DATA, TARGET_MEDIASTORE);
            COL_TYPES.put(COLUMN_DESC, TARGET_MEDIASTORE);
            COL_TYPES.put(COLUMN_DATE_TAKEN, TARGET_MEDIASTORE);
            COL_TYPES.put(COLUMN_DATE_ADDED, TARGET_MEDIASTORE);
            COL_TYPES.put(COLUMN_TITLE, TARGET_MEDIASTORE);
            COL_TYPES.put(COLUMN_SIZE, TARGET_MEDIASTORE);
            COL_TYPES.put(COLUMN_ORIENTATION, TARGET_MEDIASTORE);
            COL_TYPES.put(COLUMN_HEIGHT, TARGET_MEDIASTORE);
            COL_TYPES.put(COLUMN_WIDTH, TARGET_MEDIASTORE);

            COL_TYPES.put(COLUMN_EXIF_COMMENT, TARGET_EXIF);
            COL_TYPES.put(COLUMN_EXIF_AUTHOR, TARGET_EXIF);
        }

        public static final String CONTENT_TYPE =
                ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_PHOTO;
        public static final String CONTENT_ITEM_TYPE =
                ContentResolver.CURSOR_ITEM_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_PHOTO;

        // makes an URI to a specific image_id
        public static final Uri buildPhotoUriWithId(Long photo_id) {
            return CONTENT_URI.buildUpon().appendPath(Long.toString(photo_id)).build();
        }

        // since we will "redirect" the URI towards MediaStore, we need to be able to extract IMAGE_ID
        public static Long getImageIdFromUri(Uri uri) {
            return Long.parseLong(uri.getPathSegments().get(1));  // always in position 1
        }

    }
}

PhotoProvider

package com.example.android.galleri.app.data;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.AbstractWindowedCursor;
import android.database.Cursor;
import android.database.CursorJoiner;
import android.database.CursorWindow;
import android.database.CursorWrapper;
import android.database.MatrixCursor;
import android.media.ExifInterface;
import android.net.Uri;
import android.provider.MediaStore;
import android.util.Log;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Map;



public class PhotoProvider extends ContentProvider {

    // The URI Matcher used by this content provider.
    private static final UriMatcher sUriMatcher = buildUriMatcher();

    // query: get all thumbnails, or get specific image
    static final int THUMBS = 100;
    static final int PHOTO = 101;
    static final int FIRST_PHOTO = 102;

    // update: set comment etc
    static final int PHOTO_COMMENT = 200;


    static UriMatcher buildUriMatcher() {
        // 1) The code passed into the constructor represents the code to return for the root
        // URI.  It's common to use NO_MATCH as the code for this case. Add the constructor below.
        final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
        final String authority = PhotoContract.CONTENT_AUTHORITY;

        // 2) Use the addURI function to match each of the types.  Use the constants from
        // WeatherContract to help define the types to the UriMatcher.

        // get all thumbnails (with a JOIN towards MediaStore.Images, to get title and description, too)
        matcher.addURI(authority, PhotoContract.PATH_THUMBS, THUMBS);

        // matches photo (no ID). Means get first image
        matcher.addURI(authority, PhotoContract.PATH_PHOTO, FIRST_PHOTO);

        // matches photo/<any number> meaning any photo ID
        matcher.addURI(authority, PhotoContract.PATH_PHOTO + "/#", PHOTO);

        // matches photo/<photo id>/comment/ (comment text in ContentValues)
        matcher.addURI(authority, PhotoContract.PATH_PHOTO + "/#/" + PhotoContract.PATH_COMMENT, PHOTO_COMMENT);

        // 3) Return the new matcher!
        return matcher;
    }



    @Override
    public String getType(Uri uri) {

        // Use the Uri Matcher to determine what kind of URI this is.
        final int match = sUriMatcher.match(uri);

        // Note: We always return single row of data, so content-type is always "a dir"
        switch (match) {
            case PHOTO_COMMENT:
                return PhotoContract.PhotoEntry.CONTENT_TYPE;
            case PHOTO:
                return PhotoContract.PhotoEntry.CONTENT_TYPE;
            case THUMBS:
                return PhotoContract.ThumbEntry.CONTENT_TYPE;
            default:
                throw new UnsupportedOperationException("Unknown uri: " + uri);
        }
    }


    @Override
    public boolean onCreate() {
        return true;  // enough?
    }


    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {


        // determine whether this is THUMBS or FULLSIZE query
        int match = sUriMatcher.match(uri);
        switch (match) {

            case THUMBS: {

                // split projection into MediaStore and MediaStore.Thumbs parts
                ArrayList<String> MS_proj = new ArrayList<>();
                ArrayList<String> MS_proj_thumbs = new ArrayList<>();
                for (String f : projection) {
                    if (PhotoContract.ThumbEntry.COL_TYPES.get(f).equals(PhotoContract.TARGET_MEDIASTORE)) {
                        MS_proj.add(f);
                    } else {
                        MS_proj_thumbs.add(f);
                    }
                }

                String[] MS_proj_thumbs_array = new String[MS_proj_thumbs.size()];
                MS_proj_thumbs_array = MS_proj_thumbs.toArray(MS_proj_thumbs_array);

                // add _ID column to Images.Media projection, for use in JOIN
                MS_proj.add("_ID");
                String[] MS_proj_array = new String[MS_proj.size()];
                MS_proj_array = MS_proj.toArray(MS_proj_array);

                Uri baseUri;

                // first, get cursor from MediaStore.Images.Thumbnails containing all thumbnails
                baseUri = MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI;
                Log.v("JOAKIM", "Thumb: order by " + PhotoContract.ThumbEntry.COLUMN_DATE);
                Cursor c_thumbs = getContext().getContentResolver().query(baseUri, MS_proj_thumbs_array, null, null, PhotoContract.ThumbEntry.COLUMN_IMAGE_ID);
                if (c_thumbs == null || !c_thumbs.moveToFirst()) {
                    Log.v("JOAKIM", "MediaStore.Thumbnails cursor contains no data...");
                    return null;
                }

                // then, get cursor from MediaStore.Images.Media (for TITLE and DESCRIPTION etc)
                // add _ID column to query
                baseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                Cursor c_images = getContext().getContentResolver().query(baseUri, MS_proj_array, null, null, PhotoContract.ThumbEntry.COLUMN_IMAGE_PK);
                if (c_images == null || !c_images.moveToFirst()) {
                    Log.v("JOAKIM", "MediaStore.Images cursor contains no data...");
                    return null;
                }

                // join these and return
                // the join is on images._ID = thumbnails.IMAGE_ID
                CursorJoiner joiner = new CursorJoiner(
                        c_thumbs, new String[] { PhotoContract.ThumbEntry.COLUMN_IMAGE_ID },
                        c_images, new String[] { PhotoContract.ThumbEntry.COLUMN_IMAGE_PK }
                );

                MatrixCursor retCursor = new MatrixCursor(projection);

                /*
                Does a join on two cursors using the specified columns.
                The cursors must already be sorted on each of the specified columns in ascending order.
                This joiner only supports the case where the tuple of key column values is unique.
                */
                for (CursorJoiner.Result joinerResult : joiner) {
                    switch (joinerResult) {
                        case LEFT:
                            // handle case where a row in cursorA is unique
                            break;
                        case RIGHT:
                            // handle case where a row in cursorB is unique
                            break;
                        case BOTH:

                            // handle case where a row with the same key is in both cursors
                            retCursor.addRow(new Object[]{
                                    c_thumbs.getLong(0),  // ID
                                    c_thumbs.getString(1), // data
                                    c_thumbs.getLong(2), // image id
                                    c_images.getString(0), // title
                                    c_images.getString(1),  // desc
                                    c_images.getLong(2)  // date
                            });
                            break;
                    }
                }


                // https://stackoverflow.com/questions/12065606/getcontentresolver-query-cause-cursorwrapperinner-warning
                c_thumbs.close();
                c_images.close();

                //retCursor.setNotificationUri(getContext().getContentResolver(), uri);
                return retCursor;
            }


        }

        return null;
    }



}

0 个答案:

没有答案