Android 小部件 getViewAt 调用次数过多

Android Widget getViewAt called too many times

我正在开发的小部件有问题。这是一个由内容提供者支持的简单列表视图。问题是方法 getViewAt() 被位置 0 调用了两次。这是代码和日志。

小工具 Xml 提供商:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="120dp"
android:minHeight="160dp"
android:updatePeriodMillis="86400000"
android:initialLayout="@layout/calendar_appwidget"
android:resizeMode="horizontal|vertical"
android:widgetCategory="home_screen|keyguard">

小部件服务

...
@Override
public RemoteViewsFactory onGetViewFactory(Intent intent) {
    return new CalendarRemoteViewsFactory(this.getApplicationContext(),intent);
}
...

工厂

    @Override
    public void onCreate() {

        readDateFormat(mContext);
        simpleDateFormat = new SimpleDateFormat(datePattern, Locale.US);

        mCal = Calendar.getInstance();


    }

    @Override
    public RemoteViews getViewAt(int i) {

        RemoteViews rv = new RemoteViews(mContext.getPackageName(),R.layout.calendar_widget_item);

        Log.d("Widgets","Get view at "+i);

        UpcomingShow show = (UpcomingShow)items.get(i);

        mCal.setTime(getShowDay(show.getAirs_at()));
        int dayOfMonth = mCal.get(Calendar.DAY_OF_MONTH);

        if (mLastDay != dayOfMonth){
            mLastDay = dayOfMonth;
            rv.setTextViewText(R.id.day,String.valueOf(dayOfMonth));
            rv.setViewVisibility(R.id.day, View.VISIBLE);
        }else
            rv.setViewVisibility(R.id.day, View.INVISIBLE);

        rv.setTextViewText(R.id.title, show.getShow().getTitle());

        try {
            Date showDate = loadDate(show.getAirs_at(), datePattern);

            simpleDateFormat.applyPattern(timeFormat == Utils.TimeFormat.CLOCK_24H ? "k:mm" : "h:mm a");
            simpleDateFormat.setTimeZone(mTimeZone);
            rv.setTextViewText(R.id.date,simpleDateFormat.format(showDate) + " on " + show.getShow().getNetwork());

        } catch (Exception e) {
            e.printStackTrace();
        }

        //Episode
        String episodeStr = show.getEpisode().getSeason() + "x" + show.getEpisode().getNumber() + " " + show.getEpisode().getTitle();
        rv.setTextViewText(R.id.episode,Utils.getTextInBold(episodeStr, 0, episodeStr.indexOf(" "), Spanned.SPAN_INCLUSIVE_EXCLUSIVE));

        return rv;
    }

    @Override
    public int getCount() {
        return items != null ? items.size() : 0;
    }

    @Override
    public RemoteViews getLoadingView() {
        return null;
    }

    @Override
    public int getViewTypeCount() {
        return 1;
    }

    @Override
    public long getItemId(int i) {
        return 0;
    }

    @Override
    public void onDataSetChanged() {

        final long identityToken = Binder.clearCallingIdentity();

        pos = 0;

        Log.d("Widgets","OnDataSetChanged");
        String selection = DataProviderContract.Upcoming.COLUMN_AIRS_AT + " >= " + mCal.getTimeInMillis();
        try {
            mCursor = mContext.getContentResolver().query(
                    DataProviderContract.Upcoming.CONTENT_URI,
                    null,
                    selection,
                    null,
                    DataProviderContract.Upcoming.COLUMN_AIRS_AT + " ASC "
            );

            if (items!=null && items.size()>0)
                items.clear();

            items = getShowsFromCursor(mCursor);

            if (mCursor!=null && !mCursor.isClosed())
                mCursor.close();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            Binder.restoreCallingIdentity(identityToken);
        }



    }

小部件提供程序

public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
    final int N = appWidgetIds.length;


    // Perform this loop procedure for each App Widget that belongs to this provider
    for (int i=0; i<N; i++) {

        // Set up the intent that starts the StackViewService, which will
        // provide the views for this collection.
        Intent intent = new Intent(context, CalendarWidgetService.class);
        // Add the app widget ID to the intent extras.
        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]);
        intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));


        // Instantiate the RemoteViews object for the app widget layout.
        RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.calendar_appwidget);
        // Set up the RemoteViews object to use a RemoteViews adapter.
        // This adapter connects
        // to a RemoteViewsService  through the specified intent.
        // This is how you populate the data.
        rv.setRemoteAdapter(appWidgetIds[i], R.id.list, intent);

        //Calls update
        appWidgetManager.updateAppWidget(appWidgetIds[i], rv);

    }

    super.onUpdate(context, appWidgetManager, appWidgetIds);
}

Logcat

03-26 10:02:54.011    1781-1792/com.D/Widget﹕ Acaba o parse com: 5
03-26 10:02:54.019    1781-1791/com.D/Widgets﹕ Get view at 0
03-26 10:02:54.027    1781-1792/com.D/Widgets﹕ Get view at 0
03-26 10:02:54.031    1781-1792/com.D/Widgets﹕ Get view at 1
03-26 10:02:54.031    1781-1792/com.D/Widgets﹕ Get view at 2
03-26 10:02:54.035    1781-1792/com.D/Widgets﹕ Get view at 3
03-26 10:02:54.039    1781-1792/com.D/Widgets﹕ Get view at 4

PS: 小部件布局

<?xml version="1.0" encoding="utf-8"?>
<ListView
xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@color/background"
android:dividerHeight="0dp"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/list"/>

如您所见,该方法被 pos 0 调用两次,有时会跳转位置,例如 getViewAt(1) -> getViewAt(3) -> getViewAt(2)。我不知道为什么会这样。我做错了什么。

使用 5.0.2、4.4.4、4.1.1 进行测试

谢谢

嗯,也许你应该让你的 getItemId() return 至少位置而不是全 0。但这可能不是你问题的原因。

事实上,我认为 Android 对调用 getViewAt() 的顺序或次数没有任何保证。这不是你的错。更糟糕的是,在我的生产应用程序中,有时我会遇到要求我的位置与我的尺寸相同的情况,所以我崩溃了。

所以我的建议是,您可以安全地忽略调用的顺序或次数,并且您应该防止错误的位置:检查对 getViewAt()getItemId() 的调用是否具有无效位置并使它们return 为空或 -1。

为了提高效率,ListView 调用这些位置没有特定的顺序,同一位置可以调用两次,或者 more.This 是预期的行为。