如何创build一个configuration活动的应用程序小部件,并进行首次更新?

这真让我抓狂。 我不知道如何从configuration活动更新应用程序小部件,即使采用了推荐的做法。 为什么更新方法没有被调用的应用程序小部件的创build超出我的理解。

我想要的是:一个包含项目集合(带有列表视图)的应用部件。 但用户需要select一些东西,所以我需要一个configuration活动。

configuration活动是一个ListActivity

 @TargetApi(Build.VERSION_CODES.HONEYCOMB) public class ChecksWidgetConfigureActivity extends SherlockListActivity { private List<Long> mRowIDs; int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; private BaseAdapter mAdapter; @Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setResult(RESULT_CANCELED); setContentView(R.layout.checks_widget_configure); final Intent intent = getIntent(); final Bundle extras = intent.getExtras(); if (extras != null) { mAppWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); } // If they gave us an intent without the widget id, just bail. if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) { finish(); } mRowIDs = new ArrayList<Long>(); // it's actually loaded from an ASyncTask, don't worry about that — it works. mAdapter = new MyListAdapter((LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE)); getListView().setAdapter(mAdapter); } private class MyListAdapter extends BaseAdapter { // not relevant... } @Override protected void onListItemClick(final ListView l, final View v, final int position, final long id) { if (position < mRowIDs.size()) { // Set widget result final Intent resultValue = new Intent(); resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId); resultValue.putExtra("rowId", mRowIDs.get(position)); setResult(RESULT_OK, resultValue); // Request widget update final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this); ChecksWidgetProvider.updateAppWidget(this, appWidgetManager, mAppWidgetId, mRowIDs); } finish(); } } 

正如你所看到的,我正在从我的应用程序小部件提供程序调用一个静态方 我从官方文档中获得了这个想法。

让我们来看看我的提供者:

 @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) public class ChecksWidgetProvider extends AppWidgetProvider { public static final String TOAST_ACTION = "com.example.android.stackwidget.TOAST_ACTION"; public static final String EXTRA_ITEM = "com.example.android.stackwidget.EXTRA_ITEM"; @Override public void onUpdate(final Context context, final AppWidgetManager appWidgetManager, final int[] appWidgetIds) { super.onUpdate(context, appWidgetManager, 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++) { // Here we setup the intent which points to the StackViewService which will // provide the views for this collection. final Intent intent = new Intent(context, ChecksWidgetService.class); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]); // When intents are compared, the extras are ignored, so we need to embed the extras // into the data so that the extras will not be ignored. intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME))); final RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.checks_widget); rv.setRemoteAdapter(android.R.id.list, intent); // The empty view is displayed when the collection has no items. It should be a sibling // of the collection view. rv.setEmptyView(android.R.id.list, android.R.id.empty); // Here we setup the a pending intent template. Individuals items of a collection // cannot setup their own pending intents, instead, the collection as a whole can // setup a pending intent template, and the individual items can set a fillInIntent // to create unique before on an item to item basis. final Intent toastIntent = new Intent(context, ChecksWidgetProvider.class); toastIntent.setAction(ChecksWidgetProvider.TOAST_ACTION); toastIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]); toastIntent.setData(Uri.parse(toastIntent.toUri(Intent.URI_INTENT_SCHEME))); final PendingIntent toastPendingIntent = PendingIntent.getBroadcast(context, 0, toastIntent, PendingIntent.FLAG_UPDATE_CURRENT); rv.setPendingIntentTemplate(android.R.id.list, toastPendingIntent); appWidgetManager.updateAppWidget(appWidgetIds[i], rv); } } @Override public void onReceive(final Context context, final Intent intent) { final AppWidgetManager mgr = AppWidgetManager.getInstance(context); if (intent.getAction().equals(TOAST_ACTION)) { final int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); final long rowId = intent.getLongExtra("rowId", 0); final int viewIndex = intent.getIntExtra(EXTRA_ITEM, 0); Toast.makeText(context, "Touched view " + viewIndex + " (rowId: " + rowId + ")", Toast.LENGTH_SHORT).show(); } super.onReceive(context, intent); } @Override public void onAppWidgetOptionsChanged(final Context context, final AppWidgetManager appWidgetManager, final int appWidgetId, final Bundle newOptions) { updateAppWidget(context, appWidgetManager, appWidgetId, newOptions.getLong("rowId")); } public static void updateAppWidget(final Context context, final AppWidgetManager appWidgetManager, final int appWidgetId, final long rowId) { final RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.checks_widget); appWidgetManager.updateAppWidget(appWidgetId, views); } } 

这基本上是官方文档的复制/粘贴。 我们可以在这里看到我的静态方法。 假设现在它实际上使用了rowId

我们还可以看到另一个失败(见下文)尝试更新应用程序小部件,当我收到选项更改广播( onAppWidgetOptionsChanged )。

基于集合的应用程序小部件所需的Service几乎是文档的精确复制/粘贴:

 @TargetApi(Build.VERSION_CODES.HONEYCOMB) public class ChecksWidgetService extends RemoteViewsService { @Override public RemoteViewsFactory onGetViewFactory(final Intent intent) { return new StackRemoteViewsFactory(this.getApplicationContext(), intent); } } class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory { private static final int mCount = 10; private final List<WidgetItem> mWidgetItems = new ArrayList<WidgetItem>(); private final Context mContext; private final int mAppWidgetId; private final long mRowId; public StackRemoteViewsFactory(final Context context, final Intent intent) { mContext = context; mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); mRowId = intent.getLongExtra("rowId", 0); } @Override public void onCreate() { // In onCreate() you setup any connections / cursors to your data source. Heavy lifting, // for example downloading or creating content etc, should be deferred to onDataSetChanged() // or getViewAt(). Taking more than 20 seconds in this call will result in an ANR. for (int i = 0; i < mCount; i++) { mWidgetItems.add(new WidgetItem(i + " (rowId: " + mRowId + ") !")); } // We sleep for 3 seconds here to show how the empty view appears in the interim. // The empty view is set in the StackWidgetProvider and should be a sibling of the // collection view. try { Thread.sleep(3000); } catch (final InterruptedException e) { e.printStackTrace(); } } @Override public void onDestroy() { // In onDestroy() you should tear down anything that was setup for your data source, // eg. cursors, connections, etc. mWidgetItems.clear(); } @Override public int getCount() { return mCount; } @Override public RemoteViews getViewAt(final int position) { // position will always range from 0 to getCount() - 1. // We construct a remote views item based on our widget item xml file, and set the // text based on the position. final RemoteViews rv = new RemoteViews(mContext.getPackageName(), R.layout.widget_item); rv.setTextViewText(R.id.widget_item, mWidgetItems.get(position).text); // Next, we set a fill-intent which will be used to fill-in the pending intent template // which is set on the collection view in StackWidgetProvider. final Bundle extras = new Bundle(); extras.putInt(ChecksWidgetProvider.EXTRA_ITEM, position); final Intent fillInIntent = new Intent(); fillInIntent.putExtras(extras); rv.setOnClickFillInIntent(R.id.widget_item, fillInIntent); // You can do heaving lifting in here, synchronously. For example, if you need to // process an image, fetch something from the network, etc., it is ok to do it here, // synchronously. A loading view will show up in lieu of the actual contents in the // interim. try { Ld("Loading view " + position); Thread.sleep(500); } catch (final InterruptedException e) { e.printStackTrace(); } // Return the remote views object. return rv; } @Override public RemoteViews getLoadingView() { // You can create a custom loading view (for instance when getViewAt() is slow.) If you // return null here, you will get the default loading view. return null; } @Override public int getViewTypeCount() { return 1; } @Override public long getItemId(final int position) { return position; } @Override public boolean hasStableIds() { return true; } @Override public void onDataSetChanged() { // This is triggered when you call AppWidgetManager notifyAppWidgetViewDataChanged // on the collection view corresponding to this factory. You can do heaving lifting in // here, synchronously. For example, if you need to process an image, fetch something // from the network, etc., it is ok to do it here, synchronously. The widget will remain // in its current state while work is being done here, so you don't need to worry about // locking up the widget. } } 

最后,我的小部件布局:

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/widgetLayout" android:orientation="vertical" android:padding="@dimen/widget_margin" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/resizeable_widget_title" style="@style/show_subTitle" android:padding="2dp" android:paddingLeft="5dp" android:textColor="#FFFFFFFF" android:background="@drawable/background_pink_striked_transparent" android:text="@string/show_title_key_dates" /> <ListView android:id="@android:id/list" android:layout_marginRight="5dp" android:layout_marginLeft="5dp" android:background="@color/timeline_month_dark" android:layout_width="match_parent" android:layout_height="match_parent" /> <TextView android:id="@android:id/empty" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:textColor="#ffffff" android:textStyle="bold" android:text="@string/empty_view_text" android:textSize="20sp" /> </LinearLayout> 

我的android清单XML文件的相关部分:

 <receiver android:name="com.my.full.pkg.ChecksWidgetProvider"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/checks_widget_info" /> </receiver> <activity android:name="com.my.full.pkg.ChecksWidgetConfigureActivity"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" /> </intent-filter> </activity> <service android:name="com.my.full.pkg.ChecksWidgetService" android:permission="android.permission.BIND_REMOTEVIEWS" /> 

xml/checks_widget_info.xml

 <?xml version="1.0" encoding="utf-8"?> <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:minWidth="146dp" android:minHeight="146dp" android:updatePeriodMillis="86400000" android:initialLayout="@layout/checks_widget" android:configure="com.my.full.pkg.ChecksWidgetConfigureActivity" android:resizeMode="horizontal|vertical" android:previewImage="@drawable/resizeable_widget_preview" /> 

那么,怎么了? 那么,当我创build小部件是空的。 我的意思是无效。 空。 没有。 我没有在我的布局中定义的空视图! 我勒个去?

如果我重新安装应用程序或重新启动设备(或杀死启动器应用程序),应用程序窗口小部件实际上更新,并包含自动添加的10个项目,如在示例中。

configuration活动结束后我无法更新该死的东西。 从文档中提取的这个句子超出了我的意思:“ 创buildApp Widget时不会调用onUpdate()方法 – 它只是第一次跳过。

我的问题是:

  • 为什么在这个世界上,Android开发团队select不首次创build窗口小部件?
  • 如何在configuration活动完成之前更新我的应用程序小部件?

我不明白的另一件事是行动stream程:

  1. 使用最后编译的代码安装应用程序,在启动器上准备空间,从启动器打开“小部件”菜单
  2. select我的小部件,并将其放置到所需的区域
  3. 那一刻,我的应用程序小部件提供程序接收android.appwidget.action.APPWIDGET_ENABLED ,然后android.appwidget.action.APPWIDGET_UPDATE
  4. 然后我的应用程序小部件提供程序获取其onUpdate方法调用。 我预料到这发生在configuration活动完成后…
  5. 我的configuration活动已经开始。 但应用程序小部件似乎已经创build和更新,我不明白。
  6. 我从configuration活动中select项目: onListItemClick被调用
  7. 我的提供程序的静态updateAppWidget被调用,拼命尝试更新小部件。
  8. configuration活动设置其结果并结束。
  9. 提供者接收到android.appwidget.action.APPWIDGET_UPDATE_OPTIONS :好吧,创build时接收大小更新确实很有意义。 这就是我拼命更新updateAppWidget
  10. 我的提供程序onUpdate不被调用。 为什么??!!

最后:小部件是空的。 不是listview-empty或@android:id / empty-empty,确实是EMPTY 。 没有显示视图。 没有。
如果我再次安装应用程序,应用程序小部件将按照预期在列表视图中填充视图。
调整窗口小部件的大小没有影响。 它只是再次调用onAppWidgetOptionsChanged ,这没有效果。

我的意思是空的:应用程序的小部件布局充气,但列表视图不充气,空视图不显示。

Solutions Collecting From Web of "如何创build一个configuration活动的应用程序小部件,并进行首次更新?"

通过AppWidgetManager进行更新的缺点是,您必须提供从deviseangular度来看的RemoteViews,因为与RemoteView相关的逻辑应该封装在AppWidgetProvider中(或者在您的案例中RemoteViewsService.RemoteViewsFactory)。

SciencyGuy通过静态方法公开远程视图逻辑的方法是解决这个问题的方法之一,但是有一种更直接的方式将广播直接发送到小部件:

 Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE, null, this, ChecksWidgetProvider.class); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {mAppWidgetId}); sendBroadcast(intent); 

因此,将调用AppWidgetProvider的onUpdate()方法来为该小部件创buildRemoteView。

在configuration活动完成后,没有触发onUpdate方法是正确的。 这取决于您的configuration活动来执行初始更新。 所以你需要build立初始视图。

这是configuration结束时应该做的事情的要点:

 // First set result OK with appropriate widgetId Intent resultValue = new Intent(); resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); setResult(RESULT_OK, resultValue); // Build/Update widget AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(getApplicationContext()); // This is equivalent to your ChecksWidgetProvider.updateAppWidget() appWidgetManager.updateAppWidget(appWidgetId, ChecksWidgetProvider.buildRemoteViews(getApplicationContext(), appWidgetId)); // Updates the collection view, not necessary the first time appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.notes_list); // Destroy activity finish(); 

您已经正确设置了结果。 你调用ChecksWidgetProvider.updateAppWidget(),但是updateAppWidget()不会返回正确的结果。

当前的updateAppWidget()返回一个空的RemoteViews对象。 这就解释了为什么你的widget首先是完全空的。 你没有填充任何东西的观点。 我build议你将你的代码从onUpdate移动到静态的buildRemoteViews()方法,你可以从onUpdate和updateAppWidget()中调用它们:

 public static RemoteViews buildRemoteViews(final Context context, final int appWidgetId) { final RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.checks_widget); rv.setRemoteAdapter(android.R.id.list, intent); // The empty view is displayed when the collection has no items. It should be a sibling // of the collection view. rv.setEmptyView(android.R.id.list, android.R.id.empty); // Here we setup the a pending intent template. Individuals items of a collection // cannot setup their own pending intents, instead, the collection as a whole can // setup a pending intent template, and the individual items can set a fillInIntent // to create unique before on an item to item basis. final Intent toastIntent = new Intent(context, ChecksWidgetProvider.class); toastIntent.setAction(ChecksWidgetProvider.TOAST_ACTION); toastIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); toastIntent.setData(Uri.parse(toastIntent.toUri(Intent.URI_INTENT_SCHEME))); final PendingIntent toastPendingIntent = PendingIntent.getBroadcast(context, 0, toastIntent, PendingIntent.FLAG_UPDATE_CURRENT); rv.setPendingIntentTemplate(android.R.id.list, toastPendingIntent); return rv; } public static void updateAppWidget(final Context context, final AppWidgetManager appWidgetManager, final int appWidgetId) { final RemoteViews views = buildRemoteViews(context, appWidgetId); appWidgetManager.updateAppWidget(appWidgetId, views); } @Override public void onUpdate(final Context context, final AppWidgetManager appWidgetManager, final int[] appWidgetIds) { super.onUpdate(context, appWidgetManager, appWidgetIds); // Perform this loop procedure for each App Widget that belongs to this provider for (int appWidgetId: appWidgetIds) { RemoteViews rv = buildRemoteViews(context, appWidgetId); appWidgetManager.updateAppWidget(appWidgetIds[i], rv); } } 

这应该照顾小部件的初始化。

在我的示例代码中调用finish()之前的最后一步是更新集合视图。 正如评论所说,这是第一次没有必要。 但是,为了防止在添加小部件之后重新configuration小部件,以防万一。 在这种情况下,必须手动更新集合视图以确保加载相应的视图和数据。

我没有看到你的appwidgetprovider.xml和AndroidManifest.xml,但我的猜测是你没有正确设置你的configuration活动。

以下是如何做到这一点:

  1. 将以下属性添加到您的appwidgetprovider.xml中:

     <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" ... android:configure="com.full.package.name.ChecksWidgetConfigureActivity" ... /> 
  2. 你的configuration活动应该有一个适当的intent-filter

     <activity android:name=".ChecksWidgetConfigureActivity"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/> </intent-filter> </activity> 

如果configuration活动configuration正确, onUpdate()仅在完成后触发。