View - RemoteViews
設計Android的工程師起名字還是挺規范的,而且一眼就知道是什么意思。RemoteViews,顧名思義,遠程的View。Android為了能讓進程A顯示進程B的View,設計了這么一種View(其實不是真正的View)。其實我們開發過程中,發通知到狀態欄顯示也是利用了RemoteViews,我們來了解一下RemoteViews吧。
我們先看看RemoteViews怎么配合Notification使用:
import android.annotation.SuppressLint; import android.app.Activity; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.widget.RemoteViews;@SuppressLint("NewApi") public class MainActivity extends Activity {private RemoteViews contentView;private Notification notification;private NotificationManager notificationManager;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);sendNotification();}private void sendNotification() {contentView = new RemoteViews(getPackageName(), R.layout.layout_remote);contentView.setTextViewText(R.id.remote_title, "Remote View Title");contentView.setTextViewText(R.id.remote_content, "This Remote View Content ... \nThis Remote View Content ... \nThis Remote View Content ...");Intent intent = new Intent(this, MainActivity.class);PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);// RemoteViews的事件只能是PendingIntent contentView.setOnClickPendingIntent(R.id.remote_content, pendingIntent);notification = new Notification.Builder(this) .setWhen(System.currentTimeMillis()) // 設置顯示通知的時間.setAutoCancel(true) // 設置是否可以手動取消.setSmallIcon(R.mipmap.ic_launcher) // 設置在狀態欄的小圖標,如果沒有設置,不顯示通知.setCustomBigContentView(contentView) // 設置自定義View,setCustomBigContentView可以顯示remoteviews的完整高度,setCustomContentView只能顯示系統通知欄高度。 .build();notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);// 發通知notificationManager.notify(1, notification);}}其中R.layout.layout_remote布局文件如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:padding="10dp"android:orientation="vertical" ><TextView android:id="@+id/remote_title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:textSize="18sp"android:textColor="@android:color/holo_blue_light"/><TextView android:id="@+id/remote_content"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="3dp"android:textSize="16sp"android:textColor="@android:color/darker_gray"/></LinearLayout>?
效果如圖所示:
?
因為我是調用setCustomBigContentView來加載RemoteViews的,所以RemoteViews可以顯示完整,不受系統通知欄高度限制。
我們接下來解析狀態欄是怎么加載我們定義的RemoteViews的,Let’s Go !!
?RemoteViews加載
我們首先要知道狀態欄是SystemServer進程,而我們定義的RemoteViews是在我們App進程,狀態欄要加載并顯示我們的RemoteViews,這肯定是通過IPC,主要實現是Binder。
RemoteViews會通過Binder傳遞給SystemServer進程,系統會根據RemoteViews的包名和布局id等信息,獲取到應用的資源(布局文件,圖標等),然后通過LayoutInflater加載RemoteViews中的布局文件,最后在狀態欄和通知欄顯示出來。
RemoteViews更新
RemoteViews提供了很多個方法,更新RemoteViews的布局文件:
// 部分方法 - 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) 設置點擊事件 - setInt(viewId, methodName, value) 反射調用參數為int的methodName方法 - setLong(viewId, methodName, value) 反射調用參數為long的methodName方法 ...當調用以上方法來更新RemoteViews時,RemoteViews并 不會立刻更新,只是封裝了一系列的Action,然后等待時機更新。
我們從源碼分析,當我們調用setTextViewText來更新內容時:
private void updateNotification() {contentView.setTextViewText(R.id.remote_content, "This Remote View Update Content ... \nThis Remote View Update Content ... \nThis Remote View Update Content ...");notificationManager.notify(1, notification); }?
我們來看看setTextViewText源碼:
// RemoteViews類 public void setTextViewText(int viewId, CharSequence text) {setCharSequence(viewId, "setText", text); }?
調用了setCharSequence方法:
// RemoteViews類 public void setCharSequence(int viewId, String methodName, CharSequence value) {addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value)); }?
addAction方法:
// RemoteViews類 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);a.updateMemoryUsageEstimate(mMemoryUsageCounter); }?
可以看出,整個set過程,只是封裝了一個Action并添加到mActions(一個List)中,所以這個過程并沒有更新RemoteViews哦。我們看看ReflectionAction是什么:
// ReflectionAction類 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;}... }?
就是儲存了一些屬性,主要是傳遞給SystemServer進程的一些更新RemoteViews布局信息。
當我們調用notificationManager.notify(1, notification)方法,RemoteViews布局才會開始更新。?
我們來看看notify代碼:
?
最終調用了notifyAsUser方法:
// NotificationManager類 public void notifyAsUser(String tag, int id, Notification notification, UserHandle user){...INotificationManager service = getService();...final Notification copy = Builder.maybeCloneStrippedForDelivery(notification);try {service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,copy, idOut, user.getIdentifier());...} catch (RemoteException e) {throw e.rethrowFromSystemServer();} }?
service為INotificationManager的代理對象,調用了enqueueNotificationWithTag方法后,通過Binder,也就調用了NotificationManagerService(INotificationManager的存根對象,存在于SystemServer進程)的enqueueNotificationWithTag方法:
// NotificationManagerService類 public void enqueueNotificationWithTag(String pkg, String packageName, String tag, int id, Notification notification, int[] idOut, @UserIdInt userIdInt) {...StatusBarNotification n = new StatusBarNotification(pkg, id, tag, r.uid, r.initialPid, notification);try { mStatusBar.updateNotification(r.statusBarKey, n) ...}... }?
調用了StatusBarNotification的updateNotification方法:
// StatusBarNotification類 public void updateNotification(IBinder key, StatusBarNotification notification) {...final RemoteViews contentView = notification.notification.contentView;...contentView.reapply(mContext, oldEntry.content);... }?
最終在SystemServer進程調用了RemoteViews的reapply方法:
// RemoteViews類 public void reapply(Context context, View v) {reapply(context, v, null); } public void reapply(Context context, View v, OnClickHandler handler) {RemoteViews rvToApply = getRemoteViewsToApply(context);if (hasLandscapeAndPortraitLayouts()) {if (v.getId() != rvToApply.getLayoutId()) {throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +" that does not share the same root layout id.");}}rvToApply.performApply(v, (ViewGroup) v.getParent(), handler); }?
最終調用了RemoteViews的performApply方法:
// RemoteViews類 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);}} }?
我們之前setXXX方法時不是儲存了Action嗎,通過調用performApply方法,遍歷所有Action,然后更新RemoteViews的布局文件。代碼中,調用了Action的apply方法實現View的更新,Action是一個抽象類,apply方法由子類實現。我們看看ReflectionAction類的apply方法:
// ReflectionAction類 private final class ReflectionAction extends Action {...@Overridepublic void apply(View root, ViewGroup rootParent, OnClickHandler handler) {final View view = root.findViewById(viewId);if (view == null) return;Class<?> param = getParameterType();if (param == null) {throws new ActionException("bad type : " + this.type);}try {getMethod(view, this.methodName, param).invoke(view, wrapArg(this.value));} catch (ActionException e) {throws e;} catch (Exception ex) {throws new ActionException(ex);}}... }?
主要是通過反射,調用View的方法,更新View。
?注意
RemoteViews設置的布局文件并不支持所有的View,以下是RemoteViews所支持的View:
layout
FrameLayout,LinearLayout,RelativeLayout,GridLayout
view
Button,ImageView,ImageButton,TextView,ProgressBar,ListView,GridView,StackView,ViewStub,AdapterViewFlipper,ViewFlipper,AnalogClock,Chronometer
?小結
通過對RemoteViews的了解,我們靈活的設計出多樣式的RemoteViews,還可以在不用應用(A)顯示自己應用(B)想要顯示的View,這個有需要再探索。
?
轉:?https://blog.csdn.net/johanman/article/details/76019771
?
轉載于:https://www.cnblogs.com/blosaa/p/9564332.html
總結
以上是生活随笔為你收集整理的View - RemoteViews的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 医疗AI技术火热,但其商业模式的落脚点究
- 下一篇: DDoS高防(国际)子产品发布,替代原本