一 AppWidget的使用:
1、首先在res/layout文件夹下定义一个 布局文件
res/layout/app_widget.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/iv"
android:src="@mipmap/ic_launcher"
/>
LinearLayout>
2、在 res/xml文件夹中新建一个 appwidgetProvider的配置文件
res/xml/appwidget-provider-info.xml
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/app_widget"
android:minHeight="110dp"
android:minWidth="110dp"
android:updatePeriodMillis="8640000"
>
appwidget-provider>
其中initialLayout 代表该AppWidget引用的资源文件
minHeight ,minWidth代表appWidget的大小 这里写110dp代表 站 2x2个方格,
这里有一个公式: n X 70 -30 = ? n代表占多少格。
updatePeriodMillis 代表该appWidget多长时间更新一次,这里单位为毫秒。
3、编写AppWidgetProvider
public class MyAppWidget extends AppWidgetProvider {
private static final String TAG = "MyAppWidget";
public static final String CLICK_ACTION = "com.blueberry.sample.appwidget_CLICK";
@Override
public void onReceive(final Context context, final Intent intent) {
super.onReceive(context, intent);
Log.i(TAG, "onReceiver: action = " + intent.getAction());
if (intent.getAction().equals(CLICK_ACTION)) {
Toast.makeText(context, "click it", Toast.LENGTH_SHORT).show();
new Thread(new Runnable() {
@Override
public void run() {
Bitmap srcBitmap = BitmapFactory.decodeResource(context.getResources(),
R.mipmap.ic_launcher);
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
for (int i = 0; i < 37; i++) {
float degree = (i * 10) % 360;
RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
R.layout.app_widget);
remoteViews.setImageViewBitmap(R.id.iv,
rotateBitmap(srcBitmap, degree));
Intent intentClick = new Intent();
intentClick.setAction(CLICK_ACTION);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context,
0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
remoteViews.setOnClickPendingIntent(R.id.iv, pendingIntent);
appWidgetManager.updateAppWidget(new ComponentName(context,
MyAppWidget.class), remoteViews);
SystemClock.sleep(30);
}
}
}).start();
}
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
Log.i(TAG, "onUpdate.");
final int counter = appWidgetIds.length;
Log.i(TAG, "counter: " + counter);
for (int i = 0; i < counter; i++) {
int appWidgetId = appWidgetIds[i];
onWidgetUpdate(context, appWidgetManager, appWidgetId);
}
}
private void onWidgetUpdate(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
Log.i(TAG, "appWidgetId= " + appWidgetId);
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.app_widget);
Intent intentClick = new Intent();
intentClick.setAction(CLICK_ACTION);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intentClick, 0);
remoteViews.setOnClickPendingIntent(R.id.iv, pendingIntent);
appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
}
private static Bitmap rotateBitmap(Bitmap bitmap, float degree) {
Matrix matrix = new Matrix();
matrix.reset();
matrix.setRotate(degree);
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
}
}
4、在清单文件中注册
<receiver android:name=".widgets.MyAppWidget">
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/appwidget_provider_info">meta-data>
<intent-filter>
<action android:name="com.blueberry.sample.appwidget_CLICK">action>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE">action>
intent-filter>
receiver>
其中com.blueberry.sample.appwidget_CLICK 是我自己定义的一个ACTION.
其实AppWidgetProvider 是一个广播接收器,查看源码可知:
AppWidgetProvider类中在它的onReceiver()方法中,通过判断ACTION,然后执行了几个钩子方法
// BEGIN_INCLUDE(onReceive)
public void onReceive(Context context, Intent intent) {
// Protect against rogue update broadcasts (not really a security issue,
// just filter bad broacasts out so subclasses are less likely to crash).
String action = intent.getAction();
if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null) {
int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
if (appWidgetIds != null && appWidgetIds.length > 0) {
this.onUpdate(context, AppWidgetManager.getInstance(context), appWidgetIds);
}
}
} else if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)) {
final int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);
this.onDeleted(context, new int[] { appWidgetId });
}
} else if (AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)
&& extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS)) {
int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);
Bundle widgetExtras = extras.getBundle(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS);
this.onAppWidgetOptionsChanged(context, AppWidgetManager.getInstance(context),
appWidgetId, widgetExtras);
}
} else if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) {
this.onEnabled(context);
} else if (AppWidgetManager.ACTION_APPWIDGET_DISABLED.equals(action)) {
this.onDisabled(context);
} else if (AppWidgetManager.ACTION_APPWIDGET_RESTORED.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null) {
int[] oldIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS);
int[] newIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
if (oldIds != null && oldIds.length > 0) {
this.onRestored(context, oldIds, newIds);
this.onUpdate(context, AppWidgetManager.getInstance(context), newIds);
}
}
}
}
根据文档可知:
onEnable:当该窗口小部件第一次被添加到桌面时调用该方法,可添加多次但只在第一次调用
onUpdate: 小部件添加时或者每次小部件跟新时都会调用此方法,小部件的跟新时机由updatePeriodMillis来指定,每个周期小部件都会自动更新一次。
onDeleted:每删除一次桌面小部件就会被调用一次。
onDisable: 当最后一个该类型的桌面小部件被删除时调用该方法。
所以在我们自定义小部件时 需要重写public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) 来更新小部件。
这里在更新的时候我们根据布局文件创建出来一个removeViews ,并且我们给我们imageView设置了一个监听事件,点击它然后会发出一个广播,这个广播的action是我们自己定义的。
然后收到这个广播之后,旋转bitmap 然后重新设置给ImageView,并更新小部件。
这里需要注意的是:
1、小部件只支持RemoteView
2、我们使用appWidgetManager 来更新的小部件。
支持RemoteView的控件有:
FrameLayout
LinearLayout
RelativeLayout
GridLayout
AnalogClock
Button
Chronometer
ImageButton
ImageView
ProgressBar
TextView
ViewFlipper
ListView
GridView
StackView
AdapterViewFlipper
具体请参考:https://developer.android.com/guide/topics/appwidgets/index.html
二、原理分析
上段我们讲到,appWidget只支持RemoteViews 和他需要使用mAppWidgetManager来更新小部件,这是为什么呢?
原因是,小部件跟我们的应用并非在同一个进程。所以我们要夸进程来更新小部件。而RemoteView支持夸进程,而mAppWidgetManager底层正是Binder。它对应的是AppWidgetServiceImpl。
我们在使用mAppWidgetManger来更新小部件时调用:
public void updateAppWidget(ComponentName provider, RemoteViews views) {
if (mService == null) {
return;
}
try {
mService.updateAppWidgetProvider(provider, views);
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
}
}
它调用了 mService.updateAppWidgetProvider(provider, views);
而mService实现是 AppWidgetServiceImpl
也就是说,它调用了 AppWidgetServiceImpl 的
public void updateAppWidgetProvider(ComponentName componentName, RemoteViews views) {
final int userId = UserHandle.getCallingUserId();
if (DEBUG) {
Slog.i(TAG, "updateAppWidgetProvider() " + userId);
}
// Make sure the package runs under the caller uid.
mSecurityPolicy.enforceCallFromPackage(componentName.getPackageName());
synchronized (mLock) {
ensureGroupStateLoadedLocked(userId);
// NOTE: The lookup is enforcing security across users by making
// sure the caller can access only its providers.
ProviderId providerId = new ProviderId(Binder.getCallingUid(), componentName);
Provider provider = lookupProviderLocked(providerId);
if (provider == null) {
Slog.w(TAG, "Provider doesn't exist " + providerId);
return;
}
ArrayList instances = provider.widgets;
final int N = instances.size();
for (int i = 0; i < N; i++) {
Widget widget = instances.get(i);
updateAppWidgetInstanceLocked(widget, views, false);
}
}
}
继续看 updateAppWidgetInstanceLocked(widget, views, false);
private void updateAppWidgetInstanceLocked(Widget widget, RemoteViews views,
boolean isPartialUpdate) {
if (widget != null && widget.provider != null
&& !widget.provider.zombie && !widget.host.zombie) {
if (isPartialUpdate && widget.views != null) {
// For a partial update, we merge the new RemoteViews with the old.
widget.views.mergeRemoteViews(views);
} else {
// For a full update we replace the RemoteViews completely.
widget.views = views;
}
scheduleNotifyUpdateAppWidgetLocked(widget, views);
}
}
private void scheduleNotifyUpdateAppWidgetLocked(Widget widget, RemoteViews updateViews) {
if (widget == null || widget.provider == null || widget.provider.zombie
|| widget.host.callbacks == null || widget.host.zombie) {
return;
}
SomeArgs args = SomeArgs.obtain();
args.arg1 = widget.host;
args.arg2 = widget.host.callbacks;
args.arg3 = updateViews;
args.argi1 = widget.appWidgetId;
mCallbackHandler.obtainMessage(
CallbackHandler.MSG_NOTIFY_UPDATE_APP_WIDGET,
args).sendToTarget();
}
这里使用了一个Handler发送了一个消息,它的接收代码:
@Override
public void handleMessage(Message message) {
switch (message.what) {
case MSG_NOTIFY_UPDATE_APP_WIDGET: {
SomeArgs args = (SomeArgs) message.obj;
Host host = (Host) args.arg1;
IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2;
RemoteViews views = (RemoteViews) args.arg3;
final int appWidgetId = args.argi1;
args.recycle();
handleNotifyUpdateAppWidget(host, callbacks, appWidgetId, views);
} break;
private void handleNotifyUpdateAppWidget(Host host, IAppWidgetHost callbacks,
int appWidgetId, RemoteViews views) {
try {
callbacks.updateAppWidget(appWidgetId, views);
} catch (RemoteException re) {
synchronized (mLock) {
Slog.e(TAG, "Widget host dead: " + host.id, re);
host.callbacks = null;
}
}
}
这里调用了 callbacks.updateAppWidget(appWidgetId, views);
这里的callback实际又是个binder,它的实现是AppWidgetHost.Callbacks
public class AppWidgetHost {
class Callbacks extends IAppWidgetHost.Stub {
public void updateAppWidget(int appWidgetId, RemoteViews views) {
if (isLocalBinder() && views != null) {
views = views.clone();
}
Message msg = mHandler.obtainMessage(HANDLE_UPDATE, appWidgetId, 0, views);
msg.sendToTarget();
可以看到它的updateAppWidget ,发送了一个消息来更新小部件,它的接收程序为:
class UpdateHandler extends Handler {
public UpdateHandler(Looper looper) {
super(looper);
}
public void handleMessage(Message msg) {
switch (msg.what) {
case HANDLE_UPDATE: {
updateAppWidgetView(msg.arg1, (RemoteViews)msg.obj);
break;
}
void updateAppWidgetView(int appWidgetId, RemoteViews views) {
AppWidgetHostView v;
synchronized (mViews) {
v = mViews.get(appWidgetId);
}
if (v != null) {
v.updateAppWidget(views);
}
}
public void updateAppWidget(RemoteViews remoteViews) {
......
int layoutId = remoteViews.getLayoutId();
if (content == null && layoutId == mLayoutId) {
try {
// 调用这里
remoteViews.reapply(mContext, mView, mOnClickHandler);
content = mView;
recycled = true;
if (LOGD) Log.d(TAG, "was able to recycled existing layout");
} catch (RuntimeException e) {
exception = e;
}
}
if (content == null) {
try {
//调用这里
content = remoteViews.apply(mContext, this, mOnClickHandler);
if (LOGD) Log.d(TAG, "had to inflate new layout");
} catch (RuntimeException e) {
exception = e;
}
}
....
}
之类可以看到 如果 content ==null 的时候他调用 remoteViews.apply(…)方法
如果content!=null 它调用remoteViews.reApply(…)方法
那我们就接着看RemoteViews;
public class RemoteViews implements Parcelable, Filter {
可以看到它实现了Parcelable接口,所以可以序列化在进程间传递。
我们直接看它的apply方法
public View apply(Context context, ViewGroup parent, OnClickHandler handler) {
RemoteViews rvToApply = getRemoteViewsToApply(context);
View result;
final Context contextForResources = getContextForResources(context);
Context inflationContext = new ContextWrapper(context) {
@Override
public Resources getResources() {
return contextForResources.getResources();
}
@Override
public Resources.Theme getTheme() {
return contextForResources.getTheme();
}
};
LayoutInflater inflater = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater = inflater.cloneInContext(inflationContext);
inflater.setFilter(this);
result = inflater.inflate(rvToApply.getLayoutId(), parent, false);
rvToApply.performApply(result, parent, handler);
return result;
}
private void performApply(View v, ViewGroup parent, OnClickHandler handler) {
if (mActions != null) {
handler = handler == null ? DEFAULT_ON_CLICK_HANDLER : handler;
final int count = mActions.size();
for (int i = 0; i < count; i++) {
Action a = mActions.get(i);
a.apply(v, parent, handler);
}
}
}
可以看到它遍历一个 mApcitions结合然后依次 调用他们的 apply方法。
那么这些action是怎么被添加到这个集合中的呢?
我们在给RemoteViews中的TextView、ImageView设置属性的时候都使用remoteViews中类似这样的方法: setImageViewBitmap(int viewId, Bitmap bitmap),这种方法实际就创建出了action来添加到 mAction中
我们就看一下这个方法:
public void setImageViewBitmap(int viewId, Bitmap bitmap) {
setBitmap(viewId, "setImageBitmap", bitmap);
}
public void setBitmap(int viewId, String methodName, Bitmap value) {
addAction(new BitmapReflectionAction(viewId, methodName, value));
}
BitmapReflectionAction 实际就是一个Action
private class BitmapReflectionAction extends Action {
....
@Override
public void apply(View root, ViewGroup rootParent,
OnClickHandler handler) throws ActionException {
ReflectionAction ra = new ReflectionAction(viewId, methodName, ReflectionAction.BITMAP,
bitmap);
ra.apply(root, rootParent, handler);
}
.....
}
这个又new 出了一个ReflectionAction 来调用它的apply(),我们追踪到这个类可以看到:
@Override
public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
final View view = root.findViewById(viewId);
if (view == null) return;
Class> param = getParameterType();
if (param == null) {
throw new ActionException("bad type: " + this.type);
}
try {
getMethod(view, this.methodName, param).invoke(view, wrapArg(this.value));
} catch (ActionException e) {
throw e;
} catch (Exception ex) {
throw new ActionException(ex);
}
}
它使用了反射,最后终执行了setImageView方法。这样就完成了更新view操作。
RemoteViews.reapply() 也是同理,这里不再叙述了。