原生Android中图库的Widget组件小解

本文背景:前两天遇到一个问题,在原生的图库中应该都有这个想象(可以在谷歌儿子中试一下)。

             在桌面添加图库的widget,会提示选择图片,这时你随便选择一张。然后在重复添加一个widget,并且选择同一张图片。这时你点击前面添加的widget,你会发现点击没有反应,进入不到图库。第二个就可以进入。


经过一天的研究代码和查找,终于找到了问题所在。下面就给大家简单说一下图库的widget生成。另外不写下来,自己可能以后就忘了,所以按照我自己的思路就写一下。

        在图库的代码中专门有一个文件夹存放widget相关的java文件。com.android.gallery3d.gadget包下。

        LocalPhotoSource.java
        MediaSetSource.java
        PhotoAppWidgetProvider.java
        WidgetClickHandler.java
        WidgetConfigure.java  
        WidgetDatabaseHelper.java   
        WidgetService.java
        WidgetSource.java 
        WidgetTypeChooser.java
        WidgetUtils.java
        共有上面这几个文件。

一、1,用PhotoAppWidgetProvider继承了widget的必备类AppWidgetProvider。

        public class PhotoAppWidgetProvider extends AppWidgetProvider{}

        2,咱们看一下Gallery的AndroidManifest.xml文件。它是这样声明PhotoAppWidgetProvider。

<receiver android:name="com.android.gallery3d.gadget.PhotoAppWidgetProvider"
                android:label="@string/appwidget_title">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>
            <meta-data android:name="android.appwidget.provider"
                    android:resource="@xml/widget_info" />
</receiver>

        这个声明也是生成widget的必备声明,只有一个update action,所以在类中只重写了onUpdate()方法。咱们看一下怎么重写的。

    @Override
    public void onUpdate(Context context,
            AppWidgetManager appWidgetManager, int[] appWidgetIds) {

        if (ApiHelper.HAS_REMOTE_VIEWS_SERVICE) {
            // migrate gallery widgets from pre-JB releases to JB due to bucket ID change
            GalleryWidgetMigrator.migrateGalleryWidgets(context);
        }

        WidgetDatabaseHelper helper = new WidgetDatabaseHelper(context);
        try {
            for (int id : appWidgetIds) {
                Entry entry = helper.getEntry(id);
                if (entry != null) {
                    RemoteViews views = buildWidget(context, id, entry);
                    appWidgetManager.updateAppWidget(id, views);
                } else {
                    Log.e(TAG, "cannot load widget: " + id);
                }
            }
        } finally {
            helper.close();
        }
        super.onUpdate(context, appWidgetManager, appWidgetIds);
    }

    static RemoteViews buildWidget(Context context, int id, Entry entry) {

        switch (entry.type) {
            case WidgetDatabaseHelper.TYPE_ALBUM:
            case WidgetDatabaseHelper.TYPE_SHUFFLE:
                return buildStackWidget(context, id, entry);
            case WidgetDatabaseHelper.TYPE_SINGLE_PHOTO:
                return buildFrameWidget(context, id, entry);
        }
        throw new RuntimeException("invalid type - " + entry.type);
    }

大家可以看到上面还用到了数据库WidgetDatabaseHelper类,它的作用是对widget的内容的记录,记住widget展示的图片的uri,相册的uri,和相册类型。看一下它的数据表。

    private static final String FIELD_APPWIDGET_ID = "appWidgetId";
    private static final String FIELD_IMAGE_URI = "imageUri";
    private static final String FIELD_PHOTO_BLOB = "photoBlob";
    private static final String FIELD_WIDGET_TYPE = "widgetType";
    private static final String FIELD_ALBUM_PATH = "albumPath";
    private static final String FIELD_RELATIVE_PATH = "relativePath";

这类变量就是表中的列名,从名字上可以看出各列的意义。上面的onUpdate的主要作用是更新widget的信息。

而从开始创建widget时,先走的并不是这个方法,而是另外的一条路。

     

        3,上面的manifest文件咱们看到android:resource="@xml/widget_info"属性。看一下这个xml文件。也是生成widget的必需品。

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
        android:minWidth="@dimen/appwidget_width"
        android:minHeight="@dimen/appwidget_height"
        android:updatePeriodMillis="86400000"
        android:previewImage="@drawable/preview"
        android:initialLayout="@layout/appwidget_main"
        android:configure="com.android.gallery3d.gadget.WidgetConfigure"/>

       这个文件的作用是widget的属性。minWidth:widget的宽度,minHeight:高度,updatePeriodMillis:更新的时间周期,previewImage:widget的图标,initialLayout:初始widget的布局,configure: 如果需要在启动前先启动一个Activity进行设置,在这里给出Activity的完整类名。

       所以在创建时,会先走WidgetConfigure这个activity。看一下他的代码(稍后我会把代码附加到文章中)。

public class WidgetConfigure extends Activity {},在oncreate它有调用一个选择的activity,进行选择widget类型,然后设置widget。

   @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode != RESULT_OK) {
            setResult(resultCode, new Intent().putExtra(
                    AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId));
            finish();
            return;
        }

        if (requestCode == REQUEST_WIDGET_TYPE) {
            setWidgetType(data);
        } else if (requestCode == REQUEST_CHOOSE_ALBUM) {
            setChoosenAlbum(data);
        } else if (requestCode == REQUEST_GET_PHOTO) {
            setChoosenPhoto(data);
        } else if (requestCode == REQUEST_CROP_IMAGE) {
            setPhotoWidget(data);
        } else {
            throw new AssertionError("unknown request: " + requestCode);
        }
    }

选择单个图片时,会走setPhotoWidget(data);这个方法:

    private void setPhotoWidget(Intent data) {
        // Store the cropped photo in our database
        Bitmap bitmap = (Bitmap) data.getParcelableExtra("data");
        WidgetDatabaseHelper helper = new WidgetDatabaseHelper(this);
        try {
            helper.setPhoto(mAppWidgetId, mPickedItem, bitmap);
            updateWidgetAndFinish(helper.getEntry(mAppWidgetId));
        } finally {
            helper.close();
        }
    }

这个方法里,进行了数据的保存和调用设置widget。

    private void updateWidgetAndFinish(WidgetDatabaseHelper.Entry entry) {
        AppWidgetManager manager = AppWidgetManager.getInstance(this);
        RemoteViews views = PhotoAppWidgetProvider.buildWidget(this, mAppWidgetId, entry);
        manager.updateAppWidget(mAppWidgetId, views);
        setResult(RESULT_OK, new Intent().putExtra(
                AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId));
        finish();
    }

这个方法里,就调用了上面的PhotoAppWidgetProvider中的方法。创建RemoteViews方法。最终会调用buildFrameWidget(context, id, entry);这个方法。

    static RemoteViews buildFrameWidget(Context context, int appWidgetId, Entry entry) {
        RemoteViews views = new RemoteViews(
                context.getPackageName(), R.layout.photo_frame);
        try {
            byte[] data = entry.imageData;
            Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
            views.setImageViewBitmap(R.id.photo, bitmap);
        } catch (Throwable t) {
            Log.w(TAG, "cannot load widget image: " + appWidgetId, t);
        }

        if (entry.imageUri != null) {
            try {
                Uri uri = Uri.parse(entry.imageUri);
                Intent clickIntent = new Intent(context, WidgetClickHandler.class)
                        .setData(uri);
                PendingIntent pendingClickIntent = PendingIntent.getActivity(context, 0,
                        clickIntent, PendingIntent.FLAG_CANCEL_CURRENT);
                views.setOnClickPendingIntent(R.id.photo, pendingClickIntent);
            } catch (Throwable t) {
                Log.w(TAG, "cannot load widget uri: " + appWidgetId, t);
            }
        }
        return views;
    }

这个方法就是设置的Remoteview的方法。文章开头的问题就是出现在这个方法里面。设置view的PendingIntent时的不同标记。看红色的字体,我给大家看一下他的另外几个标记:

public final class PendingIntent implements Parcelable {

    /**
     * Flag for use with {@link #getActivity}, {@link #getBroadcast}, and
     * {@link #getService}: this
     * PendingIntent can only be used once.  If set, after
     * {@link #send()} is called on it, it will be automatically
     * canceled for you and any future attempt to send through it will fail.
     */
    public static final int FLAG_ONE_SHOT = 1<<30;   //这个标记是此设置的PendingIntent只能点击一次。也就是点击一次后,就会失去作用
    /**
     * Flag for use with {@link #getActivity}, {@link #getBroadcast}, and
     * {@link #getService}: if the described PendingIntent does not already
     * exist, then simply return null instead of creating it.
     */
    public static final int FLAG_NO_CREATE = 1<<29;//这个标记是此设置的PendingIntent一次都不能点击,不会创建intent。
    /**
     * Flag for use with {@link #getActivity}, {@link #getBroadcast}, and
     * {@link #getService}: if the described PendingIntent already exists,
     * the current one is canceled before generating a new one.  You can use
     * this to retrieve a new PendingIntent when you are only changing the
     * extra data in the Intent; by canceling the previous pending intent,
     * this ensures that only entities given the new data will be able to
     * launch it.  If this assurance is not an issue, consider
     * {@link #FLAG_UPDATE_CURRENT}.
     */
    public static final int FLAG_CANCEL_CURRENT = 1<<28;//这个标记是此设置的PendingIntent,如果生成的相同Intent,那么最后一个管用,前面的都会被cancel掉。
    /**
     * Flag for use with {@link #getActivity}, {@link #getBroadcast}, and
     * {@link #getService}: if the described PendingIntent already exists,
     * then keep it but its replace its extra data with what is in this new
     * Intent.  This can be used if you are creating intents where only the
     * extras change, and don't care that any entities that received your
     * previous PendingIntent will be able to launch it with your new
     * extras even if they are not explicitly given to it.
     */
    public static final int FLAG_UPDATE_CURRENT = 1<<27;//这个标记是此设置的PendingIntent,生成的intent全部管用。

}

所以最开始遇到的问题,就是这个标记搞得鬼。只要把红色标记的部分改为FLAG_UPDATE_CURRENT这个就可以解决掉次问题。


此文章全部是自己的观念和理解。有什么不对的地方,请大家多多指正。代码全部都是谷歌的gallery2的原生代码。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值