1.AppWidgetProviderInfo对象

这个对象为AppWidget提供元数据,包括布局、更新频率等信息,这个对象定义在xml文件中,不需要自己生成,时系统自己生成的。

2.AppWidgetProvider

这个类定义了AppWidget的基本生命周期函数,具体如下:

onReceive(Context, Intent) 接收广播事件
onUpdate(Context , AppWidgetManager, int[] appWidgetIds) 到达指定的更新时间或用户向桌面添加widget时候调用
onEnabled(Context) 当AppWidget实例第一次被创建时调用
onDeleted(Context, int[] appWidgetIds) 当AppWidget被删除时调用
onDisabled(Context) 当最后一个AppWidget被删除时调用

四.创建AppWidget的基本步骤

1.定义AppWidgetProviderInfo:在res中新建文件夹xml,在其中定义AppWidget_ProviderInfo.xml文件,主要设置的参数如下:
minWidth: 定义Wdiget组件的宽度
minHeight: 定义Wdiget组件的高度
updatePeriodMillis: 更新的时间周期
initialLayout: Widget的布局文件

代码示例:

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


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


 android:minWidth="80dp" 


 android:minHeight="32dp" 


 android:updatePeriodMillis="86400000" 


 android:initialLayout="@layout/appwidgetlayout" > 


</appwidget-provider>



其中的appwidgetlayout为layout文件夹下面新建一个xml文件,在这个文件中描述了appWidget的控件和布局等等信息,如:

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

<LinearLayout 

 xmlns:android="http:///apk/res/android" 

 android:layout_width="wrap_content" 

 android:layout_height="wrap_content" 

 android:orientation="horizontal" 

 android:background="@drawable/appbackground" 

 android:gravity="center"> 


 <TextView 

 android:id="@+id/title" 

 android:layout_width="90dip" 

 android:layout_height="wrap_content" 

 android:text="无歌曲播放" 

 android:textColor="#ffffff" 

 android:textSize="15dip" 

 android:layout_marginLeft="30dip" 

 /> 

 <ImageButton 

 android:id="@+id/lastButton" 

 android:layout_width="wrap_content" 

 android:layout_height="wrap_content" 

 android:background="@drawable/latest_selecor" 

 android:layout_marginRight="5dip" 

 /> 


 <ImageButton 

 android:id="@+id/playButton" 

 android:layout_width="wrap_content" 

 android:layout_height="wrap_content" 

 android:layout_marginRight="5dip" 

 android:layout_marginLeft="5dip" 


 /> 

 <ImageButton 

 android:id="@+id/nextButton" 

 android:layout_width="wrap_content" 

 android:layout_height="wrap_content" 

 android:background="@drawable/next_selecor" 

 android:layout_marginLeft="5dip" 

 /> 

 </LinearLayout>



2.为AppWidget指定布局和样式,即此AppWidget在手机桌面上显示的样式,其实就是一个布局xml文件。需要注意的是使用的组件必须是RemoteViews所支持的,目前原生API中支持的组件如下:

FrameLayout 

LinearLayout 

RelativeLayout 


AnalogClock 

Button 

Chronmeter 

ImageButton 

ImageView 

ProgressB

ar
TextView
如果使用了除此之外的组件,则在Widget创建时会导致android.view.InflateExceptionn异常。这就导致有一些功能或样式无法实现,如很基本的list或文本编辑框都是无法直接实现的。

3.实现AppWidgetProvider。

覆写其中的回调函数,后边会详细介绍。

4.在AndroidManifest.xml中声明(用receiver标签声明)

<receiver android:name="music.dreamer.useful.AppWidget"> 

 <meta-data android:name="android.appwidget.provider" //name固定这么写 

 android:resource="@xml/appwidgetprovider"/>//在xml文件夹下建立的AppWidgetProviderInfo文件 

 <intent-filter> 

 <action android:name="android.appwidget.action.APPWIDGET_UPDATE"></action> //接收什么样的广播,默认的action有4种,对应除onReceive外的4种生命周期函数,也可以自己写。自己写的如以下4个: 


 <action android:name="music.dreamer.playmusic"></action> 

 <action android:name="music.dreamer.pause"></action> 

 <action android:name="music.dreamer.play"></action> 

 <action android:name="music.dreamer.musictitle"></action> 

 </intent-filter> 

 </receiver>



五.在AppWidget中使用控件

1.一个重要的概念

AppWidget和其原本的App并不在同一个进程中,而是运行在HomeScreen进程当中,因此,在控件监听器的绑定,更新等操作都会与以前基本的方法有所不同。

2.PendingIntent

(1)概念

PendingIntent是一个特殊的Intent,实际上它像一个邮包,其中包裹着真正的Intent,当邮包未打开时,Intent是被“挂起”的,所以并不执行,只有当邮包拆开时才会执行。它与Intent的区别在于:Intent 是及时启动,intent 随所在的activity 消失而消失。
PendingIntent 可以看作是对intent的包装,通常通过getActivity,getBroadcast ,getService来得到pendingintent的实例,当前activity并不能马上启动它所包含的intent,而是在外部执行 pendingintent时,调用intent的。正由于pendingintent中 保存有当前App的Context,使它赋予外部App一种能力,使得外部App可以如同当前App一样的执行pendingintent里的 Intent, 就算在执行时当前App已经不存在了,也能通过存在pendingintent里的Context照样执行Intent。另外还可以处理intent执行后的操作。常和alermanger 和notificationmanager一起使用。
Intent一般是用作Activity、Sercvice、BroadcastReceiver之间传递数据,而Pendingintent,一般用在 Notification上,可以理解为延迟执行的intent,PendingIntent是对Intent一个包装。

(2)执行过程

进程A创建PendingIntent,发送给进程B,进程B此事并不执行,直到用户出发某一事件时,包裹被拆开,里面的Intent真正执行。所以Intent什么时候被执行是不知道的,由用户出发事件来决定,整个过程类似回调函数的原理。

(3)创建

有三种方法创建PendingIntent:

getActivity(Context context,int requestCode,Intent intent,int flags) 这时的PendingIntent作用是启动一个新的Activity 


getBroadcast(Context context,int requestCode,Intent intent,int flags) 这时的PendingIntent作用是发送一个广播 


getService(Context context,int requestCode,Intent intent,int flags) 这时的PendingIntent作用是启动一个Service



3.RemoteViews

(1)RemoteViews表示了一系列view对象,即AppWidget所有的控件。

(2)RemoteViews所标示的对象都运行在另外的进程中。

(3)应用

RemoteViews.setOnClickPendingIntent(.widgetButtonId,pendingIntent); //第一个参数就是需要绑定的控件, 


 //第二个参数是点击后触发执行的PendingIntent。 


4.为控件绑定监听器的实现(点击按钮,开启新的Activity) 


 @Override 


 public void onUpdate(Context context, AppWidgetManager appWidgetManager, 


 int[] appWidgetIds) { //appWidgetManager用来管理appWidget,而appWidgetIds[]指的是,每当新建一个appWidget, 


 //系统就会给新建的appWidget一个ID,而这个数组中储存的就是这些ID。 


 // TODO Auto-generated method stub 


 final int N = appWidgetIds.length; 


 for (int i = 0; i < N; i++) { 


 //创建Intent 对象 


 Intent intent=new Intent(context,TargetActivity,.class); 


 //创建PendingIntent 对象,即把intent装进pendingIntent中 


 PendingIntent pendingIntent=new PendingIntent.getActivity(context,0,intent,0); 


 //通过布局文件得到RemoteViews 对象 


 RemoteViews remoteViews=new RemoteViews (context.getPackageName,R.layout.my_AppWidget); 


 //绑定监听事件,即当widgetButtonId控件有onClick事件时,会执行pendingIntent(就是之前所说的拆邮包) 


 remoteViews.setOnClickPendingIntent(.widgetButtonId,pendingIntent); 


 //使用appWidgetManager更新AppWidge。第一个参数指明更新的是哪一个appWidgetId,第二个参数指明了更新的远程Views。 


 appWidgetManager.updateAppWidget(appWidgetIds[i],remoteViews); 


 } 


 super.onUpdate(context,appWidgetManager,appWidgetIds);


}

六.接收AppWidget中的广播(AppWidget与AppWidgetProvider的互动)

1.机制

其实,AppWidgetProvider就是一个广播接收器。在AppWidgetProvider中,pendingIntent传来后由onReceive方法接收,之后检查intent对象中的action,由action来决定分发给哪一个函数。Android默认设置了四种action,对应了除onReceive外剩下的四种方法。因此,我们可以理解为AppWidgetProvider中的方法其实又依赖于onReceive。

2.实现步骤

(1)在AndroidManifest.xml中为AppWidgetProvider注册intent-filter,如前介绍。

(2)使用getBroadcast方法创建一个PendingIntent对象。

(2)为AppWidget中的控件注册处理器(即绑定监听器)。

(4)在onReceive方法中接收广播消息。

3.代码示例(本人写的更新播放按钮的WIDGET)

@Override 

public void onUpdate(Context context, AppWidgetManager appWidgetManager, 

 int[] appWidgetIds) { 

 Log.i("info", "onUpdate..."); 

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

 /*设置播放键的动作*/ 

 views.setImageViewResource(.playButton, R.drawable.play_selecor); 

 Intent playIntent = new Intent(PLAY_ACTION); 


 //设置pendingIntent的作用 

 PendingIntent playPending = PendingIntent.getBroadcast(context, 0, playIntent, 0); 

 views.setOnClickPendingIntent(.playButton, playPending); 

 /*设置上一首按钮的动作*/ 

 Intent lastIntent = new Intent(lAST_ACTION); 

 PendingIntent lastPending = PendingIntent.getBroadcast(context, 0, lastIntent, 0); 

 views.setOnClickPendingIntent(.lastButton, lastPending); 

 /*设置下一首按钮的动作*/ 

 Intent nextIntent = new Intent(NEXT_ACTION); 

 PendingIntent nextPending = PendingIntent.getBroadcast(context, 0, nextIntent, 0); 

 views.setOnClickPendingIntent(.nextButton, nextPending); 


 /*获取正在播放的音乐名*/ 

 Intent intent = new Intent(); 

 intent.setAction(START_APP); 

 context.sendBroadcast(intent); 

 //使用appWidgetManager更新AppWidge。第一个参数指明更新的是哪一个appWidgetId,第二个参数指明了更新的远程Views。 

 appWidgetManager.updateAppWidget(appWidgetIds, views); 

}



之后覆写onReceive方法,实现接收广播后需要执行的操作即可(如“七”将要介绍)。当点击button时,会触发pendingIntent的执行,这时候会发送一个广播,而onReceive会接收广播并执行相应操作。

七.更新AppWidget中控件的状态

1.AppWidget中的更新操作是使用RemoteViews的一系列方法进行的。(比如更换图片:RemoteViews.setImageViewResource等) 


2.更新后应记得使用AppWidgetManager通知AppWidget进行更新。 


3.代码示例 


说明:这里实现的即是点击某按钮,发送一个广播,在onReceive方法里实现更新操作,即需要覆写onReceive方法。 


@Override 

public void onReceive(Context context, Intent intent) { 

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

 if (intent.getAction().equals("music.dreamer.pause")){ 

 views.setImageViewResource(.playButton, R.drawable.play_selecor); 

 } else if (intent.getAction().equals("music.dreamer.play")){ 


//将id为playButton的控件中的图片更新为pause_selecor 


 views.setImageViewResource(.playButton, R.drawable.pause_selecor); 

 } else if (intent.getAction().equals("music.dreamer.musictitle")){ 

 String musicName = intent.getExtras().getString("title"); 

 if (musicName.length()>6){ 

 musicName = musicName.substring(0, 5)+"..."; 

 } 

 views.setTextViewText(.title, musicName); 

 } 

 AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); 

 ComponentName componentName = new ComponentName(context,AppWidget.class); 


 //通知AppWidgetProvider更新 

 appWidgetManager.updateAppWidget(componentName, views); 

 Log.i("info", "onReceive..."); 

 super.onReceive(context, intent); 

}