Finally i have sometimes to update my blog since i have left from my old company.
Recently my colleague and i have developed an android widget. We found that the widget become “no response” upon clicking on the widget after the android device configuration has been changed.
When you placed the widget on the home-screen, everything will be fine when clicking on it, i can see the i am able to receive the log from the source code which handling the expected intent for clicking. But now you change the orientation from portrait to landscape, no intent action is able to recieve.
I have then written an example for testing why it would happen. The example is a very simple widget which registered the pendingIntent for the button during the widget onUpdate, and then spawn a thread which update the text of widget to random integer for each 2 seconds using RemoteViews object. (shown in the below screen capture)
1. So let see the source codes to find out the root cause of failure (no response). First we need to android widget class which extends AppWidgetProvider as follwing.
public class TestWidget extends AppWidgetProvider {
/* The tag for logging */
public static final String LOG_TAG = "TWIKY-WIDGET";
/* The intent action */
public static final String ACTION_TEST_CLICK = "org.twiky.test.click";
..
}
2.Then during method onUpdate, we register out pendingIntent to the widget button so that we can recieve the clicking intent from that button. This is done by making a RemoteViews object with the target widget layout, applies #setOnClickPendingIntent and then use appWidgetManager#updateAppWidget to populate the updated view to the device screen.
Additionally a thread is spawned for each widget to update the text of the button. It create another RemoteViews pointing to the widget layout, setting the button text by using RemoteViews#setTextViewText, and then updating the view again using appWidgetManager#updateAppWidget.
@Override
public void onUpdate(
final Context ctx,
final AppWidgetManager appWidgetManager,
final int[] aiAppWidgetIds)
{
Log.d(LOG_TAG, "#onUpdate: " + Arrays.toString(aiAppWidgetIds));
super.onUpdate(ctx, appWidgetManager, aiAppWidgetIds);
final String sPackageName = ctx.getPackageName();
// Connect remote view
RemoteViews oRemoteViews = new RemoteViews(sPackageName, R.layout.widget);
// Pending intent on-click
oRemoteViews.setOnClickPendingIntent(R.id.btn_test,
this.createOnClickPendingIntent(ctx));
for (int iAppWidgetId: aiAppWidgetIds){
// Update the remote views
appWidgetManager.updateAppWidget(iAppWidgetId, oRemoteViews);
// Spawn a thread for updating the widget views.
// Note that [We don't create any pending intent again to the target view].
//
final int iAppWidgetIdF = iAppWidgetId;
Thread oThread = new Thread(
new Runnable(){
private int m_iCount;
private final Random m_oSeed = new Random();
public void run(){
try {
while(true){
RemoteViews oRemoteViews = new RemoteViews(ctx.getPackageName(), R.layout.widget);
// Once un-comment the following code, the widget
// will always responds correctly when you click
// on the button.
//
//oRemoteViews.setOnClickPendingIntent(R.id.btn_test, createOnClickPendingIntent(ctx));
oRemoteViews.setTextViewText(
R.id.btn_test, String.valueOf(this.m_oSeed.nextInt(100)));
Log.d(LOG_TAG, "Updating the remote views: " + iAppWidgetIdF + " count:" + this.m_iCount);
Thread.sleep(2000);
appWidgetManager.updateAppWidget(iAppWidgetIdF, oRemoteViews);
this.m_iCount++;
}
} catch (InterruptedException iex){
iex.printStackTrace();
}
}
}
);
oThread.start();
g_hThreadMap.put(iAppWidgetId, oThread);
}
}
So when the application is deployed to my mobile device, everything works fine and the widget is able to receive from appWidgetProvider#onReceive when i click on the button. (It shows a Toast and a log from logcat when clicking on it)
public void onReceive(
final Context ctx,
final Intent intent)
{
Log.d(LOG_TAG, "#onReceive: " + intent);
super.onReceive(ctx, intent);
String sAction = intent.getAction();
if (sAction.equals(ACTION_TEST_CLICK)){
// This indicate the pending intent works fine when we are able
// to receive the expected intent.
//
Log.d(LOG_TAG, "Button clicked [logcat] ");
Toast.makeText(ctx, "Button clicked [pendeing intent]", 100).show();
}
}
It seemed everything works fine and i can continue my work. But when the device orientation changes, the widget can’t receive any intent firing from the button. I.e. no more toast on my home screen.
After dicussing and investigating with my colleague, the issue seemed to be mentioned in latest Android API
Set the RemoteViews to use for the specified appWidgetIds. Note that the RemoteViews parameter will be cached by the AppWidgetService, and hence should contain a complete representation of the widget.
The RemoteViews passed into appWidgetManager must be the complete representation of the widget you want to present. I.e. In case you want to update the view, you must either “reference to the RemoteView that used before” Or “creating A new remote views which included all changes from beginning”. In this case, the RemoteViews object used when updating the display text of button in the spawned thread “”MUST include the pendingIntent registration again for each consecutive update”". Otherwise when the device orientation changes, the app widget manager re-loads the latest RemoteViews from the views cache (those cache are views without setting up any pendingIntent). So the result is obvious, the widget become no response on when clicking on it.
Solution
The solution has been mentioned already, when you need update RemoteViews, make sure the views included all pendingIntent registration. In the above code, just uncomment the following and the widget will always click-able.
oRemoteViews.setOnClickPendingIntent(R.id.btn_test, createOnClickPendingIntent(ctx));
The complete source code
You can access the complete eclipse project for this issue under Google project hosting:
http://twiky.googlecode.com/svn/branches/widgetUpdatingTraps












Follow this link via RSS