一、RemoteViews內部機制
RemoteViews的作用在其他進程中顯示並且更新View的界面,爲了更好地理解它的內部機制,我們來看一下他的主要功能。
首先我們看一下他的構造方法,這裏介紹一個最常用的構造方法
public RemoteViews(String packageName, int layoutId) {
this(getApplicationInfo(packageName, UserHandle.myUserId()), layoutId);
}
他接受兩個參數,第一個表示當前應用的包名,第二個參數表示待加載的佈局文件,這個很好理解,RemoteViews目前並不能支持所有的View類型,支持的類型有:
- Layout
FrameLayout LinearLayout RelativeLayout GridLayout
- View
AnalogClock,Button,Chronometer,ImageButton,ImageView,ProgressBar,TextView,ViewFlipper ListView,GridView,stackView,AdapterViewFlipper,ViewStub
上面的描述是RemoteViews所支持所有View類型,RemoteViews不支持他們的子類以及其他View的類型,也就是說RemoteViews中不能使用除了上述列表中以外的View,也無法使用自定義View。比如我們在通知欄的RemoteViews中使用系統的EditText,那麼通知欄消息無法將彈出並且會拋出異常。
RemoteViews沒有提供findViewById方法,因此無法直接訪問裏面的View元素,而必須通過RemoteViews所提供的一系列set方法來完成,當然這是因爲RemoteViews在遠程進程中顯示,所以沒辦法直接findViewById。關於set方法,可以看下這表
從這張表可以看出,原本可以直接調用View的方法,現在都需要通過set來完成,而從這些方法的聲明來看,很像是通過反射來完成的,事實大部分set方法的確是通過反射來完成的。
下面描述一下RemoteViews的內部機制,由於RemoteViews主要用於通知欄和桌面小部件中,這裏就通過它們來分析RemoteViews的工作過程。我們知道,通知欄和小部件分別由NotificationManager和AppWidgetProvider管理,**而NotificationManager和AppWidgetProvider通過Binder分別和SystemService進程中的NotificationManagerService以及AppWidgetService進行通信。**由此可見,通知欄和小部件中的佈局文件實際上是在NotificationManagerService以及AppWidgetService中被加載的,而它們運行在系統的SystemServer中,這就和我們的進程構成了跨進程通信的場景。
上面從理論上分析了RemoteViews的內部機制,接下來我們從源碼的角度分析下Remoteviews的工作流程。它的構造方法就不用多說了,這裏我們首先看他一系列的set方法,比如setTextViewText
public void setTextViewText(int viewId, CharSequence text) {
setCharSequence(viewId, "setText", text);
}
上面的代碼中,viewId是被操作的View的id,“setText”是一個方法名,text是要給TextView設置的文本,這裏可以聯想一下TextView的setText方法,是不是很一致呢?接下來再看setCharSequence的實現:
public void setCharSequence(int viewId, String methodName, CharSequence value) {
addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value));
}
從setCharSequence的實現可以看出,它的內部並沒有對View進行直接的處理,而是添加了一個ReflectionAction對象,從名字來看,這應該是一個反射類型的動作。再看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<Action>();
}
mActions.add(a);
// update the memory usage stats
a.updateMemoryUsageEstimate(mMemoryUsageCounter);
}
public View apply(Context context, ViewGroup parent, OnClickHandler handler) {
RemoteViews rvToApply = getRemoteViewsToApply(context);
View result;
// RemoteViews may be built by an application installed in another
// user. So build a context that loads resources from that user but
// still returns the current users userId so settings like data / time formats
// are loaded without requiring cross user persmissions.
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);
// Clone inflater so we load resources from correct context and
// we don't add a filter to the static version returned by getSystemService.
inflater = inflater.cloneInContext(inflationContext);
inflater.setFilter(this);
result = inflater.inflate(rvToApply.getLayoutId(), parent, false);
rvToApply.performApply(result, parent, handler);
return result;
}
從上面的代碼可以看出,首先會通過LayoutInflater 去加載RemoteViews中的佈局文件,RemoteViews中的佈局文件可以通過getLayoutId這個方法獲得,加載完佈局之後會通過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);
}
}
}
private void updateNotificationViews(NotificationData.Entry entry,
StatusBarNotification notification, boolean isHeadsUp) {
final RemoteViews contentView = notification.getNotification().contentView;
final RemoteViews bigContentView = isHeadsUp
? notification.getNotification().headsUpContentView
: notification.getNotification().bigContentView;
final Notification publicVersion = notification.getNotification().publicVersion;
final RemoteViews publicContentView = publicVersion != null ? publicVersion.contentView
: null;
// Reapply the RemoteViews
contentView.reapply(mContext, entry.expanded, mOnClickHandler);
...
}
很顯然,上述代碼表示當通知欄需要更新的時候,會通過RemoteViews的reapply方法來更新
接下來我們看一下AppWidgetHostView的updateAppWidget方法
mRemoteContext = getRemoteContext();
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, mOnClickHandler);//看到了也是調用了reapply方法來更新界面的
content = mView;
recycled = true;
if (LOGD) Log.d(TAG, "was able to recycle existing layout");
} catch (RuntimeException e) {
exception = e;
}
}
// Try normal RemoteView inflation
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;
}
}
總結下:view的apply方法,最終調用到了action中的apply方法。
瞭解了apply和reapply的作用之後,我們繼續看Action的子類具體實現,首先看下ReflectionAction的具體實現,它的源碼如下:
private final class ReflectionAction extends Action {
String methodName;
int type;
Object value;
ReflectionAction(int viewId, String methodName, int type, Object value) {
this.viewId = viewId;
this.methodName = methodName;
this.type = type;
this.value = value;
}
@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);
}
}
}
通過上述的代碼可以看出,ReflectionAction 表示的是一個反射的動作,他通過對View的操作會以反射的方式調用,其中getMethod就是根據方法名來得到放射所需要的Method對象。使用ReflectionAction的set方法有setTextViewText、setBoolean、setLong等。除了ReflectionAction 還有其它Action,比如TextViewSizeAction,具體實現如下:
private class TextViewSizeAction extends Action {
public TextViewSizeAction(int viewId, int units, float size) {
this.viewId = viewId;
this.units = units;
this.size = size;
}
public TextViewSizeAction(Parcel parcel) {
viewId = parcel.readInt();
units = parcel.readInt();
size = parcel.readFloat();
}
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(TEXT_VIEW_SIZE_ACTION_TAG);
dest.writeInt(viewId);
dest.writeInt(units);
dest.writeFloat(size);
}
@Override
public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
final TextView target = root.findViewById(viewId);
if (target == null) return;
target.setTextSize(units, size);
}
public String getActionName() {
return "TextViewSizeAction";
}
int units;
float size;
}
到這裏就基本瞭解了RemoteViews的內部機制
自己總結下:實際就是一個Binder的IPC過程。通過將一系列的操作封裝成Action動作集合,Binder傳輸到遠程進程,遠程進程調用action裏面的apply方法來更新View(裏面可以是反射方法名稱,或者通過特定的Action來實現)。(這樣看起來就像是我們自己更新了一樣)
二、RemoteViews的意義
public class BActivity extends Activity implements View.OnClickListener {
private Button bt;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_b);
bt = (Button) findViewById(R.id.bt);
bt.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.bt:
RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.layout_item);
remoteViews.setTextViewText(R.id.textView1, "Hello");
remoteViews.setImageViewResource(R.id.imageview1, R.mipmap.ic_launcher);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, TestActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
remoteViews.setOnClickPendingIntent(R.id.imageview1, pendingIntent);
Intent intent = new Intent("action");
intent.putExtra("remoteViews",remoteViews);
sendBroadcast(intent);
break;
}
}
}
A的代碼比較簡單只是接收一個廣播就行
public class AActivity extends Activity {
private LinearLayout mLinearLayout;
private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
RemoteViews remoteViews = intent.getParcelableExtra("remoteViews");
if (remoteViews != null) {
updateUI(remoteViews);
}
}
};
private void updateUI(RemoteViews remoteViews) {
View view = remoteViews.apply(this, mLinearLayout);
mLinearLayout.addView(view);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_a);
initView();
}
private void initView() {
mLinearLayout = (LinearLayout) findViewById(R.id.mLinearLayout);
IntentFilter intent = new IntentFilter("action");
registerReceiver(mBroadcastReceiver, intent);
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(mBroadcastReceiver);
}
}
int layoutId = getResources().getIdentifier("layout_simulated_notification","layout",getPackageName());
View view = getLayoutInflater().inflate(layoutId,mLinearLayout,false);
remoteViews.reapply(this,view);
mLinearLayout.addView(view);
到這裏我們對Android的整個View的體系應該有了個比較深入的認識了。