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; @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);
// 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代码:
// NotificationManager类
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()));
}
最终调用了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 {
...
@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);
}
}
...
}
主要是通过反射,调用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
View - RemoteViews的更多相关文章
- Android RemoteViews 11问11答
1.什么是RemoteView? 答:其实就是一种特殊的view结构,这种view 能够跨进程传输.并且这种remoteview 还提供了一些方法 可以跨进程更新界面.具体在android里面 一个是 ...
- Android开发艺术探索第五章——理解RemoteViews
Android开发艺术探索第五章--理解RemoteViews 这门课的重心在于RemoteViews,RemoteViews可以理解为一种远程的View,其实他和远程的Service是一样的,Rem ...
- android Notification定义与应用
首先要明白一个概念: Intent 与 PendingIntent 的区别: Intent:是意图,即告诉系统我要干什么,然后做Intent应该做的事,而intent是消息的内容 PendingInt ...
- 0703-APP-Notification-statue-bar
1.展示显示textTicker和仅仅有icon的两种情况:当參数showTicker为true时显示否则不显示 // In this sample, we'll use the same text ...
- Android Widget 小部件(一) 简单实现
在屏幕上加入Widget:或长按屏幕空白处,或找到WidgetPreview App选择. 原生系统4.0下面使用长按方式,4.0及以上 打开WIDGETS 创建Widget的一般步骤: 在menif ...
- Android Widget 小部件(四---完结) 使用ListView、GridView、StackView、ViewFlipper展示Widget
官方有话这样说: A RemoteViews object (and, consequently, an App Widget) can support the following layout cl ...
- Android Remote Views
听名字就可以看出,remote views是一种远程view,感觉有点像远程service,其实remote views是view的一个结构,他可以在其他的进程中显示,由于它可以在其他的进程中显示,那 ...
- Android Widget 开发详解(二) +支持listView滑动的widget
转载请标明出处:http://blog.csdn.net/sk719887916/article/details/47027263 不少开发项目中都会有widget功能,别小瞧了它,他也是androi ...
- 【起航计划 026】2015 起航计划 Android APIDemo的魔鬼步伐 25 App->Notification->Status Bar 状态栏显示自定义的通知布局,省却声音、震动
这个例子的Icons Only 和 Icons and marquee 没有什么特别好说明的. 而Use Remote views in balloon 介绍了可以自定义在Extended Statu ...
随机推荐
- ReactNative 常见红屏黄屏及终端报错
刚开始接触RN,总是会遇到各种红屏黄屏报错,红屏是fatal error(程序无法正常运行),黄屏是Warming(非致命错误,程序可以运行但是存在潜在问题可能在某些情况下将导致fatal error ...
- [OPENCV]cvHoughLines2使用说明
1.cvHoughLines2函数定义: CvSeq *cvHoughLines2 { CvArr *image, void *line_storage, int method, double rho ...
- BZOJ4083 : [Wf2014]Wire Crossing
WF2014完结撒花~ 首先求出所有线段之间的交点,并在交点之间连边,得到一个平面图. 这个平面图不一定连通,故首先添加辅助线使其连通. 然后求出所有域,在相邻域之间连一条代价为$1$的边. 对起点和 ...
- vb.net播放资源文件中的音乐
1.在自己的工程里添加一个资源文件. 2.打开添加的资源文件,资源类型选择为音频,点击添加资源把准备好的wav格式音乐文件添加进入资源文件. 3.设置资源属性和文件属性为嵌入 4.代码以及调用方法 P ...
- Egret 按钮点击缩放效果
非代码设计,exml直接操作 设计模式下选中对象,之后[源码],会直接定位到该对象在exml源码中的位置 width.down = "100%" 表示当按钮按下的时候宽度为 100 ...
- Ruby用百度搜索爬虫
Ruby用百度搜索爬虫 博主ruby学得断断续续,打算写一个有点用的小程序娱乐一下,打算用ruby通过百度通道爬取网络信息. 第三方库准备 mechanize:比较方便地处理网络请求,类似于Pytho ...
- Flask-WTF表单的使用
使用flask的WTF表单 #! /usr/bin/env python # *-* coding: utf-8 *-* from flask import Flask, render_templat ...
- hdu1847 Good Luck in CET-4 Everybody!(巴什博弈)
http://acm.hdu.edu.cn/showproblem.php?pid=1847 从1开始枚举情况,找规律.1先手胜2先手胜3先手败4先手胜5先手胜... n只要能转移到先手败,就可以实现 ...
- Kubernetes基础:Service
本文的试验环境为CentOS 7.3,Kubernetes集群为1.11.2,安装步骤参见kubeadm安装kubernetes V1.11.1 集群 Service 介绍 我们通过Pod.Deplo ...
- 【JavaScript 插件】图片展示插件 PhotoSwipe 初识
前言: 考虑自己网站的图片展示,而且要支持移动端和PC端.自己写的代码也不尽如意,要写好的话也需要时间,于是就想到了使用相关插件. 准备: PhotoSwipe 官网地址:http://photosw ...