Version 2.2
Copyright © 2009, 2010, 2011, 2012 Lars Vogel
18.05.2012
| Revision History | ||
|---|---|---|
| Revision 0.1 | 01.09.2010 | Lars Vogel |
| Created | ||
| Revision 0.2 - 2.2 | 13.12.2010 - 18.05.2012 | Lars Vogel |
| bug fixes and enhancements | ||
Table of Contents
Widgets
are little applications which can be placed on the home
screen of your
Android device. An
Widget
gets its data on a periodic timetable. There are two methods
to update
a widget, one is based on an XML configuration
file and the other is
based on the Android
AlarmManager
service.
A
Widget
runs as part of the
homescreen
process. This requires that
Widgets
preserve the
permissions of their application.
Widgets
use
RemoteViews
to create there user interface. A
RemoteView
can be executed by another process with the same permissions as the
original application. This way the
Widget
runs with
the
permissions of its defining application.
The user interface for an
Widget
is defined by an
BroadcastReceiver. This
BroadcastReceiver
inflates its layout into an object of type
RemoteViews. This
RemoteViews
object is delivered to Android, which hands it over the HomeScreen
application.
To create a
Widget
you:
Define a layout file for your
Widget.
Maintain an XML file (
AppWidgetProviderInfo
) which describes the properties of the widget,
e.g. size or the
fixed
update frequency.
Create a
BroadcastReceiver
which will be used to build the user interface of the the
Widgets.
This receiver extends
the
AppWidgetProvider
class
which provides additional lifecycle hooks for
Widgets.
Maintain the App Widget configuration in the
AndroidManifest.xml
file.
You can also specify a configuration
Activity
which is called once a new instance instance of
the
widget
is added to the Android homescreen.
To register a widget you create a
BroadcastReceiver
which an intent filter for the action
android.appwidget.action.APPWIDGET_UPDATE. s
<receiver
android:icon="@drawable/icon"
android:label="Example Widget"
android:name="MyWidgetProvider" >
<intent-filter >
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_info" />
</receiver>
The
BroadcastReceiver
can get an label and and icon assigned. These are used in the list
of
available
Widgets.
You also specify the meta-data for the widget via the
android:name="android.appwidget.provider"
attribute. The configuration file referred to by this
meta-data
contains the configuration settings for the
<codee>widget</codee>. If contains for example the update interface, the size and
the
initial layout before the first
update()
call on the widget.
<?xml version="1.0" encoding="utf-8"?> <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:initialLayout="@layout/widget_layout" android:minHeight="72dp" android:minWidth="146dp" android:updatePeriodMillis="180000" > </appwidget-provider>
A widget is restricted in the elements it can use. For layouts you
can
use
FrameLayout,
LinearLayout
and
RelativeLayout. As views you can use
AnalogClock,
Button,
Chromometer,
ImageButton,
ImageView,
ProgressBar
and
TextView.
As of Android 3.0 more views are available:
GridView,
ListView,
StackView,
ViewFlipper
and
AdapterViewFlipper.
The only interaction with is possible on the
Views
of a
Widget
is via on
OnClickListener. This
OnClickListener
can be registered on a widget and is triggered by the user.
A
Widget
will take a certain amount of cells on the homescreen. A cell is
usually used to display the icon of one application. As a calculation
rule you should define the size of the widget with the formula:
((Number of columns / rows)* 74) - 2. These are device independent
pixels and the -2 is used to avoid rounding issues.
As of Android 3.1 a
Widgets
can be flexible in size, e.g. the
user can
make is larger or smaller.
To enable this for
Widgets
you can use the
android:resizeMode="horizontal|vertical"
atttribute in the XML configuration file for the widget.
In the widget configuration file you can specify a fixed update interval. The system will wake up after this time interval and call your broadcast receiver to udpate the widget. The smallest update interval is 180000 milliseconds (30 minutes).
The
AlarmManager
allows you
to be more resource efficient and to have a higher
frequency of
updates.
Here you see an example configuration file for a widget.
<?xml version="1.0" encoding="utf-8"?> <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:initialLayout="@layout/widget_layout" android:minHeight="72dp" android:minWidth="146dp" android:updatePeriodMillis="180000" > </appwidget-provider>
Your
BroadcastReceiver
extends
AppWidgetProvider. The
AppWidgetProvider
class implements the
onReceive()
method, extracts the required information and calls the
following
widget lifecycle methods.
As you can added several instances of a widget to the homescreen you have lifecycle methods which are called only for the first instance added / removed to the homescreen and others which are called for every instance of your widget.
Table 1. Lifecycle method
| Method | Description |
|---|---|
| onEnabled() | Called the first time an instance of your widget is added to the homescreen |
| onDisabled() | Called once the last instance of your widget is removed from the homescreen. |
| onUpdate() | Called for every update of the widget. Contains the ids of appWidgetIds for which an update is needed. Note that this may be all of the AppWidget instances for this provider, or just a subset of them, as stated in the methods JavaDoc. For example if more than one widget is added to the homescreen, only the last one changes (until reinstall). |
| onDeleted() | Widget instance is removed from the homescreen |
All long running operations in these methods should be performed
in a
service, as the execution time for a broadcast receiver is
limited.
Using
asynchronous processing
in the
onReceive()
method
does not
help as the system can
kill the
broadcast process after
his
onReceive()
method.
The following description assume that you have already experience in building standard Android application. Please see Android Tutorial . It also uses partly Android services. You find an introduction into Android Services in Android Service Tutorial .
The following will create a simple example for a widget. We will
create a widget which displays a random number. This random number
will updated automatically
every 30 minutes. We will also register a
OnClickListener
so that the widgets update if the user click on it.
The resulting widget will look like the following.

Create a new Android project
de.vogella.android.widget.example
with no
Activity
in the package
de.vogella.android.widget.example.
Create a new file
myshape.xml
under the path "/res/drawable-mdpi". This file will define the
background we
use
in your widget.
<?xml version="1.0" encoding="UTF-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" > <stroke android:width="2dp" android:color="#FFFFFFFF" /> <gradient android:angle="225" android:endColor="#DD2ECCFA" android:startColor="#DD000000" /> <corners android:bottomLeftRadius="7dp" android:bottomRightRadius="7dp" android:topLeftRadius="7dp" android:topRightRadius="7dp" /> </shape>
Define the following
widget_layout.xml
file under the folder "res/layout".
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/layout" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="8dip" android:background="@drawable/myshape" > <TextView android:id="@+id/update" style="@android:style/TextAppearance.Medium" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="center" android:gravity="center_horizontal|center_vertical" android:layout_margin="4dip" android:text="Static Text" > </TextView> </LinearLayout>
Create the
AppWidgetProvider
metadata file "widget_info.xml", via
→ → →


<?xml version="1.0" encoding="utf-8"?> <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:initialLayout="@layout/widget_layout" android:minHeight="72dp" android:minWidth="300dp" android:updatePeriodMillis="300000" > </appwidget-provider>
Create the
BroadcastReeceiver
which will be called for updates. We also register an
OnClickListener
on the view of the
PendingIntent.
package de.vogella.android.widget.example; import java.util.Random; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.util.Log; import android.widget.RemoteViews; public class MyWidgetProvider extends AppWidgetProvider { private static final String ACTION_CLICK = "ACTION_CLICK"; @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { // Get all ids ComponentName thisWidget = new ComponentName(context, MyWidgetProvider.class); int[] allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget); for (int widgetId : allWidgetIds) { // Create some random data int number = (new Random().nextInt(100)); RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_layout); Log.w("WidgetExample", String.valueOf(number)); // Set the text remoteViews.setTextViewText(R.id.update, String.valueOf(number)); // Register an onClickListener Intent intent = new Intent(context, MyWidgetProvider.class); intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); remoteViews.setOnClickPendingIntent(R.id.update, pendingIntent); appWidgetManager.updateAppWidget(widgetId, remoteViews); } } }
Open
AndroidManifest.xml
and maintain the following.
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="de.vogella.android.widget.example" android:versionCode="1" android:versionName="1.0" > <application android:icon="@drawable/icon" android:label="@string/app_name" > <receiver android:name="MyWidgetProvider" > <intent-filter > <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/widget_info" /> </receiver> </application> <uses-sdk android:minSdkVersion="8" /> </manifest>
This attribute specifies that the
AppWidgetProvider
accepts the
ACTION_APPWIDGET_UPDATE
broadcast and specifies the metadata for the widget.
Run your app. Once your app has been deployed, long press on your desktop and install your new widget.


A
BroadcastReceiver
must finish his
onReceive()
method within 5 secs.
For potentially long running actions, as for example network access, you should do this processing in a service and perform the update the widgets from the service.
The following will demonstrate the usage of a service to update the widget.
Create the following
UpdateWidgetService
class in your project.
package de.vogella.android.widget.example; import java.util.Random; import android.app.PendingIntent; import android.app.Service; import android.appwidget.AppWidgetManager; import android.content.ComponentName; import android.content.Intent; import android.os.IBinder; import android.util.Log; import android.widget.RemoteViews; public class UpdateWidgetService extends Service { private static final String LOG = "de.vogella.android.widget.example"; @Override public void onStart(Intent intent, int startId) { Log.i(LOG, "Called"); // Create some random data AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this .getApplicationContext()); int[] allWidgetIds = intent .getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS); ComponentName thisWidget = new ComponentName(getApplicationContext(), MyWidgetProvider.class); int[] allWidgetIds2 = appWidgetManager.getAppWidgetIds(thisWidget); Log.w(LOG, "From Intent" + String.valueOf(allWidgetIds.length)); Log.w(LOG, "Direct" + String.valueOf(allWidgetIds2.length)); for (int widgetId : allWidgetIds) { // Create some random data int number = (new Random().nextInt(100)); RemoteViews remoteViews = new RemoteViews(this .getApplicationContext().getPackageName(), R.layout.widget_layout); Log.w("WidgetExample", String.valueOf(number)); // Set the text remoteViews.setTextViewText(R.id.update, "Random: " + String.valueOf(number)); // Register an onClickListener Intent clickIntent = new Intent(this.getApplicationContext(), MyWidgetProvider.class); clickIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); clickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, allWidgetIds); PendingIntent pendingIntent = PendingIntent.getBroadcast( getApplicationContext(), 0, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT); remoteViews.setOnClickPendingIntent(R.id.update, pendingIntent); appWidgetManager.updateAppWidget(widgetId, remoteViews); } stopSelf(); super.onStart(intent, startId); } @Override public IBinder onBind(Intent intent) { return null; } }
Add this class as a
Service
to your
AndroidManifest.xml.
<service android:name=".UpdateWidgetService"></service>
Change
MyWidgetProvider
to the following. It will now only construct the service and start it.
package de.vogella.android.widget.example; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.util.Log; public class MyWidgetProvider extends AppWidgetProvider { private static final String LOG = "de.vogella.android.widget.example"; @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { Log.w(LOG, "onUpdate method called"); // Get all ids ComponentName thisWidget = new ComponentName(context, MyWidgetProvider.class); int[] allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget); // Build the intent to call the service Intent intent = new Intent(context.getApplicationContext(), UpdateWidgetService.class); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, allWidgetIds); // Update the widgets via the service context.startService(intent); } }
Once called this service will update all widgets. You can click on one of the widgets to update all widgets.
Before posting questions, please see the vogella FAQ. If you have questions or find an error in this article please use the www.vogella.com Google Group. I have created a short list how to create good questions which might also help you.
Introduction to Android Development
Background processing in Android
Eclipse RCP Training (German) Eclipse RCP Training with Lars Vogel
Android Tutorial Introduction to Android Programming
GWT Tutorial Program in Java and compile to JavaScript and HTML
Eclipse RCP Tutorial Create native applications in Java
JUnit Tutorial Test your application
Git Tutorial Put everything you have under distributed version control system