一、什麼是RemoteViews
RemoteViews翻譯過來就是遠程視圖.顧名思義,RemoteViews不是當前進程的View,是屬於SystemServer進程.應用程序與RemoteViews之間依賴Binder實現了進程間通信.
二、RemoteViews的用法
RemoteViews使用最多的場合是通知欄和桌面小插件. 以通知欄爲例,講解下它的用法.
1、新建一個Notification
這裏要注意是在android3.0之前都是使用如下的形式構建一個Notification
// 1.新建一個Notification對象
Notification mNotification = new Notification();
// 2.添加屬性,比如標題、內容、優先級、圖片等
mNotification.tickerText = "這是通知欄的標題";
mNotification.icon = R.drawable.ic_launcher;
mNotification.flags=Notification.FLAG_NO_CLEAR;
mNotification.setLatestEventInfo(this, "這是內容", "這是標題", null);
在3.0之後官方推薦使用建造者模式創建Notification.
Notification mNotification = new Notification.Builder(this)
.setContentTitle("這是標題 ")
.setContentText("這是內容")
.setSmallIcon(R.drawable.ic_launcher)
.build();
Notification有很多屬性,這裏列舉一些
- setContentTitle 設置標題
- setContentText 設置內容
- setLargeIcon 設置通知欄大圖標
- setSmallIcon 設置通知欄小圖標
- setContent 設置RemoteViews
- setContentIntent 當通知條目被點擊,就執行這個被設置的Intent.
- setDeleteIntent 當用戶點擊"Clear All Notifications"按鈕區刪除所有的通知的時候,這個被設置的Intent被執行
- setLights 設置閃光燈
- setSound 設置聲音
- setPriority 設置優先級
2、設置Notification的RemoteViews
如果要給通知欄使用自定義佈局就要使用RemoteViews了,傳入包名和相應的佈局.
RemoteViews mRemoteViews=new RemoteViews("com.example.remoteviewdemo", R.layout.remoteview_layout);
然後通過setContent()傳入RemoteViews 對象即可.
這裏順便講一下PendingIntent,PendingIntent是”延遲意圖”的意思,就是當滿足某一條件時出觸發這個Intent.通過PendingIntent的getActivity、getBroadcast、getService等分別構建一個打開對應組件的延遲Intent.
傳入四個參數,context、intent、requestCode(自定義)、flag.
Intent intent=new Intent(MainActivity.this,MainActivity.class);
PendingIntent mPendingIntent=PendingIntent.getActivity(MainActivity.this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
PendingIntent有4種flag.
- FLAG_ONE_SHOT 只執行一次
- FLAG_NO_CREATE 若描述的Intent不存在則返回NULL值
- FLAG_CANCEL_CURRENT 如果描述的PendingIntent已經存在,則在產生新的Intent之前會先取消掉當前的
- FLAG_UPDATE_CURRENT 總是執行,這個flag用的最多
3、獲取通知管理者
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
4、彈出通知
調用notify方法,傳入一個id(自定義)和通知實例即可.
manager.notify(1, mNotification);
5、例子
我用一個按鈕彈出通知,點擊這個通知時進入到該Activity
public class MainActivity extends Activity {
private NotificationManager manager;
private Notification mNotification;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//1.創建RemoteViews實例
RemoteViews mRemoteViews=new RemoteViews("com.example.remoteviewdemo", R.layout.remoteview_layout);
//2.構建一個打開Activity的PendingIntent
Intent intent=new Intent(MainActivity.this,MainActivity.class);
PendingIntent mPendingIntent=PendingIntent.getActivity(MainActivity.this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
//3.創建一個Notification
mNotification = new Notification.Builder(this)
.setSmallIcon(R.drawable.ic_launcher)
.setContentIntent(mPendingIntent)
.setContent(mRemoteViews)
.build();
//4.獲取NotificationManager
manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Button button1 = (Button) findViewById(R.id.button1);
button1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//彈出通知
manager.notify(1, mNotification);
}
});
}
}
如下圖
6、改變RemoteViews的佈局
RemoteViews並不能直接獲得控件實例,然後對控件進行操作.它提供了
setTextViewText(viewId, text)、setImageViewResource(viewId, srcId)等方法進行操作,傳入控件id和相應的修改內容.
列舉一下常用的屬性
- setTextViewText(viewId, text) 設置文本
- setTextColor(viewId, color) 設置文本顏色
- setTextViewTextSize(viewId, units, size) 設置文本大小
- setImageViewBitmap(viewId, bitmap) 設置圖片
- setImageViewResource(viewId, srcId) 根據圖片資源設置圖片
- setViewPadding(viewId, left, top, right, bottom) 設置Padding間距
- setOnClickPendingIntent(viewId, pendingIntent) 設置點擊事件
我這裏就以setTextViewText改變文本的屬性來講解改變RemoteViews的原理.
我在原來的代碼上加上一個按鈕點擊改變內容
Button button2 = (Button) findViewById(R.id.button2);
button2.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mRemoteViews.setTextViewText(R.id.remote_content, "改變了內容");
manager.notify(1, mNotification);
}
});
看下效果
三、RemoteViews的改變原理
1.setTextViewText方法代碼如下
public class RemoteViews implements Parcelable, Filter {
......
public void setTextViewText(int viewId, CharSequence text) {
setCharSequence(viewId, "setText", text);
}
......
}
2.調用了setCharSequence方法
public class RemoteViews implements Parcelable, Filter {
......
public void setCharSequence(int viewId, String methodName, CharSequence value) {
addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value));
}
......
}
3.在setCharSequence方法裏調用了addAction方法,傳入一個ReflectionAction實例,ReflectionAction繼承自Action,它是用反射調用的
private final 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;
}
......
}
4.看下addAction方法,用了一個集合來保存Action實例,然後更新已使用內存的統計情況
public class RemoteViews implements Parcelable, Filter {
......
private void addAction(Action a) {
if (mActions == null) {
mActions = new ArrayList<Action>();
}
//添加Action
mActions.add(a);
// 更新已使用內存的統計情況
a.updateMemoryUsageEstimate(mMemoryUsageCounter);
}
......
}
這一步之後,會調用
manager.notify(1, mNotification);
來更新,追蹤這個notify方法.
public class NotificationManager
{
......
public void notify(String tag, int id, Notification notification)
{
......
INotificationManager service = getService();
try {
service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
stripped, idOut, UserHandle.myUserId());
......
}
......
}
5.上面會調用getService方法返回INotificationManager這個系統服務,它是在SystemServer進程添加的.然後該服務調用 enqueueNotificationWithTag方法最後層層調用到
public class NotificationManagerService extends INotificationManager.Stub
{
......
StatusBarNotification n = new StatusBarNotification(pkg, id, tag, r.uid, r.initialPid, notification);
try { mStatusBar.updateNotification(r.statusBarKey, n)
}
......
}
新建了StatusBarNotification實例,然後調用updateNotification方法.
這個方法會進入到
public class PhoneStatusBar extends StatusBar {
......
public void updateNotification(IBinder key, StatusBarNotification notification) {
......
final RemoteViews contentView = notification.notification.contentView;
......
contentView.reapply(mContext, oldEntry.content);
......
}
會調用StatusBarNotification 的notification.contentView返回RemoteViews 對象,然後調用reapply方法.
6.回到RemoteViews 的reapply方法
public class RemoteViews implements Parcelable, Filter {
......
public void reapply(Context context, View v, OnClickHandler handler) {
RemoteViews rvToApply = getRemoteViewsToApply(context);
......
rvToApply.performApply(v, (ViewGroup) v.getParent(), handler);
}
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);
//調用apply方法
a.apply(v, parent, handler);
}
}
}
......
}
最終調用apply方法,在這裏加載新的佈局,RemoteViews就是這麼完成的.
public class RemoteViews implements Parcelable, Filter {
......
public View apply(Context context, ViewGroup parent, OnClickHandler handler) {
RemoteViews rvToApply = getRemoteViewsToApply(context);
View result;
LayoutInflater inflater =(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
......
//加載佈局
result = inflater.inflate(rvToApply.getLayoutId(), parent, false);
rvToApply.performApply(result, parent, handler);
return result;
}
......
}
總結
RemoteViews運行在SystemServer進程,更新RemoteViews要通過Binder獲取到對應的服務然後調用RemoteViews內部的apply方法加載更新佈局.