The engineer who designs the Android is still very standard, and at the first glance, he knows what it means. RemoteViews, as the name suggests, remote View. Android in order to enable the process A to display the View of the process B, this kind of View is designed.It’s not a real View. In fact, we used RemoteViews to send notifications to the status bar. Let’s get to know about RemoteViews.
Let’s take a look at how RemoteViews works with 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; @Override protected 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); // RemoteViewsThe event can only be PendingIntent. contentView.setOnClickPendingIntent(R.id.remote_content, pendingIntent); notification = new Notification.Builder(this) .setWhen(System.currentTimeMillis()) // Set the time to display the notification. .setAutoCancel(true) // Can the settings be manually cancelled? .setSmallIcon(R.mipmap.ic_launcher) // The small icon set in the status bar does not display the notification if it is not set. .setCustomBigContentView(contentView) // Set Custom Big Content View to display the full height of remoteviews, and setCustom Content View to display only the system notification bar height. .build(); notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); // Notifications notificationManager.notify(1, notification); } }
The R.layout.layout_remote layout file is as follows:
<?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>
The effect is shown in the figure:
Because I’m calling setCustomBigContentView to load RemoteViews, RemoteViews can be displayed intact, regardless of the system notification bar height limit.
We will then analyze how the status bar loads our defined RemoteViews, Let ‘s Go!!
RemoteViews update”
RemoteViewsThere are many ways to update the layout file of RemoteViews:
// Partial method - setTextViewText(viewId, text) Set up text- setTextColor(viewId, color) Set text color- setTextViewTextSize(viewId, units, size) Set text size- setImageViewBitmap(viewId, bitmap) Set up a picture- setImageViewResource(viewId, srcId) Set pictures according to picture resources- setViewPadding(viewId, left, top, right, bottom) Set Padding spacing- setOnClickPendingIntent(viewId, pendingIntent) Set click events- setInt(viewId, methodName, value) Reflection calls the methodName method with int parameter.- setLong(viewId, methodName, value) Reflection calls the methodName method with long parameter....
When the above method is called to update RemoteViews, the RemoteViews do not update immediately, but encapsulate a series of actions and wait for the opportunity to update.
We analyze from source code, when we call setTextViewText to update content:
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); }
Let’s take a look at the setTextViewText source code:
// RemoteViewsclass public void setTextViewText(int viewId, CharSequence text) { setCharSequence(viewId, "setText", text); }
The setCharSequence method is called:
// RemoteViewsclass public void setCharSequence(int viewId, String methodName, CharSequence value) { addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value)); }
addActionMethod:
// RemoteViewsclass 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); }
As you can see, the entire set process just encapsulates an action and adds it to mActions (a List), so the process does not update RemoteViews. Let’s see what ReflectionAction is:
// ReflectionActionclass 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; } ... }
That is, some attributes are stored, mainly some updated RemoteViews layout information passed to the System Server process.
When we call the notificationManager. notify (1, notification) method, the RemoteViews layout starts to update.
Let’s look at the notify Code:
// NotificationManagerclass public void notify(int id, Notification notification){ notify(null, id, notification); } public void notify(String tag, int id, Notification notification){ notifyAsUser(tag, id, notification, new UserHandle(UserHandle.myUserId())); }
Finally, we call the notifyAsUser method:
// NotificationManagerclass 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(); } }
serviceFor the proxy object of INotificationManager, after calling the enqueueNotificationWithTag method, the NotificationManagerService is also called via BinderThe enqueueNotificationWithTag method of the INotificationManager stub object, which exists in the System Server process:
// NotificationManagerServiceclass 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) ... } ... }
The updateNotification method of StatusBarNotification is invoked:
// StatusBarNotificationclass public void updateNotification(IBinder key, StatusBarNotification notification) { ... final RemoteViews contentView = notification.notification.contentView; ... contentView.reapply(mContext, oldEntry.content); ... }
Finally, the reapply method of RemoteViews is invoked in the SystemServer process:
// RemoteViewsclass 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); }
Finally, the performApply method of RemoteViews was invoked:
// RemoteViewsclass 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); } } }
Didn’t we save the Action when we used the setXXX method, traverse all the actions by calling the performApply method, and then update the RemoteViews layout file? In code, the apply method of Action is invoked.View updates, Action is an abstract class, and apply method is implemented by subclasses. Let’s look at the apply method of ReflectionAction class:
// ReflectionActionclass private final class ReflectionAction extends Action { ... @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) { 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); } } ... }
It is mainly through reflection, calling View to update View.
Understanding RemoteViews allows us to flexibly design a variety of RemoteViews, and to display the views that our application (B) wants to display without using (A) requires further exploration.