在前面两篇文章RemoteViews的基本使用(上)之通知栏 ,RemoteViews的基本使用(下)之窗口小部件 中讲述了RemoteViews的两个应用场景,这篇文章主要介绍RemoteViews的内部机制,以及一个小扩展,使用RemoteViews实现跨进程操作界面。本篇文章以窗口小部件为例,来分析RemoteViews如何实现跨进程操作界面。我们都知道在将小部件列表中将窗口小部件拖到桌面,会调用onUpdate方法,在该方法中会调用AppWidgetManager.updateAppWidget(appWidgetIds,remoteViews)来更新窗口小部件,调用RemoteViews方法的一些set..方法,修改窗口小部件的界面。对于这些不是很清楚的哥们,可以查看文章RemoteViews的基本使用(下)之窗口小部件 ,这篇文章对窗口小部件做了简单的介绍,本篇文章主要从源码角度分析RemoteViews,对窗口小部件的生命周期以及使用不再阐述。
public void updateAppWidget(int[] appWidgetIds, RemoteViews views) {
try {
sService.updateAppWidgetIds(appWidgetIds, views, mContext.getUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
}
}
public static AppWidgetManager getInstance(Context context) {
synchronized (sManagerCache) {
if (sService == null) {
IBinder b = ServiceManager.getService(Context.APPWIDGET_SERVICE);
sService = IAppWidgetService.Stub.asInterface(b);
}
WeakReference ref = sManagerCache.get(context);
AppWidgetManager result = null;
if (ref != null) {
result = ref.get();
}
if (result == null) {
result = new AppWidgetManager(context);
sManagerCache.put(context, new WeakReference(result));
}
return result;
}
}
sService是一个代理对象,updateAppWidgetIds方法的真正调用在服务里,IAppWidgetService是一个AIDL接口,需要找到继承IAppWidgetService.Stub的那个类,这里直接告诉大家该类是AppWidgetService。
public void updateAppWidgetIds(int[] appWidgetIds, RemoteViews views) {
if (appWidgetIds == null) {
return;
}
if (appWidgetIds.length == 0) {
return;
}
final int N = appWidgetIds.length;
synchronized (mAppWidgetIds) {
for (int i=0; i updatedViews) {
int callingUid = enforceCallingUid(packageName);
synchronized (mAppWidgetIds) {
Host host = lookupOrAddHostLocked(callingUid, packageName, hostId);
host.callbacks = callbacks;
updatedViews.clear();
ArrayList instances = host.instances;
int N = instances.size();
int[] updatedIds = new int[N];
for (int i=0; i
最后会调用id.host.callbacks.updateAppWidget(id.appWidgetId, views),需要找到callbacks的实例化位置,上面代码已经给出答案,调用AppWidgetService$startListening方法会实例化callbacks对象。那么,谁调用了AppWidgetService$startListening方法呢。
public void startListening() {
int[] updatedIds;
ArrayList updatedViews = new ArrayList();
try {
if (mPackageName == null) {
mPackageName = mContext.getPackageName();
}
updatedIds = sService.startListening(mCallbacks, mPackageName, mHostId, updatedViews);
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
}
final int N = updatedIds.length;
for (int i=0; i
sService对象就是AppWidgetService,代理对象sService.startListening(mCallbacks, mPackageName, mHostId, updatedViews)的调用,基于底层Binder机制,调用远程服务的startListening方法,也就是AppWidgetService$startListening。
int mHostId;
Callbacks mCallbacks = new Callbacks();
final HashMap mViews = new HashMap();
public AppWidgetHost(Context context, int hostId) {
mContext = context;
mHostId = hostId;
mHandler = new UpdateHandler(context.getMainLooper());
synchronized (sServiceLock) {
if (sService == null) {
IBinder b = ServiceManager.getService(Context.APPWIDGET_SERVICE);
sService = IAppWidgetService.Stub.asInterface(b);
}
}
}
sService = IAppWidgetService.Stub.asInterface(b);获取的不就是AppWidgetService的代理对象么。
class Callbacks extends IAppWidgetHost.Stub {
public void updateAppWidget(int appWidgetId, RemoteViews views) {
Message msg = mHandler.obtainMessage(HANDLE_UPDATE);
msg.arg1 = appWidgetId;
msg.obj = views;
msg.sendToTarget();
}
public void providerChanged(int appWidgetId, AppWidgetProviderInfo info) {
Message msg = mHandler.obtainMessage(HANDLE_PROVIDER_CHANGED);
msg.arg1 = appWidgetId;
msg.obj = info;
msg.sendToTarget();
}
}
代码比较简单,发送了一个消息到消息队列里,接下来是处理消息,源码如下:
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;
}
case HANDLE_PROVIDER_CHANGED: {
onProviderChanged(msg.arg1, (AppWidgetProviderInfo)msg.obj);
break;
}
}
}
}
//继续查看方法updateAppWidgetView
void updateAppWidgetView(int appWidgetId, RemoteViews views) {
AppWidgetHostView v;
synchronized (mViews) {
v = mViews.get(appWidgetId);
}
if (v != null) {
v.updateAppWidget(views);
}
}
这里的v就是AppWidgetHostView,继续查看AppWidgetHostView$updateAppWidget源码如下:
/**
* Process a set of {@link RemoteViews} coming in as an update from the
* AppWidget provider. Will animate into these new views as needed
*/
public void updateAppWidget(RemoteViews remoteViews) {
if (LOGD) Log.d(TAG, "updateAppWidget called mOld=" + mOld);
boolean recycled = false;
View content = null;
Exception exception = null;
// Capture the old view into a bitmap so we can do the crossfade.
if (CROSSFADE) {
if (mFadeStartTime < 0) {
if (mView != null) {
final int width = mView.getWidth();
final int height = mView.getHeight();
try {
mOld = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
} catch (OutOfMemoryError e) {
// we just won't do the fade
mOld = null;
}
if (mOld != null) {
//mView.drawIntoBitmap(mOld);
}
}
}
}
if (remoteViews == null) {
if (mViewMode == VIEW_MODE_DEFAULT) {
// We've already done this -- nothing to do.
return;
}
content = getDefaultView();
mLayoutId = -1;
mViewMode = VIEW_MODE_DEFAULT;
} else {
// Prepare a local reference to the remote Context so we're ready to
// inflate any requested LayoutParams.
mRemoteContext = getRemoteContext(remoteViews);
int layoutId = remoteViews.getLayoutId();
// If our stale view has been prepared to match active, and the new
// layout matches, try recycling it
if (content == null && layoutId == mLayoutId) {
try {
remoteViews.reapply(mContext, mView);
content = mView;
recycled = true;
if (LOGD) Log.d(TAG, "was able to recycled existing layout");
} catch (RuntimeException e) {
exception = e;
}
}
// Try normal RemoteView inflation
if (content == null) {
try {
content = remoteViews.apply(mContext, this);
if (LOGD) Log.d(TAG, "had to inflate new layout");
} catch (RuntimeException e) {
exception = e;
}
}
mLayoutId = layoutId;
mViewMode = VIEW_MODE_CONTENT;
}
if (content == null) {
if (mViewMode == VIEW_MODE_ERROR) {
// We've already done this -- nothing to do.
return ;
}
Log.w(TAG, "updateAppWidget couldn't find any view, using error view", exception);
content = getErrorView();
mViewMode = VIEW_MODE_ERROR;
}
if (!recycled) {
prepareView(content);
addView(content);
}
if (mView != content) {
removeView(mView);
mView = content;
}
if (CROSSFADE) {
if (mFadeStartTime < 0) {
// if there is already an animation in progress, don't do anything --
// the new view will pop in on top of the old one during the cross fade,
// and that looks okay.
mFadeStartTime = SystemClock.uptimeMillis();
invalidate();
}
}
}
注意看49行,61行,分别调用remoteViews.reapply(mContext, mView),content = remoteViews.apply(mContext, this),调用了RemoteViews的apply加载或更新界面,调用RemoteViews的reapply方法更新界面,但不能加载。以本篇文章为例,界面指的是在launcher应用上显示的窗口小部件。
public void setTextViewText(int viewId, CharSequence text) {
setCharSequence(viewId, "setText", text);
}
//继续看setCharSequence
public void setCharSequence(int viewId, String methodName, CharSequence value) {
addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value));
}
//继续看addAction
private void addAction(Action a) {
if (hasLandscapeAndPortraitLayouts()) {
throw new RuntimeException("RemoteViews specifying separate landscape and portrait" +
" layouts cannot be modified. Instead, fully configure the landscape and" +
" portrait layouts individually before constructing the combined layout.");
}
if (mActions == null) {
mActions = new ArrayList();
}
mActions.add(a);
// update the memory usage stats
a.updateMemoryUsageEstimate(mMemoryUsageCounter);
}
从上面代码可知:setTextViewText方法做了这样一件事,用Action将控件的id,text的值封装起来,并将Action对象放入到list集合中。后面肯定要取出集合里的元素进行相应处理,那么是在哪呢,后面会给出答案。
private abstract static class Action implements Parcelable {
public abstract void apply(View root, ViewGroup rootParent,
OnClickHandler handler) throws ActionException;
//...code
}
可以发现Action是一个抽象类,并实现了Parcelable接口,有一个很重要的抽象方法apply。Action有很多子类,如TextViewSizeAction,ReflectionAction等,setTextViewText方法中的Action是ReflectionAction,setTextViewTextSize方法中Action是TextViewSizeAction。也就是说,在调用remoteViews.setXXX方法时,对控件的操作的数据封装在Action中。每调用一次remoteViews.setXXX方法,就将对应Action对象存入list集合中,然后统一交给AppWidgetService处理。
public View apply(Context context, ViewGroup parent) {
return apply(context, parent, null);
}
/** @hide */
public View apply(Context context, ViewGroup parent, OnClickHandler handler) {
RemoteViews rvToApply = getRemoteViewsToApply(context);
View result;
Context c = prepareContext(context);
LayoutInflater inflater = (LayoutInflater)
c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater = inflater.cloneInContext(c);
inflater.setFilter(this);
result = inflater.inflate(rvToApply.getLayoutId(), parent, false);
rvToApply.performApply(result, parent, handler);
return result;
}
//继续看performApply
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);
}
}
}
result = inflater.inflate(rvToApply.getLayoutId(), parent, false)就是加载布局文件,创建RemoteViews对象时会给字段mLayoutId赋值,即rvToApply.getLayoutId()的返回值;parent是AppWidgetHostView。
private class ReflectionAction extends Action {
ReflectionAction(int viewId, String methodName, int type, Object value) {
this.viewId = viewId;
this.methodName = methodName;
this.type = type;
this.value = value;
}
//...code
@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);
}
Class klass = view.getClass();
Method method;
try {
method = klass.getMethod(this.methodName, getParameterType());
}
catch (NoSuchMethodException ex) {
throw new ActionException("view: " + klass.getName() + " doesn't have method: "
+ this.methodName + "(" + param.getName() + ")");
}
if (!method.isAnnotationPresent(RemotableViewMethod.class)) {
throw new ActionException("view: " + klass.getName()
+ " can't use method with RemoteViews: "
+ this.methodName + "(" + param.getName() + ")");
}
try {
//noinspection ConstantIfStatement
if (false) {
Log.d(LOG_TAG, "view: " + klass.getName() + " calling method: "
+ this.methodName + "(" + param.getName() + ") with "
+ (this.value == null ? "null" : this.value.getClass().getName()));
}
method.invoke(view, this.value);
}
catch (Exception ex) {
throw new ActionException(ex);
}
}
}
第12行,findViewById获取控件引用;第20,23,43行,使用反射技术更新该控件。有兴趣的哥们可以研究下TextViewSizeAction的apply方法,它更新界面并不是用反射,在findViewById获取到控件引用后,调用view.setTextSize(..)更新界面。
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void clickButton(View v) {
//发送广播
Intent intent = new Intent();
intent.setAction("com.example.remoteview");
RemoteViews rv = new RemoteViews(getPackageName(), R.layout.rv_layout);
rv.setTextViewText(R.id.tv, "hello, from Sender");
intent.putExtra("remoteview", rv);
sendBroadcast(intent);
}
}
rv_layout.xml
public class MainActivity extends Activity {
private RelativeLayout rl;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
public void init() {
IntentFilter filter = new IntentFilter();
filter.addAction("com.example.remoteview");
MyReceiver receiver = new MyReceiver();
registerReceiver(receiver, filter);
rl = (RelativeLayout) findViewById(R.id.rl);
}
private class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if ("com.example.remoteview".equals(intent.getAction())) {
RemoteViews remoteViews = intent.getParcelableExtra("remoteview");
View view = remoteViews.apply(context, rl);
rl.addView(view);
}
}
}
}
activity_main.xml
需要注意的是,应用RvReceiver中并不需要rv_layout.xml资源文件。在应用RvSender中点击按钮后,应用RvReceiver界面如下: