1.认识AppWidget
AppWidget就是我们平常在桌面上见到的那种一个个的小窗口,利用这个小窗口可以给用户提供一些方便快捷的操作
这里的天气插件和开关快捷键都是AppWidget,有了一个比较直观的认识,本文主要从以下几点分析
1.AppWidgetProvider如何被AppWidget系统所识别;
2.AppWidgetProvider何时/如何通过RemoteViews提供并更新数据;
3.如何响应通过RemoteViews提供的PendingIntent的按钮点击操作。
2. 实现一个AppWidgetProvider
- 实现AppWidgetProvider的子类,并至少override onUpdate()方法(非必须,但是如果不这样做,该AppWidgetProvider就没有提供任何内容,也就不是AppWidgetProvider);
- 在AndroidManifest.xml中,声明上述的AppWidgetProvider的子类是一个Receiver,并且:
- 该Receiver的intent-filter的Action必须包含“android.appwidget.action.APPWIDGET_UPDATE”;
- 该Receiver的meta-data为“android.appwidget.provider”,并用一个xml文件来描述布局属性。
- 在2.2的xml文件中描述布局属性的节点名称必须为“appwidget-provider”。该xml文件主要是对所建立的appwidget的属性设置,其中比较常见的属性有appwidget更新的时间,其初始的布局文件等等。
以上几点皆是AppWidget系统判断是否是AppWidgetProvider的标志。
3. AppWidgetProvider类分析
AppWidgetProvider是一个BroadcastReceiver,必须在AndroidManifest.xml中声明该Receiver,并接收“android.appwidget.action.APPWIDGET_UPDATE”。
在AppWidgetProvider的onReceiver()实现中已经对接收到的ActionAppWidgetManager.ACTION_APPWIDGET_UPDATE / AppWidgetManager.ACTION_APPWIDGET_DELETED/ AppWidgetManager.ACTION_APPWIDGET_ENABLED以及AppWidgetManager.ACTION_APPWIDGET_DISABLED做了处理,分别执行onUpdate()/ onDeleted() / onEnabled() / onDisabled()。
所以,AppWidgetProvider的实现类,要overrideonReceive(),以及onXXX()[注:至少要实现onUpdate(),在这里AppWidgetProvider通过RemoteViews提供内容给AppWidgetHost,否则,所谓的AppWidgetProvider什么也没提供]。
而在onReceive()的开始处就要执行super.onReceive()让AppWidgetProvider来分发AppWidgetProvider所要处理的上述广播消息。
AppWidgetProvider处理AppWidget中的广播:
- onUpdate() 处理AppWidgetManager.ACTION_APPWIDGET_UPDATE广播。该广播在需要AppWidgetProvider提供RemoteViews数据时,由AppWidgetService.sendUpdateIntentLocked()发出。
- onDeleted() 处理AppWidgetManager.ACTION_APPWIDGET_DELETED广播。该广播在有该AppWidgetProvider的实例被删除时,由AppWidgetService.deleteAppWidgetLocked()发出。
- onEnabled() 处理AppWidgetManager.ACTION_APPWIDGET_ENABLED广播。该广播在该AppWidgetProvider被实例化时,由AppWidgetService.sendEnableIntentLocked()发出。
- onDisabled() 处理AppWidgetManager.ACTION_APPWIDGET_DISABLED广播。该广播在该AppWidgetProvider的所有实例中的最后一个实例被删除时,由AppWidgetService.deleteAppWidgetLocked()发出。
一般地,AppWidgetProvider必须处理onUpdate();onEnabled()和onDisabled()最好也要处理;onDeleted()可以不处理。
4. AppWidgetProvider的如何被系统识别
4.1 AppWidgetProvider中配置AndroidManifest.xml
Android中“电量控制”这个AppWidget是由Settings中的SettingsAppWidgetProvider实现的,先来看它的AndroidManifest.xml。
下面是Settings中关于SettingsAppWidgetProvider这个AppWidgetProvider的描述信息:
<receiver android:name=".widget.SettingsAppWidgetProvider"
android:label="@string/gadget_title"android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.net.wifi.WIFI_STATE_CHANGED"/>
<action android:name="android.net.conn.BACKGROUND_DATA_SETTING_CHANGED" />
<action android:name="android.bluetooth.adapter.action.STATE_CHANGED" />
<action android:name="android.location.PROVIDERS_CHANGED"/>
<action android:name="com.android.sync.SYNC_CONN_STATUS_CHANGED" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"android:resource="@xml/appwidget_info" />
</receiver>
这个AppWidget要处理设置Wifi、Bluetooth、GPS、数据同步和亮度,所以要处理这些相应的设置项变化时的广播通知。
接着看 res/xml/appwidget_info.xml
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="294dip"
android:minHeight="72dip"
android:updatePeriodMillis="0"
android:initialLayout="@layout/widget"
>
</appwidget-provider>
这里定义了最小宽度minWidth、最小高度minHeight和初始layoutinitialLayout,用来在AppWidgetProvider还未通过RemoteViews提供数据之前,AppWidgetHost就能够获知需要为该AppWidget预留大概的位置;
updatePeriodMillis指示是否需要周期性的更新AppWidget,0是不需要周期更新。
4.2 AppWidgetProvider的信息被系统所识别
这部分是由AppWidgetService实现。
当包含AppWidgetProvider的apk被安装到系统中的时候,AppWidgetService会监听广播,并处理相应的AppWidgetProvider:
- 监听到有包被加入(Intent.ACTION_PACKAGE_ADDED或Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE)时,执行addProvidersForPackageLocked()/ updateProvidersForPackageLocked() 添加或更新其中的AppWidgetProvider;
- 监听到有包被移除(Intent.ACTION_PACKAGE_REMOVED或Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)时,执行removeProvidersForPackageLocked()移除其中的AppWidgetProvider。
下面重点关注如何加入AppWidgetProvider,看addProvidersForPackageLocked()的实现:
void addProvidersForPackageLocked(String pkgName) {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
intent.setPackage(pkgName);
List<ResolveInfo> broadcastReceivers =mPackageManager.queryBroadcastReceivers(intent,
PackageManager.GET_META_DATA);
final int N = broadcastReceivers == null ? 0 :broadcastReceivers.size();
for (int i=0; i<N; i++) {
ResolveInfo ri = broadcastReceivers.get(i);
ActivityInfo ai = ri.activityInfo;
if ((ai.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE)!= 0) {
continue;
}
if (pkgName.equals(ai.packageName)) {
addProviderLocked(ri);
}
}
}
- 检索所加入包中的所有AppWidgetManager.ACTION_APPWIDGET_UPDATE的Receiver,并放入broadcastReceivers:List<ResolveInfo>;[Line#2~ 5]
- 对每一个这样的Broadcast,满足下列要求的加入已安装AppWidgetProvider列表:
- 不是安装在外存储器上;
- Receiver的包名与安装的包名相同;
- Meta-data的节点名必须是"appwidget-provider"[解析meta-data中android:resource指向的xml内容]
解析meta-data中android:resource指向的xml内容时,所要解析哪些内容是由frameworks/base/core/res/res/values/attrs.xml中的AppWidgetProviderInfo配置:
<declare-styleable name="AppWidgetProviderInfo">
<!-- Minimum width of the AppWidget. -->
<attr name="minWidth"/>
<!-- Minimum height of the AppWidget. -->
<attr name="minHeight"/>
<!-- Update period in milliseconds, or 0 if the AppWidget will updateitself. -->
<attr name="updatePeriodMillis" format="integer"/>
<!-- A resource id of a layout. -->
<attr name="initialLayout" format="reference"/>
<!-- A class name in the AppWidget's package to be launched toconfigure.
If not supplied, then no activity will be launched. -->
<attr name="configure" format="string" />
</declare-styleable>
这些值被解析出来之后,连同label、icon以及由ComponentName(packageName,className)构造的provider被赋值到AppWidgetProviderInfo中,并被记录在AppWidgetService的mInstalledProviders:ArrayList<Provider>中。
5.AppWidgetProvider通过RemoteViews提供内容
RemoteView代表了与调用它的那个activity不在同一个进程的view,因此叫做”远程view”。在appWidget中使用这个类就可以实现当对appwidget的那个进程进行操作时响应其它进程中的activity。而RemoteViews则表示了一系列的RemoteView。
- 在onUpdate()中,创建RemoteViews的实例,传入AppWidgteProvider所在的包名和该AppWidget所要用的Layout;
- 如果要响应layoutId中某个viewId被点击操作,要创建本地的PendingIntent,并通过setOnClickPendingIntetn设置到RemoteViews中;
- 为layoutId中要显示的控件加上显示元素,比如某个ImageView的ImageResource。
- 用AppWidgetManager.updateAppWidget()更新RemoteViews到系统中,AppWidget系统会更新与之绑定的AppWidgetHost。
RemoteViews设置与显示详细实现可参考《Android中RemoteViews的实现》;AppWidgetHost如何更新RemoteView可参看《Android中Launcher对于AppWidget处理的分析:AppWidgetHost角色》。
6.通过PendingIntent设置按钮响应
<span style="font-family:Microsoft YaHei;font-size:14px;"> Intent launchIntent = new Intent();
launchIntent.setClass(context, SettingsAppWidgetProvider.class);
launchIntent.addCategory(Intent.CATEGORY_ALTERNATIVE);
launchIntent.setData(Uri.parse("custom:" + buttonId));
PendingIntent pi = PendingIntent.getBroadcast(context, 0 /* norequestCode */,
launchIntent, 0 /* no flags */);</span>
buttonId是layout中的各个Button对应的自定义的Id,该Id只要在本程序中用来能够区分出是哪个Button就可以,被指进”custom:”参数。
AppWidgetProvider本身就是个BroadcastReceiver,在其onReceive()中,就可以判断出是哪个Button被点击了:
<span style="font-family:Microsoft YaHei;font-size:14px;"> if(intent.hasCategory(Intent.CATEGORY_ALTERNATIVE)) {
Uri data = intent.getData();
int buttonId = Integer.parseInt(data.getSchemeSpecificPart());
if (buttonId == BUTTON_WIFI) {
// 切换Wifi状态
} else if (buttonId == BUTTON_BRIGHTNESS) {
// 切换亮度
} else if (buttonId == BUTTON_SYNC) {
// 切换数据同步设置
} else if (buttonId == BUTTON_GPS) {
// 切换GPS打开开关
} else if (buttonId == BUTTON_BLUETOOTH) {
// 切换蓝牙打开状态
}
}</span>
在appWidget开发中,其点击事件可分成三种类型:- 开启Activity
- 开始Service
- 发送按钮Action
6.1、开启Activity
1、首先先定义个开启Activity的intenteg: Intent fullIntent=new Intent(this ,FullScreen.class);
若要传递数据,则使用intent.putExtra()方法
eg: fullIntent.putExtra("isCircle", isCircle);
2、用intent实例化一个PendingIntent,调用pendingIntent的getActicity方法来启动另一个Activity
①若该Intent带有数据,则需要将最后一个参数的值设为:FLAG_CANCEL_CURRENT
eg: PendingIntent Pfullintent= PendingIntent.getActivity(this, 0, fullIntent, PendingIntent.FLAG_CANCEL_CURRENT);
②若该Intent不带数据,则最后一个参数设为0
eg: PendingIntent Pfullintent= PendingIntent.getActivity(this, 0, fullIntent, 0);
3、实例化RemoteView,其对应相应的Widget布局
eg: RemoteViews views= new RemoteViews(getPackageName(), R.layout.widget);
4、给RemoteView上的Button或ImageButton设置按钮事件
eg: views.setOnClickPendingIntent(R.id.IBfullscreen, Pfullintent);
5、更新AppWidget界面
①如果是在onUpdate()方法内更新AppWidget界面
eg: appWidgetManager.updateAppWidget(appWidgetIds, ActivityView);
②如果是在onUpdate()方法外(一般为Service内)更新AppWidget界面,则需要定义几个变量
eg: public RemoteViews views; //RemoteView对象
public ComponentName thisWidget; //组件名
public AppWidgetManager manager; // AppWidget管理器
thisWidget = new ComponentName(this, PictureAppWidgetProvider.class);
manager = AppWidgetManager.getInstance(this);
manager.updateAppWidget(thisWidget, views);
6.2、开启Service
1、定义一个intent来开启Serviceeg: Intent startServiceInten=new Intent("zyf.temp.Service.START");
注:参数为开启Service的动作
2、用Intent实例化一个PendingIntent,利用PendingIntent的getService方法来启动一个服务
eg: PendingIntent Pintent= PendingIntent.getService(context, 0, startServiceInten, 0);
3、实例化RemoteView,其对应相应的Widget布局
eg: RemoteViews views= new RemoteViews(getPackageName(), R.layout.widget);
4、给RemoteView上的Button或ImageButton设置按钮事件
eg: views.setOnClickPendingIntent(R.id.IBfullscreen, Pfullintent);
5、更新AppWidget界面
①如果是在onUpdate()方法内更新AppWidget界面
eg: appWidgetManager.updateAppWidget(appWidgetIds, ActivityView);
②如果是在onUpdate()方法外(一般为Service内)更新AppWidget界面,则需要定义几个变量
eg: public RemoteViews views; //RemoteView对象
public ComponentName thisWidget; //组件名
public AppWidgetManager manager; // AppWidget管理器
thisWidget = new ComponentName(this, PictureAppWidgetProvider.class);
manager = AppWidgetManager.getInstance(this);
manager.updateAppWidget(thisWidget, views);
6.3、发送按钮Action
1、定义一个Intent来发送按钮Actioneg: Intent prevInten=new Intent("PREV");
2、用Intent实例化一个PendingIntent,利用PendingIntent的getBroadcast方法来发送广播
eg: PendingIntent Pprevintent= PendingIntent.getBroadcast(this, 0, prevInten, 0);
3、实例化RemoteView,其对应相应的Widget布局
eg: RemoteViews views= new RemoteViews(getPackageName(), R.layout.widget);
4、给RemoteView上的Button或ImageButton设置按钮事件
eg: views.setOnClickPendingIntent(R.id.IBprev, Pprevintent);
5、更新AppWidget界面
①如果是在onUpdate()方法内更新AppWidget界面
eg: appWidgetManager.updateAppWidget(appWidgetIds, ActivityView);
②如果是在onUpdate()方法外(一般为Service内)更新AppWidget界面,则需要定义几个变量
eg: public RemoteViews views; //RemoteView对象
public ComponentName thisWidget; //组件名
public AppWidgetManager manager; // AppWidget管理器
thisWidget = new ComponentName(this, PictureAppWidgetProvider.class);
manager = AppWidgetManager.getInstance(this);
manager.updateAppWidget(thisWidget, views);
6、接收该Action
①在AppWidget自己的onReceive方法内接收
⒈在Action,要在Manifest.xml中加入Action
eg: <intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"> </action>
<action android:name="PREV"></action>
</intent-filter>
⒉在onReceive()方法内编写要实现的动作
eg: if(intent.getAction().equals("PREV"))
{
//在这编写接收到该Action后要实现的动作
}
②在Service内接收
⒈注册一个BroadcastReceive,声明接收器
eg: IntentFilter filter=new IntentFilter();
filter.addAction("PREV");
registerReceiver(doCommand, filter);
⒉,在BroadcastReceive类的onReceive方法内编写要实现的动作
eg: if(intent.getAction().equals("PREV"))
{
//在这编写接收到该Action后要实现的动作
}