1、RemoteViews原理分析及应用RemoteViews原理分析及应用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 opera
2、tions for modifying * the content of the inflated hierarchy. */public class RemoteViews implements Parcelable, Filter 我们可以得到以下几点结论:RemoteViews只是一个实现了Parcelable和Filter接口的类,而并非继承自View。RemoteViews用来描述可运行在其他进程中的视图结构,但RemoteViews本身不是视图,只是一个描述类。RemoteViews描述的远程视图需要通过layout资源文件定义。RemoteViews类提供了一系列修改远程视图的方
3、法。现在我们对RemoteViews应该有了一个大概的认识,它可以跨进程来显示和更新视图。RemoteViews主要有两个应用场景:自定义通知栏桌面小部件本文最后会给出RemoteViews的应用实例,接下来我们先分析RemoteViews的实现原理。RemoteViews原理分析构造函数RemoteViews提供了多个构造函数,如:public RemoteViews(String packageName, int layoutId) public RemoteViews(String packageName, int userId, int layoutId) public RemoteV
4、iews(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 Th
5、e 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. *
6、* 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
7、usage statistics mMemoryUsageCounter = new MemoryUsageCounter(); recalculateMemoryUsage();同样也很简单,第一个参数为远程视图展示内容所属的Application信息,第二个参数为布局文件ID。该构造方法主要是初始化mApplication与mLayoutId,其他代码为图片缓存及内存计算的一些逻辑。核心属性字段RemoteView有如下几个比较重要的属性字段:/* * Application that hosts the remote views. * * hide */private Applicati
8、onInfo 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 mActions;前两个比较好理解,而且上文提到是在构造函数中赋值的。mActions是用来存储Action的一个列表,而Action可以理解为对远程视图操作的一个封装,下
9、文会详细解释。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是
10、否可以作为远程视图使用。由此我们也可以推断,并非所有View都可以作为远程视图,只有声明了RemoteView注解的View才可以。我们从源码定义来简单验证一下:TextView的定义RemoteViewpublic class TextView extends View implements ViewTreeObserver.OnPreDrawListener Button的定义RemoteViewpublic class Button extends TextView ImageView的定义RemoteViewpublic class ImageView extends View Pro
11、gressBar的定义RemoteViewpublic class ProgressBar extends View LinearLayout的定义RemoteViewpublic class LinearLayout extends ViewGroup EditText的定义public class EditText extends TextView 不再一一列举,可见EditText虽然是继承自TextView的,但它没有使用RemoteView注解,因此并不能用作远程视图。RemoteViews所支持的View类型如下:LinearLayout、RelativeLayout、FrameL
12、ayout、GridLayout、AbsoluteLayout(已弃用)TextView、Button、ImageView、ImageButton、Chronometer、ProgressBar、ListView、GridView、StackView、ViewFlipper、AdapterViewFlipper、ViewStub、AnalogClock(已弃用)也就是说远程视图只能使用上述所列举的View,它们的子类及其他View都是不支持的,如果使用了不支持的View,则会报异常。实现Parcelable和Filter接口的意义Parcelable比较容易理解,就是支持序列化以便于跨进程操作
13、。那么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
14、 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,否则返回
15、false。在RemoteViews中实现了Filter接口的方法:public boolean onLoadClass(Class clazz) return clazz.isAnnotationPresent(RemoteView.class);可以看到就是根据RemoteView注解来判断是否可以使用该View作为远程视图。RemoteViews实现原理跨进程是哪两个进程很显然我们的应用自身是一个进程,那么另一个进程是什么呢?在RemoteViews的应用场景中,如自定义通知栏和桌面小部件,它们都运行在系统进程中,即SystemServer进程。如此一来,远程视图运行在SystemSer
16、ver进程中,我们在自己的应用进程中跨进程来操作远程视图。前文说到RemoteViews实现了Parcelable接口,那么RemoteViews便可以从应用进程传输到系统进程了。远程View是如何加载的通常情况下,我们使用LayoutInflater加载布局只需要知道布局ID即可。还记得前文讲RemoteViews的构造函数时,有两个重要的字段:mApplicationmLayoutId在系统进程中加载远程视图正是利用了上述两个字段。在RemoteViews的源码中加载布局的逻辑如下:/* * Inflates the view hierarchy represented by this o
17、bject and applies * all of the actions. * * Caller beware: this may throw * * param context Default context to use * param parent Parent that the resulting view hierarchy will be attached to. This method * does not attach the hierarchy. The caller should do so when appropriate. * return The inflated
18、 view hierarchy */public View apply(Context context, ViewGroup parent) return apply(context, parent, null);接下来会调用:public View apply(Context context, ViewGroup parent, OnClickHandler handler) RemoteViews rvToApply = getRemoteViewsToApply(context); View result = inflateView(context, rvToApply, parent)
19、; loadTransitionOverride(context, handler); rvToApply.performApply(result, parent, handler); return result;先重点关注下面这一行代码:View result = inflateView(context, rvToApply, parent);其内部实现就是常见的布局加载方式了:private View inflateView(Context context, RemoteViews rv, ViewGroup parent) / RemoteViews may be built by an
20、 application installed in another / user. So build a context that loads resources from that user but / still returns the current users userId so settings like data / time formats / are loaded without requiring cross user persmissions. final Context contextForResources = getContextForResources(contex
21、t); Context inflationContext = new ContextWrapper(context) Override public Resources getResources() return contextForResources.getResources(); Override public Resources.Theme getTheme() return contextForResources.getTheme(); Override public String getPackageName() return contextForResources.getPacka
22、geName(); ; LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); / Clone inflater so we load resources from correct context and / we dont add a filter to the static version returned by getSystemService. inflater = inflater.cloneInContext(inflationCont
23、ext); inflater.setFilter(this); return inflater.inflate(rv.getLayoutId(), parent, false);主要关注最后几行代码即可,把握主要流程,一些细节可以暂时忽略。RemoteViews的apply方法中还有这样一行代码:rvToApply.performApply(result, parent, handler);接下来会调用:private void performApply(View v, ViewGroup parent, OnClickHandler handler) if (mActions != null
24、) 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); 即遍历RemoteViews中存储的Action,然后执行Action的apply方法。远程View是如何操作的上一小节提到了Action对象,那么Action又是什么呢?由于RemoteViews运行在远端进程中,因此无法通过
25、findViewById的方法来获取View。为了操作远程视图,于是就将对视图的操作封装成一个Action对象,Action是一个实现了Parcelable接口的抽象类,因此可以跨进程传输。先来看下Action的定义:/* * Base class for all actions that can be performed on an * inflated view. * * SUBCLASSES MUST BE IMMUTABLE SO CLONE WORKS! */private abstract static class Action implements Parcelable publ
26、ic abstract void apply(View root, ViewGroup rootParent, OnClickHandler handler) throws ActionException; public static final int MERGE_REPLACE = 0; public static final int MERGE_APPEND = 1; public static final int MERGE_IGNORE = 2; public int describeContents() return 0; /* * Overridden by each class
27、 to report on its own memory usage */ public void updateMemoryUsageEstimate(MemoryUsageCounter counter) / We currently only calculate Bitmap memory usage, so by default, / dont do anything here public void setBitmapCache(BitmapCache bitmapCache) / Do nothing public int mergeBehavior() return MERGE_R
28、EPLACE; public abstract String getActionName(); public String getUniqueKey() return (getActionName() + viewId); /* * This is called on the background thread. It should perform any non-ui computations * and return the final on which will run on the UI thread. * Override this if some of the tasks can
29、be performed async. */ public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) return this; int viewId;从说明来看,Action就是对远程视图操作的一个封装,它提供了一个抽象方法:public abstract void apply(View root, ViewGroup rootParent, OnClickHandler handler) throws ActionException;该抽象方法需要子类做具体实现。Ac
30、tion有很多子类,几乎每个子类都用来辅助一种View操作,下面简单罗列两个:/* * Helper action to set compound drawables on a TextView. Supports relative * (s/t/e/b) or cardinal (l/t/r/b) arrangement. */private class TextViewDrawableAction extends Action /* * Helper action to set text size on a TextView in any supported units. */private class TextViewSizeAction extends Action 这里不再一一列举,有兴趣的可以参考源码。了解了Action的概念之后,我们以为远程的TextView设置文本为例,来具体看一下其工作流程。平时我们给TextView设置文本只需要调用其setText方法即可,但RemoteViews无法这样使用,RemoteViews提供了一系列关于View操作的set方法,这里会用到如下方法:/*
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1