我正在使用小部件来显示RSS提要。显示了Feed但我无法通过单击按钮进行更新。在窗口小部件配置xml中设置更新周期后,列表也不会更新。
你能帮帮我吗?
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"
/>
我希望你需要帮助我。
答案 0 :(得分:6)
在我为您提供问题的潜在答案之前,以下是如何改进代码的一些想法(注意它们与答案相关,因为它们有助于防止错误发生)。
<强>的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接收器的定义中包含带有正确操作的标签,否则您将不会收到任何更新(我猜您已经拥有该更新或您的小部件根本不会显示任何数据):< / p>
<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上搜索代码示例)。
答案 1 :(得分:2)
虽然这不能解决您通过按钮更新的问题,但updatePeriodMillis
肯定无法正常工作。
根据AppWidgetProviderInfo文档,最短更新周期为30分钟(1800000毫秒)。您在XML中指定了仅30秒的更新周期。如果您在检查小部件之前没有等待整整30分钟,则不会看到任何新数据。
答案 2 :(得分:0)
The issue is:
您已在xml中指定更新时间仅为30秒
android:updatePeriodMillis="30000"
您需要等待整整30分钟才能使其正常工作。 请参阅链接了解详情