桌面组件开发

来源:互联网 发布:农产品淘宝店名怎么起 编辑:程序博客网 时间:2024/05/10 21:34

http://blog.csdn.net/stonecao/archive/2011/03/18/6259221.aspx

桌面组件开发学习笔记 收藏
1.       桌面组件开发概述

a)         什么是桌面组件:

桌面组件是一个很小的用于添加到桌面的应用程序,例如一个桌面日历,一个桌面时钟,或者一首后台播放歌曲的详细信息。

b)        怎么打开桌面组件:

当你长android桌面空白处,跳出一个Add to Home screen对话框,列表中有一个选项是widgets,这个就是桌面组件,你点击进入widgets后就会显示一个所有的桌面组件的列表,你选中一个就打开了一个桌面组件了。

c)        桌面组件程序包含哪几部分:

                         i.              AppWidgetProvider

每个桌面组件就是一个AppWidget,当你需要在代码中实现一个桌面组件,首先你必须得实现AppWidgetProvider类,当你点击AppWidgetProvider类你会发现其实AppWidgetProvider就是一个BroadcastReceiver子类,也就是说它也是一个广播接收器:

 

你需要实现它的onUpdate等方法,然后在AndroidManifest.xml配置文件中注册该广播接收器:

        <receiver android:name=".ExampleAppWidgetProvider">

            <meta-data android:name="android.appwidget.provider"

                    android:resource="@xml/appwidget_provider" />

            <intent-filter>

                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />

            </intent-filter>

        </receiver>
 

                       ii.              桌面组件属性文件

每个桌面组件必须要有一个描述桌面组件在桌面上大小,位置,刷新频率等信息的描述文件,见前面标记为红色的android:resource属性,该属性就为该AppWidgetProvider指定属性文件:

<?xml version="1.0" encoding="utf-8"?>

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"

    android:minWidth="100dp"

    android:minHeight="50dp"

    android:updatePeriodMillis="86400000"

    android:initialLayout="@layout/appwidget_provider"

    android:configure="com.yarin.android.Examples_09_07.Activity01"

    >

</appwidget-provider>
 

                      iii.              桌面组件布局文件:

其实这个不是必须的,该文件用来布局桌面组件内部内容,就像activity的布局文件类似,当你为一个桌面组件创建一个RemoteViews对象时,你可以把这个布局文件设置进去,桌面组件具体展示的内容也决定于该布局文件。

d)        桌面组件的基本运行机制

桌面组件与普通的组件不一样,它是运行在桌面程序上的,也就是系统应用程序launcher,桌面组件内容显示更新全部是通过RemoteViews对象,刚创建桌面组件的时候系统会绑定AppWidgetProvider到一个AppWidgetId,然后后面通过AppWidgetManager.updateAppWidget方法把RemoteViews对象更新到对应的桌面组件,后面我会对这一机制进行详细说明。

2.       几个主要配置说明

a)         AndroidManifest.xml

该文件用来注册桌面组件的AppWidgetProvider,这个我对配置中的内容进行详细说明

        <receiver android:name=".ExampleAppWidgetProvider" android:icon="@drawable/icon">

            <meta-data android:name="android.appwidget.provider"

                    android:resource="@xml/appwidget_provider" />

            <intent-filter>

                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />

            </intent-filter>

        </receiver>
 

                    i.              android:icon:指定该桌面组件在桌面桌面组件列表中显示的图标,如下图所示:

 

 

                       ii.              android:name:

                      iii.              android:resource:说明该provider引用的桌面组件属性文件

                     iv.              action:广播事件,网上很多地方没有对此给出很清楚地解释,这个事件原是桌面组件更新事件,当AppWidgetProvider接收到该广播事件,执行其onUpdate方法,但是我仔细跟了一下代码,发现它只会在桌面组件刚刚创建的时候执行,可能我还没有研究透彻。

b)        桌面组件属性文件

<?xml version="1.0" encoding="utf-8"?>

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"

    android:minWidth="100dp"

    android:minHeight="50dp"

    android:updatePeriodMillis="86400000"

    android:initialLayout="@layout/appwidget_provider"

    android:configure="com.yarin.android.Examples_09_07.Activity01"

    >

</appwidget-provider>
 

                         i.              android:minWidth:指定桌面组件的最小宽度,与AppWidget -ProviderInfo. minHeight成员对应

                       ii.              android:minHeight: 指定桌面组件的最小高度,与AppWidget -ProviderInfo. minWidth成员对应

                      iii.              android:updatePeriodMillis:指定桌面组件的更新周期,与AppWidget –ProviderInfo. updatePeriodMillis成员对应,不过我修改了该属性后好像没有什么效果

                     iv.              android:initialLayout:指定桌面组件初始化布局文件,与AppWidget –ProviderInfo. initialLayout成员对应,后面可为RemoteViews对象重新设置布局文件

                       v.              android:configure:指定桌面组件创建时候的一个初始配置activity,与AppWidgetProviderInfo. configured对应,可选。

3.       桌面组件的启动过程详细分析

研究桌面组件启动过程有助于深入了解桌面组件的运行机制,为后面我们开发自己的桌面组件提供很好的参考,也为后面我们开发桌面组件碰到问题时解决问题提供一条有效地途径,这里我有些地方并没有深入进去,这由于时间关系,也有的没有真正理解透彻,有待后面继续完善。

桌面组件是在桌面程序上添加的,因此要研究桌面组件先要从launcher这个系统应用程序入手,需要搭建launcher应用的源代码环境,前面我有一个文档已经对如何搭建调试android源代码程序进行了详细说明,这里不作展开,下面我就开始对桌面组件的代码执行过程进行详细说明:

a)         桌面组件AppWidgetProvider加载//暂时未了解,待补充

b)        打开Add to Home screen对话框

长按桌面或者按menu键可以打开该对话框,长按桌面空白处对应launcher中onLongClick事件,在onLongClick方法它将进入showAddDialog(cellInfo)异步去打开一个对话框Add to Home screen

c)        话框Add to Home screen对应类launcher.CreateShortcut,前面的异步事件将传递到CreateShortcut.onShow方法(这里我不明白为什么没有执行launcher.CreateShortcut的createDialog方法,我猜测启动时候已经把该对话框给创建好了)。

d)        点击选中widgets,进入launcher.CreateShortcut的onClick方法,仔细阅读它代码可知道它进入case AddAdapter.ITEM_APPWIDGET:我把代码列在下面仔细说明:

case AddAdapter.ITEM_APPWIDGET: {

   int appWidgetId = Launcher.this.mAppWidgetHost.allocateAppWidgetId();

//这里生成了一个appWidgetId,供后面绑定AppWidgetProvider使用

                    Intent pickIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_PICK);

 //新建一个intent,该intent是打开一个现实Widgets列表的activity,该activity对应类AppWidgetPickActivity                   pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);//设置EXTRA_APPWIDGET_ID

                    // start the pick activity

                    startActivityForResult(pickIntent, REQUEST_PICK_APPWIDGET);//发送intent打开AppWidgetPickActivity

                    break;

                }
 

e)         之后就进入了AppWidgetPickActivity的生命周期,该Activity是在com.android.settings中,先在onCreate方法中创建了一个InstalledAppWidgets列表,该列表就是我们在界面上能见到的所有widgets。

f)         选中一个widgets,进入AppWidgetPickActivity.onClick事件监听,注意阅读该方法代码,它会进入else,所以注意它的关键代码:

                mAppWidgetManager.bindAppWidgetId(mAppWidgetId, intent.getComponent());//绑定选中的桌面组件与mAppWidgetId

                result = RESULT_OK;//设置返回结果为ok
 

想进一步跟踪下去mAppWidgetManager.bindAppWidgetId,可是发现后面它调用了本地方法,无法看到它的实现细节,不过根据logcat日志,我发现它应该是创建了一个创建一个broadcast Intent广播,该广播事件是是android.appwidget.action.APPWIDGET_UPDATE,通过该intent事件进入AppWidgetProvider.onUpdate方法,另外每一个activity执行结束后面都会进入launcher.onActivityResult,查看该函数方法有两个关键的case:

                case REQUEST_PICK_APPWIDGET:

                    addAppWidget(data);

                    break;

                case REQUEST_CREATE_APPWIDGET:

                    completeAddAppWidget(data, mAddItemCellInfo);
 

最终会进入completeAddAppWidget该方法,然后把桌面组件添加到桌面上去,要研究桌面组件如何添加到桌面上,需要仔细研究launcher. completeAddAppWidget方法。

g)      另外在AppWidgetProvider.onUpdate,一般是添加桌面组件ui更新代码,ui的更新都是通过RemoteViews,后面我会给一个例子说明onUpdate中如何使用Remoteviews更新桌面组件,注意AppWidgetProvider.onUpdate方法是在该桌面组件对应的进程中进行的。

h)      Remoteviews更新桌面组件最终还是要在launcher进程中执行,在launcher中有一个handler: AppWidgetHost.UpdateHandler,该handler就是用来处理Remoteviews更新的,一旦我们调用appWidgetManager.updateAppWidget(appWidgetId, views)这个方法,AppWidgetHost.UpdateHandler. handleMessage事件就会响应,不过要注意是该事件响应是在launcher进程中执行:

            switch (msg.what) {

                case HANDLE_UPDATE: {

                    updateAppWidgetView(msg.arg1, (RemoteViews)msg.obj);//执行Remoteviews更新

                    break;

                }
 

i)      桌面组件的删除

桌面组件把它托动到垃圾箱将响应AppWidgetHost.deleteAppWidgetId函数,想要了解桌面组件删除的具体实现可以从此切入。

4.       桌面组件的AppWidgetId与AppWidgetProvider的绑定机制:

a)         在桌面组件刚被添加的时候,系统通过AppWidgetHost.allocateAppWidgetId函数为桌面组件分配一个新的AppWidgetId

b)        选择一个桌面组件的时候,通过点击的列表选项可获取到对应桌面组件对应的ComponentName,一个ComponentName由一个包名和一个AppWidgetProvider类名称唯一指定,然后通过AppWidgetManager.bindAppWidgetId(int appWidgetId, ComponentName provider)把前面生成的AppWidgetId与一个ComponentName绑定,一个ComponentName可以绑定多个AppWidgetId。

c)        一个AppWidgetProvider可以由一个ComponentName唯一确定,每一个AppWidgetProvider上可能建立了多个桌面组件,每一个桌面组件对以应一个AppWidgetId,要获取一个AppWidgetProvider上所有的桌面组件的AppWidgetId数据,可以先建一个ComponentName对象,设置ComponentName中的类名和包名为AppWidgetProvider的类名和包名,然后可通过AppWidgetManager. getAppWidgetIds(ComponentName provider)方法获取AppWidgetId列表

d)        每一个桌面组件都对应有一个AppWidgetProviderInfo类来描述桌面组件的信息,该类在桌面组件与AppWidgetId绑定的时候生成,可以通过AppWidgetManager.getAppWidgetInfo(int appWidgetId)方法获取,AppWidgetProviderInfo信息对应前面我将的桌面组件属性文件。

5.       与桌面组件开发相关的几个重要类说明

a)         AppWidgetProvider

桌面组件实现的组要类,它的父类是一个广播接收器,它主要作用就是接收更新桌面组件的广播消息,然后更新桌面组件

                         i.              public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds):

该方法对应事件:android.appwidget.action.APPWIDGET_UPDATE,当桌面组件被周期更新的时候它被调用,深入父类的onReceive方法就会了解到,当父类的onReceive方法接收到android.appwidget.action.APPWIDGET_UPDATE消息就会调用该update方法。

                       ii.              public void onReceive(Context context, Intent intent):

AppWidgetProvider本身是一个广播接收器,父类已经覆盖了该方法,你重新覆盖该方法的时候,注意要添加super. onReceive到你的方法中,否则onUpdate将不再会被调用

                      iii.              public void onDeleted(Context context, int[] appWidgetIds):

AppWidgetProvider接收消息android.appwidget.action.APPWIDGET_DELETED时候调用该方法。

b)        AppWidgetProviderInfo

该类用来描述桌面组件的属性

c)        AppWidgetHost

d)        AppWidgetHostView

e)         AppWidgetManager

桌面组件管理类,在写桌面组件代码时经常要使用的类,根据AppWidgetProvider获取桌面组件id,获取AppWidgetProviderInfo,更新桌面组件等操作都需要使用该类来完成。

f)         RemoteViews

                         i.              RemoteViews(String packageName, int layoutId):

新一个remoteview对象,需要应用程序包名和一个布局文件Id

                       ii.              setOnClickPendingIntent(int viewId, PendingIntent pendingIntent):

为桌面组件的一个view对象添加点击响应事件

g)        PendingIntent

                         i.              Static getActivity(Context context, int requestCode, Intent intent, int flags)

该静态方法用于创建一个启动一个Activity的PendingIntent,context参数设置为当前应用的context, intent为点击事件发送的intent,该intent中需要设置启动activity的classs, requestCode, flags不同参数值的含义请参考帮助文档,

h)        Binder

i)          IAppWidgetService

j)          ComponentName

6.       更新桌面组件

一般的,桌面组件不需要自己做一些更新操作,在桌面组件属性文件中有一个定义桌面组件更新周期的的属性,应该可以实现桌面组件自己的周期更新,不过我现在还没有研究它怎么使用的,现实情况大多是其它的应用通知桌面组件去更新,比如一个音乐播放服务通知一个歌词秀的桌面组件去更新歌词,我这里就以这种场景为例,说明桌面组件是如何通知更新的

a)         增加一个广播接收器去接收来自其它应用需要更新桌面组件的广播消息,这里有两种做法,可以去新增加一个广播接收器,也可以使用AppWidgetProvider本身作为广播接收器,增加一个onReceive方法,我这里以第二种方式为例,我先实现广播接收功能代码如下:

    @Override

    public void onReceive(Context context, Intent intent) {

       // 先接受处理自定义消息,我这里自定义了一个发送消息的action,该action广播由另一个后台service发出,每隔1秒发送一次,接收后使用system.out把它打印出来

       if(ACTION_SEND_MESSAGE.equals(intent.getAction())){

           System.out.println("receive broadcaset intent,action="+intent.getAction()+"extra="+intent.getExtras());

       }else{

       //父类的onReceive方法是必须的,因为在父类的onReceive方法中实现了广播接收器的onReceive抽象方法,并对update,delete等消息进行了处理

           super.onReceive(context, intent);

       }     

    }
 

b)        当我在自定义的AppWidgetProvider添加上面的消息处理函数后,然后需要在AndroidManifest.xml中增加AppWidgetProvider对我自定义的发送消息广播的接收,见下面标记为红色部分:

        <receiver android:name=".ExampleAppWidgetProvider" android:icon="@drawable/icon">

            <meta-data android:name="android.appwidget.provider"

                    android:resource="@xml/appwidget_provider" />

            <intent-filter>

                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />

                <action android:name="com.cao.action.SEND_MESSAGE" />

            </intent-filter>

        </receiver>
 

c)        启动发送后台消息的service,在后台日志中可以看到每隔一秒打印了一个接收到广播消息的日志:

 

 

d)        在onReceive方法中添加把消息更新到桌面组件的代码:

       if(ACTION_SEND_MESSAGE.equals(intent.getAction())){

           String msg = intent.getStringExtra("MSG");

           System.out.println("ExampleAppWidgetProvider receive:"+msg);

          

           //先获取一个AppWidgetManager对象

           AppWidgetManager appWidgetMgr = AppWidgetManager.getInstance(context);

           //新建一个ComponentName对象,用来获取获取桌面组件widgetIds

           ComponentName compName = new ComponentName(context,

                  ExampleAppWidgetProvider.class);

           int[] widgetIds = appWidgetMgr.getAppWidgetIds(compName);

           //建立RemoteViews对象,设置桌面组件ui内容

           RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget_provider);

           //指定布局的组件内容,这里对桌面组件的一个TextView对象设置文本。

           views.setTextViewText(R.id.appwidget_text, msg);

           //使用AppWidgetManager对象的updateAppWidget更新桌面组件

           appWidgetMgr.updateAppWidget(widgetIds, views);

       }else{
 

添加上面方法后可以看到桌面组件显示的内容每隔一秒钟就进行了一次更新。

7.       桌面组件事件响应

RemoteView中有一个方法setOnClickPendingIntent(int viewId, PendingIntent pending Intent),该方法就是为一个桌面组件内部的ui添加一个点击事件,该方法主要是如何创建一个PendingIntent对象,打开帮助文档看到PendingIntent有3个静态方法:

getActivity(Context context, int requestCode, Intent intent, int flags)

getBroadcast(Context context, int requestCode, Intent intent, int flags)

getService(Context context, int requestCode, Intent intent, int flags)

这三个方法分别是用来创建通知Activity,通知BroadCastReceicer,通知Service的intent消息,context用来设置当前的context,intent设置的是点击事件产生时发送的intent,增加桌面组件点击事件,我写的这个例子为桌面组件的textview添加了一个点击事件,点击桌面组件将打开一个activity,实现该点击事件的代码:

           //添加点击事件

           PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, new Intent(context,Activity01.class), 0);

           views.setOnClickPendingIntent(R.id.appwidget_text, pendingIntent);
 


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/stonecao/archive/2011/03/18/6259221.aspx

 

原创粉丝点击