你能帮我么?
WidgetProvider
public class WidgetProvider extends AppWidgetProvider { //Tag for Logging private static final String TAG = "Widget"; // String to be sent on Broadcast as soon as Data is Fetched // should be included on WidgetProvider manifest intent action // to be recognized by this WidgetProvider to receive broadcast public static final String DATA_FETCHED = "mypackage.DATA_FETCHED"; public static final String EXTRA_LIST_VIEW_ROW_NUMBER = "mypackage.EXTRA_LIST_VIEW_ROW_NUMBER"; public static final String WIDGET_BUTTON = "mypackage.WIDGET_BUTTON"; /* * this method is called every 30 mins (30 min =30x60x1000) as specified on widgetinfo.xml this * method is also called on every phone reboot from this method nothing is * updated right now but instead RetmoteFetchService class is called this * service will fetch data,and send broadcast to WidgetProvider this * broadcast will be received by WidgetProvider onReceive which in turn * updates the widget */ @Override public void onUpdate(Context ctxt, AppWidgetManager appWidgetManager, int[] appWidgetIds) { Log.d(TAG, "Hello WidgetProvider onUpdate"); /* * int[] appWidgetIds holds ids of multiple instance of your widget * meaning you are placing more than one widgets on your homescreen */ for (int i = 0; i < appWidgetIds.length; ++i) { // Create an Intent for on click refresh Intent intent = new Intent(WIDGET_BUTTON); PendingIntent pendingIntent = PendingIntent.getBroadcast(ctxt, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); // Get the layout for the App Widget and attach an on-click listener // to the button RemoteViews remoteViews = new RemoteViews(ctxt.getPackageName(), R.layout.widget_layout); remoteViews.setOnClickPendingIntent(R.id.refresh_widget, pendingIntent); // Tell the AppWidgetManager to perform an update on the current app widget appWidgetManager.updateAppWidget(appWidgetIds[i], remoteViews); //start RemoteFetchService to parse XML in AsyncTask Intent serviceIntent = new Intent(ctxt, RemoteFetchService.class); serviceIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]); ctxt.startService(serviceIntent); } super.onUpdate(ctxt, appWidgetManager, appWidgetIds); } protected PendingIntent getPendingSelfIntent(Context context, String action) { Intent intent = new Intent(context, getClass()); intent.setAction(action); return PendingIntent.getBroadcast(context, 0, intent, 0); } /* * It receives the broadcast as per the action set on intent filters on * Manifest.xml once data is fetched from RemoteFetchService,it sends * broadcast and WidgetProvider notifies to change the data the data change * right now happens on ListProvider as it takes RemoteFetchService * listItemList as data */ @SuppressWarnings("deprecation") @Override public void onReceive(Context context, Intent intent) { super.onReceive(context, intent); Log.d(TAG, "Hello WidgetProvider onReceive"); // if RSS Feed was parsed in RemoteFetchService.java if (intent.getAction().equals(DATA_FETCHED)) { Log.d(TAG, "Data fetched in Widget Provider in OnReceive"); int appWidgetId = intent.getIntExtra( AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); AppWidgetManager appWidgetManager = AppWidgetManager .getInstance(context); // which layout to show on widget RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_layout); // RemoteViews Service needed to provide adapter for ListView Intent svcIntent = new Intent(context, WidgetService.class); // passing app widget id to that RemoteViews Service svcIntent .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); // setting a unique Uri to the intent svcIntent.setData(Uri.parse(svcIntent .toUri(Intent.URI_INTENT_SCHEME))); // setting adapter to listview of the widget remoteViews.setRemoteAdapter(appWidgetId, R.id.listViewWidget, svcIntent); // setting an empty view in case of no data remoteViews.setEmptyView(R.id.listViewWidget, R.id.empty_view); // onclick item listview // This section makes it possible for items to have individualized // behavior. // It does this by setting up a pending intent template. Individuals // items of a collection // cannot set up their own pending intents. Instead, the collection // as a whole sets // up a pending intent template, and the individual items set a // fillInIntent // to create unique behavior on an item-by-item basis. Intent toastIntent = new Intent(context, WidgetProvider.class); // Set the action for the intent. // When the user touches a particular view, it will have the effect // of // broadcasting TOAST_ACTION. toastIntent.setAction(WidgetProvider.EXTRA_LIST_VIEW_ROW_NUMBER); toastIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME))); PendingIntent toastPendingIntent = PendingIntent.getBroadcast( context, 0, toastIntent, PendingIntent.FLAG_UPDATE_CURRENT); remoteViews.setPendingIntentTemplate(R.id.listViewWidget, toastPendingIntent); appWidgetManager.updateAppWidget(appWidgetId, remoteViews); //appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.listViewWidget); } // if item on list was clicked if (intent.getAction().equals(EXTRA_LIST_VIEW_ROW_NUMBER)) { Log.d(TAG, "List Item Clicked in OnReceive in Widget Provider"); int appWidgetId = intent.getIntExtra( AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); AppWidgetManager appWidgetManager = AppWidgetManager .getInstance(context); // get position on listview which was clicked int position = intent.getIntExtra(EXTRA_LIST_VIEW_ROW_NUMBER, 0); // get RSSFeed RSSFeed feed = ListItem.Feed; Intent toastIntent = new Intent(context, ItemDetailActivity.class); toastIntent.setAction(WidgetProvider.EXTRA_LIST_VIEW_ROW_NUMBER); toastIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); /* * Toast.makeText(context, "Clicked on position :" + viewIndex, * Toast.LENGTH_SHORT).show(); */ // start ItemDetailActivity Intent detailIntent = new Intent(context, ItemDetailActivity.class); detailIntent.putExtra("pos", position); detailIntent.putExtra("feed", feed); detailIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(detailIntent); } // if refresh button was pressed if (WIDGET_BUTTON.equals(intent.getAction())) { Log.d(TAG, "Refresh Button Clicked in OnReceive in Widget Provider"); AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); int appWidgetIds[] = appWidgetManager.getAppWidgetIds( new ComponentName(context, WidgetProvider.class)); appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.listViewWidget); } else { Log.d(TAG, "No Data fetched in Widget Provider"); } } }
RemoteFetchService
public class RemoteFetchService extends Service { //Tag for Logging private static final String TAG = "Widget"; private int appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; RSSFeed feed; public Date pDate; public static ArrayList<ListItem> listItemList; @Override public IBinder onBind(Intent arg0) { return null; } /* * Retrieve appwidget id from intent it is needed to update widget later * start Async Task */ @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d(TAG, "Hello RemoteFetchService onStartCommand"); if (intent.hasExtra(AppWidgetManager.EXTRA_APPWIDGET_ID)) appWidgetId = intent.getIntExtra( AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); // fetchDataFromWeb(); new AsyncLoadXMLFeed().execute(); return super.onStartCommand(intent, flags, startId); } /** * AsyncTask which parses the xml and post it **/ public class AsyncLoadXMLFeed extends AsyncTask<Void, Void, RSSFeed> { protected RSSFeed doInBackground(Void... params) { try { Log.d(TAG, "Starte Parsing von URL in Widget"); // Obtain feed DOMParser myParser = new DOMParser(); feed = myParser.parseXml("http://www.test.de/feed"); Log.d(TAG, "ItemCount Parser in Widget: " + feed.getItemCount()); return feed; } catch (Exception e) { Log.d(TAG, "Exception beim Parsen: " + e.toString()); return null; } } @Override protected void onPostExecute(RSSFeed parsed_feed) { // super.onPostExecute(result); Log.d(TAG, "Async Parse fertig"); listItemList = new ArrayList<ListItem>(); if (feed != null) { try { int length = feed.getItemCount(); for (int i = 0; i < length; i++) { String date = calc_date_difference(feed, i); final ListItem listItem = new ListItem(); ListItem.Feed = feed; listItem.heading = feed.getItem(i).getTitle(); listItem.pubDate = date; Log.d(TAG+" Heading", feed.getItem(i).getTitle()); Log.d(TAG+" PubDate", date); listItemList.add(listItem); } } catch (Exception e) { Log.d(TAG, "Exception onPostExecute: " + e.toString()); } } else { Log.d(TAG, "Feed in onPostExecute ist null"); } // start intent and broadcast WidgetProvider, that data is fetched Intent widgetUpdateIntent = new Intent(); widgetUpdateIntent.setAction(WidgetProvider.DATA_FETCHED); widgetUpdateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); sendBroadcast(widgetUpdateIntent); // stop service stopSelf(); } } /** * Method to calculate the time difference **/ public String calc_date_difference(RSSFeed feed, int pos) { // calculate the time difference to the actual system time String pubDate = feed.getItem(pos).getDate(); SimpleDateFormat df = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.ENGLISH); try { try { pDate = df.parse(pubDate); } catch (java.text.ParseException e) { e.printStackTrace(); } pubDate = "Vor " + DateUtils.getDateDifference(pDate); return pubDate; } catch (ParseException e) { Log.e(TAG, "Error parsing date.."); return null; } } }
ListProvider
/** * If you are familiar with Adapter of ListView,this is the same as adapter with * few changes * */ public class ListProvider implements RemoteViewsFactory { // Tag for Logging private static final String TAG = "Widget"; private RemoteViews views; private Context ctxt = null; private int appWidgetId; private ArrayList<ListItem> listItemList = new ArrayList<ListItem>(); public ImageLoader imageLoader; private Bitmap bmp; public ListProvider(Context ctxt, Intent intent) { this.ctxt = ctxt; appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); if (RemoteFetchService.listItemList != null) listItemList = (ArrayList<ListItem>) RemoteFetchService.listItemList .clone(); else listItemList = new ArrayList<ListItem>(); } @Override public void onCreate() { // no-op Log.d(TAG, "Hello ListProvider onCreate"); } @Override public void onDestroy() { // no-op } @Override public int getCount() { return listItemList.size(); } /* * Similar to getView of Adapter where instead of Viewwe return RemoteViews */ @Override public RemoteViews getViewAt(int position) { final RemoteViews remoteView = new RemoteViews(ctxt.getPackageName(), R.layout.widget_row); ListItem listItem = listItemList.get(position); remoteView.setTextViewText(R.id.heading, listItem.heading); remoteView.setTextViewText(R.id.pubDate, listItem.pubDate); // onclick item listview Intent fillInIntent = new Intent(); fillInIntent.putExtra(WidgetProvider.EXTRA_LIST_VIEW_ROW_NUMBER, position); remoteView.setOnClickFillInIntent(R.id.heading, fillInIntent); return remoteView; } @Override public RemoteViews getLoadingView() { return (null); } @Override public int getViewTypeCount() { return (1); } @Override public long getItemId(int position) { return (position); } @Override public boolean hasStableIds() { return (true); } @Override public void onDataSetChanged() { // This code is executed if the refresh button is pressed or after the // update period // start RemoteFetchService to parse XML in AsyncTask Intent serviceIntent = new Intent(ctxt, RemoteFetchService.class); serviceIntent .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); ctxt.startService(serviceIntent); } }
widget_provider.xml
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:minHeight="200dp" android:minWidth="200dp" android:updatePeriodMillis="30000" android:initialLayout="@layout/widget_layout" android:autoAdvanceViewId="@+id/words" android:previewImage="@drawable/widget_preview" android:resizeMode="vertical|horizontal" />
我希望你需要帮助我.
在我给出你的问题的潜在答案之前,这里有一些想法如何改进代码(注意它们与答案有关,因为它们有助于防止错误发生).的onReceive
当您实现onReceive()时,您必须记住,appWidgetIds可以作为带有AppWidgetManager.EXTRA_APPWIDGET_ID的int传递,也可以作为带有AppWidgetManager.EXTRA_APPWIDGET_IDS的int数组传递(您自己使用这两种方法).
要解决这个问题,我通常会这样做:
@Override public void onReceive(Context context, Intent intent) { Bundle extras = intent.getExtras(); int invalidId = AppWidgetManager.INVALID_APPWIDGET_ID; int appWidgetId = extras != null ? extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, invalidId) : invalidId ; int[] appWidgetIds = extras != null ? extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS) : null; // single appWidgetId as parameter if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) { appWidgetIds = new int[] {appWidgetId}; } // multiple appWidgetIds as parameter if (appWidgetIds != null) { for (int id:appWidgetIds) { // process a single appWidgetId onReceiveInternal(context, intent, id) } } super.onReceive(context, intent); } private boolean onReceiveInternal(Context context, Intent intent, int appWidgetId) { // do your stuff for one appWidgetId }
这会阻止你这样做:
if (WIDGET_BUTTON.equals(intent.getAction())) { AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); int appWidgetIds[] = appWidgetManager.getAppWidgetIds(new ComponentName(context, WidgetProvider.class)); appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.listViewWidget); }
如果您收到单个appWidgetId的广播,则不应更新所有窗口小部件实例.为什么不?因为所有窗口小部件实例都将接收相同的广播,并且每个实例将为其他实例调用notifyAppWidgetViewDataChanged.如果用户在主屏幕上放置了4个小部件实例,则按下刷新按钮将触发16次(sic!)更新.
的onUpdate
处理DATA_FETCHED广播时,您要更新窗口小部件.现在,与onUpdate()相比,为什么你会使用不同的代码呢?他们应该真的这样做,所以我建议使用以下模式:
private void onReceiveInternal(Context context, Intent intent, int appWidgetId) { AppWidgetManager appWidgetMgr = AppWidgetManager.getInstance(context); String action = intent.getAction(); if (DATA_FETCHED.equals(action)) { onUpdateInternal(context, appWidgetMgr, appWidgetId); } // more code here } @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { for (int appWidgetId : appWidgetIds) { onUpdateInternal(context, appWidgetManager, appWidgetId); startFetchService(context, appWidgetId); // here we start the service to fetch the feed } super.onUpdate(context, appWidgetManager, appWidgetIds); } private void onUpdateInternal(Context context, AppWidgetManager appWidgetManager, int appWidgetId) { // here goes the update code }
这样做可以保证更新窗口小部件的功能相同,无论是Android触发的更新还是源数据进入时触发的更新.我知道您希望在第一种情况下启动提取服务(可以很容易地实现我的代码)上面的示例显示),但实际的ui更新应该做同样的事情.
现在你没有创建相同的RemoteViews.如果它是Android触发的更新,那么您基本上会夸大布局并为刷新按钮添加Intent,而Feed后的更新使用RemoteViewsService为每个列表项创建单独的Intent.这种方法的问题在于Android更新会破坏单个Intent,直到新的feed进入,并且从用户的角度来看,widget的行为将是不稳定的.通过我的方法,显示和行为将是一致的.
表现
请确保在WidgetProvider接收器的定义中包含带有正确操作的标签,否则您将不会收到任何更新(我猜您已经拥有该更新或您的小部件根本不会显示任何数据):
<receiver android:name="mypackage.WidgetProvider"> <intent-filter> <action android:name="mypackage.DATA_FETCHED" /> <action android:name="mypackage.EXTRA_LIST_VIEW_ROW_NUMBER" /> <action android:name="mypackage.WIDGET_BUTTON" /> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/widget_provider" /> </receiver>
一个答案
你没有发布完整的代码,所以我无法运行它.没有它,我只能确定哪个部分肯定会失败,但我可能会错过其他可能无效的部分.因此,我宣布这是“一个答案”.
在收到DATA_FETCHED广播后创建RemoteView时,您没有将Intent设置为刷新按钮(您在onUpdate中执行但在处理DATA_FETCHED时未调用该按钮 – >因此我建议使用相同的更新代码这可以防止这样的错误发生).如果没有Intent按下,刷新按钮将不会执行任何操作.
当然,对于定期更新,其他答案是正确的,根据这个,每隔30分钟发生一次:http://developer.android.com/reference/android/appwidget/AppWidgetProviderInfo.html#updatePeriodMillis
如果您需要更频繁的更新,请使用AlarmManager(在SO上搜索代码示例).