Android AppWidget开发

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/tianmi1988/article/details/50507142

1.认识AppWidget

AppWidget就是我们平常在桌面上见到的那种一个个的小窗口,利用这个小窗口可以给用户提供一些方便快捷的操作



这里的天气插件和开关快捷键都是AppWidget,有了一个比较直观的认识,本文主要从以下几点分析

1.AppWidgetProvider如何被AppWidget系统所识别;

2.AppWidgetProvider何时/如何通过RemoteViews提供并更新数据;

3.如何响应通过RemoteViews提供的PendingIntent的按钮点击操作。

2. 实现一个AppWidgetProvider

  1. 实现AppWidgetProvider的子类,并至少override onUpdate()方法(非必须,但是如果不这样做,该AppWidgetProvider就没有提供任何内容,也就不是AppWidgetProvider);
  2. 在AndroidManifest.xml中,声明上述的AppWidgetProvider的子类是一个Receiver,并且:
    • 该Receiver的intent-filter的Action必须包含“android.appwidget.action.APPWIDGET_UPDATE”;
    • 该Receiver的meta-data为“android.appwidget.provider”,并用一个xml文件来描述布局属性。
  3. 在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);
           }
        }
    }
  1. 检索所加入包中的所有AppWidgetManager.ACTION_APPWIDGET_UPDATE的Receiver,并放入broadcastReceivers:List<ResolveInfo>;[Line#2~ 5]
  2. 对每一个这样的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>中。

AppWidgetProvider AppWidgetProviderInfo



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设置按钮响应

PendingIntent与以前我们的Intent不同,以前我们新建一个intent时,立刻就用它启动一个activity,或者启动一个service,亦或是发送一个broadcast。这里我们新建一个PendingIntent后,按照字面意思并不马上使用它,而是当我们需要使用它的时候再启动,比如说当某一事件需要响应时,我们这时候可以使用建立好了的PendingIntent了。一个PendingIntent中包含了一个intent。
可以通过给RemoteViews设置PendingIntent获知感兴趣的View被点击时的响应:
<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的intent
eg: 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来开启Service
eg: 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来发送按钮Action
eg: 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后要实现的动作
}







猜你喜欢

转载自blog.csdn.net/tianmi1988/article/details/50507142