转载请注明出处:http://blog.youkuaiyun.com/ahence/article/details/62418926
RemoteViews基本概念
RemoteViews乍一看名字似乎也是一种View,实则不然,它并不是View。来看RemoteViews的定义及官方说明:
/**
* A class that describes a view hierarchy that can be displayed in
* another process. The hierarchy is inflated from a layout resource
* file, and this class provides some basic operations for modifying
* the content of the inflated hierarchy.
*/
public class RemoteViews implements Parcelable, Filter {
……
}
我们可以得到以下几点结论:
- RemoteViews只是一个实现了Parcelable和Filter接口的类,而并非继承自View。
- RemoteViews用来描述可运行在其他进程中的视图结构,但RemoteViews本身不是视图,只是一个描述类。
- RemoteViews描述的远程视图需要通过layout资源文件定义。
- RemoteViews类提供了一系列修改远程视图的方法。
现在我们对RemoteViews应该有了一个大概的认识,它可以跨进程来显示和更新视图。RemoteViews主要有两个应用场景:
- 自定义通知栏
- 桌面小部件
本文最后会给出RemoteViews的应用实例,接下来我们先分析RemoteViews的实现原理。
RemoteViews原理分析
构造函数
RemoteViews提供了多个构造函数,如:
public RemoteViews(String packageName, int layoutId) {}
public RemoteViews(String packageName, int userId, int layoutId) {}
public RemoteViews(RemoteViews landscape, RemoteViews portrait) {}
public RemoteViews(Parcel parcel) {}
以一个最常用的构造方法为例:
/**
* Create a new RemoteViews object that will display the views contained
* in the specified layout file.
*
* @param packageName Name of the package that contains the layout resource
* @param layoutId The id of the layout resource
*/
public RemoteViews(String packageName, int layoutId) {
this(getApplicationInfo(packageName, UserHandle.myUserId()), layoutId);
}
由注释可知,第一个参数为包名,第二个参数为布局资源文件的ID,接下来会调用:
/**
* Create a new RemoteViews object that will display the views contained
* in the specified layout file.
*
* @param application The application whose content is shown by the views.
* @param layoutId The id of the layout resource.
*
* @hide
*/
protected RemoteViews(ApplicationInfo application, int layoutId) {
mApplication = application;
mLayoutId = layoutId;
mBitmapCache = new BitmapCache();
// setup the memory usage statistics
mMemoryUsageCounter = new MemoryUsageCounter();
recalculateMemoryUsage();
}
同样也很简单,第一个参数为远程视图展示内容所属的Application信息,第二个参数为布局文件ID。该构造方法主要是初始化mApplication
与mLayoutId
,其他代码为图片缓存及内存计算的一些逻辑。
核心属性字段
RemoteView有如下几个比较重要的属性字段:
/**
* Application that hosts the remote views.
*
* @hide
*/
private ApplicationInfo mApplication;
/**
* The resource ID of the layout file. (Added to the parcel)
*/
private final int mLayoutId;
/**
* An array of actions to perform on the view tree once it has been
* inflated
*/
private ArrayList<Action> mActions;
前两个比较好理解,而且上文提到是在构造函数中赋值的。mActions
是用来存储Action
的一个列表,而Action
可以理解为对远程视图操作的一个封装,下文会详细解释。
RemoteView注解
在RemoteViews源码中声明了如下注解:
/**
* This annotation indicates that a subclass of View is alllowed to be used
* with the {@link RemoteViews} mechanism.
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface RemoteView {
}
从注解类型来看为运行时注解,作用于类或接口,结合注释可知此注解用于View的子类,用来标识该View是否可以作为远程视图使用。由此我们也可以推断,并非所有View都可以作为远程视图,只有声明了RemoteView注解的View才可以。我们从源码定义来简单验证一下:
TextView的定义
@RemoteView
public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
}
Button的定义
@RemoteView
public class Button extends TextView {
}
ImageView的定义
@RemoteView
public class ImageView extends View {
}
ProgressBar的定义
@RemoteView
public class ProgressBar extends View {
}
LinearLayout的定义
@RemoteView
public class LinearLayout extends ViewGroup {
}
EditText的定义
public class EditText extends TextView {
}
不再一一列举,可见EditText
虽然是继承自TextView
的,但它没有使用@RemoteView
注解,因此并不能用作远程视图。
RemoteViews所支持的View类型如下:
LinearLayout、RelativeLayout、FrameLayout、GridLayout、AbsoluteLayout(已弃用)
TextView、Button、ImageView、ImageButton、Chronometer、ProgressBar、ListView、GridView、StackView、ViewFlipper、AdapterViewFlipper、ViewStub、AnalogClock(已弃用)
也就是说远程视图只能使用上述所列举的View,它们的子类及其他View都是不支持的,如果使用了不支持的View,则会报异常。
实现Parcelable和Filter接口的意义
Parcelable比较容易理解,就是支持序列化以便于跨进程操作。
那么Filter的作用是什么呢?Filter接口的定义如下:
/**
* Hook to allow clients of the LayoutInflater to restrict the set of Views that are allowed
* to be inflated.
*
*/
public interface Filter {
/**
* Hook to allow clients of the LayoutInflater to restrict the set of Views
* that are allowed to be inflated.
*
* @param clazz The class object for the View that is about to be inflated
*
* @return True if this class is allowed to be inflated, or false otherwise
*/
@SuppressWarnings("unchecked")
boolean onLoadClass(Class clazz);
}
从注释中不难看出Filter是用来限制和过滤View用的,上文提到并非所有的View都能用作远程视图,如果为上述列举的View,则onLoadClass(Class clazz)
返回true,否则返回false。
在RemoteViews中实现了Filter接口的方法:
public boolean onLoadClass(Class clazz) {
return clazz.isAnnotationPresent(RemoteView.class);
}
可以看到就是根据@RemoteView
注解来判断是否可以使用该View作为远程视图。
RemoteViews实现原理
跨进程是哪两个进程
很显然我们的应用自身是一个进程,那么另一个进程是什么呢?