自定义Widget小组件及原理分析
1.使用
1.1 自定义Widget
创建一个可以被其他进程加载的Widget,就是要把创建的RemoteViews传入AppWidgetManager。
RemoteViews并不是真正的View,它储存着构建View所需的信息,使用Widget的进程获取到RemoteViews后就可以构建Widget了。
public class AlarmWidget extends AppWidgetProvider {
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// widget更新时触发。因为可能有好几个widget实例,所以是appWidgetIds
for (int appWidgetId : appWidgetIds) {
CharSequence widgetText = context.getString(R.string.appwidget_text);
// Construct the RemoteViews object
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.alarm_widget);
views.setTextViewText(R.id.appwidget_text, widgetText);
// Instruct the widget manager to update the widget
appWidgetManager.updateAppWidget(appWidgetId, views);
}
}
@Override
public void onEnabled(Context context) {
// widget可用时触发
}
@Override
public void onDisabled(Context context) {
// widget不可用时触发
}
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
// widget被删除时触发
}
@Override
public void onReceive(Context context, Intent intent) {
// 收到指定的广播时触发。可以启动Service
}
}
别忘记注册到AndroidManifest.xml,需要带上xml定义,里面指定了widget的layout:
<receiver android:name=".widget.AlarmWidget">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/alarm_widget_info" />
</receiver>
RemoteViews支持的布局和控件有限,所以创建布局时需要注意。
然后,在Service中,把自定义Widget注册进AppWidgetManager。如果自定义Widget存在Activity,记得将Service和Activity进行进程分离,降低Service在后台时被kill的概率。
appWidgetManager = AppWidgetManager.getInstance(getApplicationContext());
provider = new ComponentName(getApplicationContext(), AlarmWidget.class);
appWidgetManager.updateAppWidget(provider, remoteViews);
1.2 调用自定义Widget
类图加载widget
//其参数hostid大意是指定该AppWidgetHost 即本Activity的标记Id, 直接设置为一个整数值吧 。
mAppWidgetHost = new AppWidgetHost(MainActivity.this, HOST_ID) ;
//为了保证AppWidget的及时更新 , 必须在Activity的onCreate/onStar方法调用该方法
// 当然可以在onStop方法中,调用mAppWidgetHost.stopListenering() 停止AppWidget更新
mAppWidgetHost.startListening() ;
//获得AppWidgetManager对象
appWidgetManager = AppWidgetManager.getInstance(MainActivity.this) ;
btAddShortCut.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
//显示所有能创建AppWidget的列表 发送此 ACTION_APPWIDGET_PICK 的Action
Intent pickIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_PICK) ;
//向系统申请一个新的appWidgetId ,该appWidgetId与我们发送Action为ACTION_APPWIDGET_PICK
// 后所选择的AppWidget绑定 。 因此,我们可以通过这个appWidgetId获取该AppWidget的信息了
//为当前所在进程申请一个新的appWidgetId
int newAppWidgetId = mAppWidgetHost.allocateAppWidgetId() ;
//作为Intent附加值 , 该appWidgetId将会与选定的AppWidget绑定
pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, newAppWidgetId) ;
//选择某项AppWidget后,立即返回,即回调onActivityResult()方法
startActivityForResult(pickIntent , MY_REQUEST_APPWIDGET) ;
}
});
}
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
if (requestCode == MY_REQUEST_APPWIDGET) {
int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID , AppWidgetManager.INVALID_APPWIDGET_ID) ;
AppWidgetProviderInfo appWidgetProviderInfo = appWidgetManager.getAppWidgetInfo(appWidgetId) ;
AppWidgetHostView hostView = mAppWidgetHost.createView(MainActivity.this, appWidgetId, appWidgetProviderInfo);
int widget_minWidht = appWidgetProviderInfo.minWidth ;
int widget_minHeight = appWidgetProviderInfo.minHeight ;
//设置长宽 appWidgetProviderInfo 对象的 minWidth 和 minHeight 属性
LinearLayout.LayoutParams linearLayoutParams = new LinearLayout.LayoutParams(widget_minWidht, widget_minHeight);
//添加至LinearLayout父视图中
linearLayout.addView(hostView,linearLayoutParams) ;
}
}
2.原理分析
2.1 RemoteViews的序列化
RemoteViews要实现IPC传递,必然是可序列化的:
public class RemoteViews implements Parcelable, Filter {
aidl中定义(/home/ecarx/E02_BOXX1/frameworks/base/core/java/android/widget/RemoteViews.aidl)
package android.widget;
parcelable RemoteViews;
就可以在服务中IPC传递啦:
/home/ecarx/E02_BOXX1/frameworks/base/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
void updateAppWidgetIds(String callingPackage, in int[] appWidgetIds, in RemoteViews views);
2.2 RmoteViews变身AppWidgetHostView
从上面的序列图可以看到,调用AppWidgetHost的createView方法来获取AppWidgetHostView,是先通过AppWidgetService在注册的所有Widget中,识别到对应的Widget类,然后转为RemoteViews返回给AppWidgetHost:
// AppWidgetServiceImpl
private Widget lookupWidgetLocked(int appWidgetId, int uid, String packageName) {
final int N = mWidgets.size();
for (int i = 0; i < N; i++) {
Widget widget = mWidgets.get(i);
if (widget.appWidgetId == appWidgetId
&& mSecurityPolicy.canAccessAppWidget(widget, uid, packageName)) {
return widget;
}
}
return null;
}
然后AppWidgetHost在将RemoteViews组装成AppWidgetHostView,返回给调用者:
//AppWidgetHost
public final AppWidgetHostView createView(Context context, int appWidgetId,
AppWidgetProviderInfo appWidget) {
...
RemoteViews views;
try {
views = sService.getAppWidgetViews(mContextOpPackageName, appWidgetId);
} catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
}
view.updateAppWidget(views);
return view;
}