You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android开发:如何刷新Widget中的ListView?

Android Widget ListView 刷新问题解决方案

问题描述

我用Eclipse开发一款Android应用,包含一个带ListView的Widget,ListView每行都有按钮,用于将行项保存到SQLite数据库。需要实现两种场景下更新这个ListView:

  • 用户在主应用中更新列表时
  • 用户点击Widget上的刷新ImageView时

尝试过多种方案,但都不适用于带Service的ListView Widget,求解决办法。

相关代码

WidgetProvider.class

public class WidgetProvider extends AppWidgetProvider {

    DBHelper pdh;
    public static final String ACTION_TOAST = "actionToast";
    public static final String ACTION_BUTTON = "actionButton";
    public static final String EXTRA_ITEM_POSITION = "extraItemPosition";
    SimpleDateFormat sdfFinal = new SimpleDateFormat("yyyy-MM-dd");
    SimpleDateFormat stf = new SimpleDateFormat("HH:mm");

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

        for (int i = 0; i < N; ++i) {
            RemoteViews remoteViews = updateWidgetListView(context, appWidgetIds[i]);

            Intent intent = new Intent(context, MainActivity.class);
            PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);

            Intent clickIntentRefresh = new Intent(context, WidgetProvider.class);
            clickIntentRefresh.setAction(ACTION_BUTTON);
            
            PendingIntent clickPendingIntentRefresh = PendingIntent.getBroadcast(context, 0, clickIntentRefresh, 0);
            
            Intent clickIntent = new Intent(context, WidgetProvider.class);
            clickIntent.setAction(ACTION_TOAST);
            PendingIntent clickPendingIntent = PendingIntent.getBroadcast(context, 0, clickIntent, 0);

            remoteViews.setOnClickPendingIntent(R.id.ivRefresh,clickPendingIntentRefresh);
            remoteViews.setOnClickPendingIntent(R.id.button1, pendingIntent);
            remoteViews.setPendingIntentTemplate(R.id.listViewWidget, clickPendingIntent);
            appWidgetManager.updateAppWidget(appWidgetIds[i], remoteViews);

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

    @Override
    public void onReceive(Context context, Intent intent) {
        super.onReceive(context, intent);

        if (ACTION_TOAST.equals(intent.getAction())) {
            String s = intent.getStringExtra(EXTRA_ITEM_POSITION);

            pdh = new DBHelper(context);

            Calendar cal = Calendar.getInstance();

            String strDateStamp = sdfFinal.format(cal.getTime());
            String strTimeStamp = stf.format(cal.getTime());
            pdh.insertRecord(s, null, strDateStamp, strTimeStamp);

            Toast.makeText(context, "Saved!", Toast.LENGTH_SHORT).show();

        } else if (ACTION_BUTTON.equals(intent.getAction())) {

            Toast.makeText(context, "Refresh Clicked!", Toast.LENGTH_SHORT).show();

        }
    }

    private RemoteViews updateWidgetListView(Context context, int appWidgetId) {

        RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_layout);

        Intent svcIntent = new Intent(context, WidgetService.class);

        svcIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
        svcIntent.setData(Uri.parse(svcIntent.toUri(Intent.URI_INTENT_SCHEME)));

        remoteViews.setRemoteAdapter(R.id.listViewWidget, svcIntent);
        remoteViews.setEmptyView(R.id.listViewWidget, R.id.empty_view);

        return remoteViews;

    }
}

WidgetService.class

public class WidgetService extends RemoteViewsService {
    /*
     * So pretty simple just defining the Adapter of the listview here Adapter is
     * ListProvider
     */

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

ListProvider.class

public class ListProvider implements RemoteViewsFactory {

    public static final String EXTRA_ITEM_POSITION = "extraItemPosition";
    private ArrayList<ListItem> listItemList = new ArrayList<ListItem>();
    private Context context = null;
    private Intent intent;

    public ListProvider(Context context, Intent intent) {
        this.intent = intent;
        this.context = context;
        intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);

        populateListItem();
    }

    private void populateListItem() {
        try {
            DBHelper pdh = new DBHelper(context);

            listItemList.clear();
            
            SQLiteDatabase db = pdh.getReadableDatabase();
            String[] columns = new String[] { "Categories" };
            Cursor c = db.query("tblCategories", columns, null, null, null, null, "Categories asc");

            int categoriesInt = c.getColumnIndex("Categories");

            int totalCategories = c.getCount();

            for (int i = 0; i < totalCategories; i++) {
                c.moveToPosition(i);
                ListItem listItem = new ListItem();
                listItem.heading = c.getString(categoriesInt);
                listItemList.add(listItem);
            }

            c.close();
            db.close();
            pdh.close();
        } catch (Exception ex) {

        }

    }

    @Override
    public int getCount() {
        return listItemList.size();
    }

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

    /*
     * Similar to getView of Adapter where instead of View we return RemoteViews
     *
     */
    @Override
    public RemoteViews getViewAt(int position) {
        try {           
            final RemoteViews remoteView = new RemoteViews(context.getPackageName(), R.layout.list_row);
            ListItem listItem = (ListItem) listItemList.get(position);
            remoteView.setTextViewText(R.id.heading, listItem.heading);

            Intent fillIntent = new Intent();
            fillIntent.putExtra(EXTRA_ITEM_POSITION, listItem.heading);
            remoteView.setOnClickFillInIntent(R.id.button1, fillIntent);

            return remoteView;
        } catch (Exception ex) {
            return null;
        }
    }

    @Override
    public RemoteViews getLoadingView() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public int getViewTypeCount() {
        // TODO Auto-generated method stub
        return 1;
    }

    @Override
    public boolean hasStableIds() {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public void onCreate() {
        // TODO Auto-generated method stub

    }

    @Override
    public void onDataSetChanged() {
        // TODO Auto-generated method stub
        
    }

    @Override
    public void onDestroy() {
        // TODO Auto-generated method stub

    }
}

ListItem.class

public class ListItem {

    public String heading;
}

AndroidManifest.xml

<receiver android:name=".WidgetProvider" >
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>

    <meta-data
        android:name="android.appwidget.provider"
        android:resource="@xml/widgetinfo" />
</receiver>

<service
    android:name=".WidgetService"
    android:permission="android.permission.BIND_REMOTEVIEWS" />

解决方案

带RemoteViewsService的Widget列表刷新,核心是触发RemoteViewsFactory重新加载数据,并通知AppWidgetManager更新,分以下几步修改:

1. 实现刷新按钮的更新逻辑

修改WidgetProvider的onReceive方法中ACTION_BUTTON分支,触发ListView数据刷新:

else if (ACTION_BUTTON.equals(intent.getAction())) {
    Toast.makeText(context, "刷新中...", Toast.LENGTH_SHORT).show();
    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
    int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, WidgetProvider.class));
    // 通知ListView数据已变更,触发onDataSetChanged
    appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.listViewWidget);
    // 重新执行onUpdate确保Widget配置正确
    onUpdate(context, appWidgetManager, appWidgetIds);
}

2. 让ListProvider支持数据刷新

在ListProvider中实现onDataSetChanged()方法,重新从数据库加载数据:

@Override
public void onDataSetChanged() {
    // 重新获取最新数据
    populateListItem();
}

3. 主应用更新数据后通知Widget

在主应用中,当完成SQLite数据更新后,发送广播触发Widget更新:

// 主应用更新数据后调用此代码
Intent updateIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
ComponentName widgetComponent = new ComponentName(context, WidgetProvider.class);
updateIntent.setComponent(widgetComponent);

AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
int[] widgetIds = appWidgetManager.getAppWidgetIds(widgetComponent);
updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, widgetIds);

context.sendBroadcast(updateIntent);

同时,在WidgetProvider的onUpdate方法中,循环里添加数据变更通知:

// 在appWidgetManager.updateAppWidget之前添加
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds[i], R.id.listViewWidget);

4. 优化PendingIntent(避免缓存问题)

创建PendingIntent时添加FLAG_UPDATE_CURRENT(适配旧版本)或对应Android 12+的FLAG,防止Intent缓存:
比如刷新按钮的PendingIntent:

PendingIntent clickPendingIntentRefresh = PendingIntent.getBroadcast(context, 0, clickIntentRefresh, PendingIntent.FLAG_UPDATE_CURRENT);

5. 完善异常处理

在ListProvider的populateListItem方法中,不要空捕获异常,添加日志方便调试:

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

内容的提问来源于stack exchange,提问作者George T.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 03:50:28