Version 2.6
Copyright © 2009, 2010, 2011, 2012, 2013 Lars Vogel
12.01.2013
| Revision History | |||
|---|---|---|---|
| Revision 0.1 | 01.09.2010 | Lars Vogel |
Created |
| Revision 0.2 - 2.6 | 13.12.2010 - 12.01.2013 | Lars Vogel |
bug fixes and enhancements |
Table of Contents
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 .
Widgets are little applications which can be placed on a widget host, typically the homescreen or the lockscreen, of your Android device.
A Widget runs as part of the process of its host. 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 a
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 Widgets you:
Define a layout file
Create an XML file (AppWidgetProviderInfo) which describes the properties of the widget,
e.g. size or the
fixed
update frequency.
Create a BroadcastReceiver which is used to build the user interface of the Widget.
Enter the
Widget
configuration in the
AndroidManifest.xml
file.
Optional you can specify a configuration
activity
which is called once a new instance of
the
widget
is added to the widget host.
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 it larger or smaller.
To enable this for
Widgets
you can use the
android:resizeMode="horizontal|vertical"
attribute in the XML configuration file for the widget.
To register a widget you create a
BroadcastReceiver
with an intent filter for the
android.appwidget.action.APPWIDGET_UPDATE
action
.
<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 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 by this
meta-data
contains
the configuration settings for the
widget. If contains for example the update interface, the size and
the
initial layout of
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="1800000" > </appwidget-provider>
A widget is restricted in the
View
classes it can use. As layouts you
can
use the
FrameLayout,
LinearLayout
and
RelativeLayout
classes. 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. This adapter
Views
require that you define a
collection view widget
which is described later in this tutorial.
The only interaction that 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.
Your
BroadcastReceiver
typically
extends the
AppWidgetProvider
class.
The
AppWidgetProvider
class implements the
onReceive()
method, extracts the required information and calls the
following
widget lifecycle methods.
As you can add 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.
A
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.
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 update the widget. The smallest update interval is 1800000 milliseconds (30 minutes).
The
AlarmManager
allows you
to be more resource efficient and to have a higher
frequency
of
updates. To use this approach you define a
service
and schedule this
service
via the
AlarmManager
regularly. This service updates the widget.
Please note that a higher update frequency will wake up the phone from the energy safe mode. As a result your widget consumes more energy.
In the following tutorial you create a widget which displays a random
number. This random number
will updated automatically
every 30 minutes.
You also register a
OnClickListener
so that the widgets updates once the user clicks on it.
The resulting widget will look like the following.

Create a new Android project called
de.vogella.android.widget.example
with an
activity
in the package
de.vogella.android.widget.example.
Create a new file
myshape.xml
in the
/res/drawable
directory. 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
res/layout
folder.
<?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
widget_info.xml
file, 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 following BroadcastReceiver which will be called for updates.
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 the
AndroidManifest.xml
and register your
Widget.
<?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.


Collection view
Widgets
add support for views like
List,
Stack
and
Grid.
For Collection view Widgets you need two layouts, one for the widget and one for each item in the widget collection.
The
Widget
items are filled by an instance of the
RemoteViewsFactory
factory class.
This factory class is provided by a
service
which must extend the
RemoteViewsService
class. This service requires the
andriod.permission.BIND_REMOTEVIEWS
permission.
To connect your
Views
with the
service
you use your
onUpdate()
method in your Widget receiver.
You define an
Intent
pointing to the
service
and use the
setRemoteAdapter
method on the
RemoteViews
class.
Intent intent = new Intent(context, YourRemoteViewsService.class); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); views.setRemoteAdapter(apppWidgetId, R.id.widget_your_id_to_collectionview, intent) // Rest as normal
Since Android 4.2, it is possible to launch Home Screen application
widgets
on the Lock Screen of an Android device. For that you just need
to
additionally declare a widget to support ???keyguard??? category in the
android:widgetCategory
attribute in the
AppWidgetProviderInfo
XML
file. The following code shows an example.
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:widgetCategory="keyguard|home_screen" ... > ... </appwidget-provider>
In this example you declare a widget to support both - the Home and the Lock screens. If you recompile and launch your application now, you will be able to add the widget to the Lock Screen already.
You can also detect a widget category at runtime. For this in the
AppWidgetProvider.onUpdate()
method you can check for the category
option of a widget with the
following code.
Bundle options = appWidgetManager.getAppWidgetOptions(widgetId); int category = options.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY, -1); boolean isLockScreen = category == AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD;
Using this technique you can decide in runtime whether widgets, your application provides, will look different, when they are hosted on the Lock Screen.
Similarly to how you used android:initialLayout attribute for defining
an
initial layout for Home screen widgets, you can use a new
android:initialKeyguardLayout
attribute for Lock screen in the
AppWidgetProviderInfo
XML file. This layout will appear immediately
after a widget is added
and will be replaced by the real layout once
widget is initialized.
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 file.
<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.
Enable your widget for the lockscreen by adding the required info to the widget metadata file.
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:widgetCategory="keyguard|home_screen" ... > ... </appwidget-provider>
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
vogella Training Android and Eclipse Training from the vogella team
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