您的位置:首页 > 移动开发

从源码解析,为何AppWidget不支持自定义View

2017-06-13 10:53 441 查看

为何AppWidget不支持自定义View?

几个大类RemoveViews, RemoveViewsService, RemoveViewsFactory就不说了。不是本节主题。

 

切入正题,直接从AppWidgetHostView.java开始讲。这就相当于Activity的DecorView。

贴一段代码先,来自AppWidgetHostView.java版本:5.1

    /**
     * Inflate and return the default layout requested by AppWidget provider.
     */
    protected View getDefaultView() {
        ......
 
        try {
            if (mInfo != null) {
                Context theirContext = getRemoteContext();
                mRemoteContext= theirContext;
                LayoutInflater inflater = (LayoutInflater)
                        theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                inflater = inflater.cloneInContext(theirContext);
                inflater.setFilter(sInflaterFilter);
                AppWidgetManager manager = AppWidgetManager.getInstance(mContext);
                Bundle options = manager.getAppWidgetOptions(mAppWidgetId);
 
                int layoutId = mInfo.initialLayout;
                ......
                defaultView = inflater.inflate(layoutId, this, false);
            } else {
                Log.w(TAG, "can't inflate defaultView because mInfo is missing");
            }
        } catch (RuntimeException e) {
            exception = e;
        }
 
        ......
 
        return defaultView;
}

再看下面这段代码sInflaterFilter的定义

    // When we're inflating the initialLayout for a AppWidget, we only allow
    // views that are allowed in RemoteViews.
    static final LayoutInflater.Filter sInflaterFilter = new LayoutInflater.Filter() {
        public boolean onLoadClass(Class clazz) {
            return
clazz.isAnnotationPresent(RemoteViews.RemoteView.class);
        }
    };
再贴一段getRemoteContext()的实现

    /**
     * Build a {@link Context} cloned into another package name, usually for the
     * purposes of reading remote resources.
     */
    private Context getRemoteContext() {
        try {
            // Return if cloned successfully, otherwise default
            return mContext.createApplicationContext(
                    mInfo.providerInfo.applicationInfo,
                    Context.CONTEXT_RESTRICTED);
        } catch (NameNotFoundException e) {
            Log.e(TAG, "Package name " +  mInfo.providerInfo.packageName + " not found");
            return mContext;
        }
    }
以下如何解析AppWidget的View的具体函数。

1. mInfo.applicationInfo是桌面小控件所属的application的info

2. 先通过mInfo.applicationInfo获取Context。

3. 看sInflaterFilter,可以看出是否在类头定义了@RemoteViews.RemoteView,这样就知道如果没有定义Annotation,肯定是无法使用的。

4. 接下来就看inflater.inflate

继续看LayoutInflater.java

    public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
        ....
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
}
 public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
            ......
            final View temp = createViewFromTag(root, name, attrs, false);
            ......
            return result;
        }
    }
 
这部分代码大概说下,就是解析xml,解析其中各种ViewTag,最后通过createViewFromTag创建View。然后addView到HostView去。

 
View createViewFromTag(View parent, String name, AttributeSet attrs, boolean inheritContext) {
    ......
    if (-1 == name.indexOf('.')) {
        view = onCreateView(parent, name, attrs);
    } else {
         view = createView(name, null, attrs);
    }
    ......
}
这里略过了一些不重要的。具体createView进行实质性创建

 

重点看下createView

public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
        ......
        try {
            ......
 
            if (constructor == null) {
                // Class not found in the cache, see if it's real, and try to add it
                clazz = mContext.getClassLoader().loadClass(
                        prefix != null ? (prefix + name) : name).asSubclass(View.class);
                
                if (mFilter != null && clazz != null) {
                    boolean allowed = mFilter.onLoadClass(clazz);
                    if (!allowed) {
                        failNotAllowed(name, prefix, attrs);
                    }
                }
                constructor = clazz.getConstructor(mConstructorSignature);
                sConstructorMap.put(name, constructor);
            } else {
                ......
            }
 
            ......
            return view;
 
        ......
    }
系统刚起来的时候,这个自定义View肯定是不在cache里头的。所以,自然就进入了

If(constructor == null)成立的逻辑。

通过mContext获取ClassLoader,然后读取对应的class。

这里有个逻辑:ClassLoader能否找到class取决于,这个class是否在这个ClassLoader读取的路径里头。

Android中类加载器有BootClassLoader,URLClassLoader,

PathClassLoader,DexClassLoader,BaseDexClassLoader,等都最终继承自java.lang.ClassLoader。

不同的ClassLoader只能加载不同类型的class。

那么,AppWidget倒是用的是什么呢?

来看一段我在LayoutInflater.java中新增打印的Log信息(Appwidget没有打印出详细trace)

 

 W/System.err( 1165): java.lang.ClassNotFoundException: Didn't find class "com.example.testappwidget.AnimationImageView" on path: DexPathList[[directory
"."],nativeLibraryDirectories=[/vendor/lib, /system/lib]]
 W/System.err( 1165): at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
 W/System.err( 1165): at java.lang.ClassLoader.loadClass(ClassLoader.java:511)
 W/System.err( 1165): at java.lang.ClassLoader.loadClass(ClassLoader.java:469)
 W/System.err( 1165): at android.view.LayoutInflater.createView(LayoutInflater.java:574)
 W/System.err( 1165): at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:770)
 W/System.err( 1165): at android.view.LayoutInflater.rInflate(LayoutInflater.java:834)
 W/System.err( 1165): at android.view.LayoutInflater.inflate(LayoutInflater.java:504)
 W/System.err( 1165): at android.view.LayoutInflater.inflate(LayoutInflater.java:414)
 W/System.err( 1165): at android.appwidget.AppWidgetHostView.getDefaultView(AppWidgetHostView.java:565)
 W/System.err( 1165): at android.appwidget.AppWidgetHostView.updateAppWidget(AppWidgetHostView.java:370)
 W/System.err( 1165): at com.android.launcher3.LauncherAppWidgetHostView.updateAppWidget(LauncherAppWidgetHostView.java:61)
 W/System.err( 1165): at android.appwidget.AppWidgetHost.createView(AppWidgetHost.java:325)
 W/System.err( 1165): at com.android.launcher3.Launcher.bindAppWidget(Launcher.java:4546)
 W/System.err( 1165): at com.android.launcher3.LauncherModel$LoaderTask$7.run(LauncherModel.java:2655)
 W/System.err( 1165): at com.android.launcher3.DeferredHandler$Impl.handleMessage(DeferredHandler.java:51)
 W/System.err( 1165): at android.os.Handler.dispatchMessage(Handler.java:102)
 W/System.err( 1165): at android.os.Looper.loop(Looper.java:135)
 W/System.err( 1165): at android.app.ActivityThread.main(ActivityThread.java:5280)
 W/System.err( 1165): at java.lang.reflect.Method.invoke(Native
Method)
 W/System.err( 1165): at java.lang.reflect.Method.invoke(Method.java:372)
 W/System.err( 1165): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:963)
 W/System.err( 1165): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:758)
 W/System.err( 1165): Suppressed: java.lang.ClassNotFoundException:
com.example.testappwidget.AnimationImageView
 W/System.err( 1165): at java.lang.Class.classForName(Native
Method)
 W/System.err( 1165): at java.lang.BootClassLoader.findClass(ClassLoader.java:781)
 W/System.err( 1165): at java.lang.BootClassLoader.loadClass(ClassLoader.java:841)
 W/System.err( 1165): at java.lang.ClassLoader.loadClass(ClassLoader.java:504)
W/System.err( 1165): ... 20 more

W/System.err( 1165): Caused by: java.lang.NoClassDefFoundError:
Class not found using the boot class loader; no stack available

W/AppWidgetHostView( 1165): Error inflating AppWidget AppWidgetProviderInfo(UserHandle{0}/ComponentInfo{com.example.testappwidget/com.example.testappwidget.ExampleAppWidgetProvider}):
android.view.InflateException: Binary XML file line #21: Error inflating class com.example.testappwidget.AnimationImageView

注意看红色字体,AppWidgetHost是framework自带的,用RemotesViewsService进行管理,当然用的BootClassLoader。然而BootClassLoader当然就无法使用具体apk下的自定义View。

    在看一下SystemUI的ClassLoader:

dalvik.system.PathClassLoader[DexPathList[[zip file "/system/priv-app/SystemUI/SystemUI.apk"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]]

这个是AppWidget的ClassLoader:

Dalvik.system.PathClassLoader[DexPathList[[directory "."],nativeLibraryDirectories=[/vendor/lib, /system/lib]]]

   所以,AppWidget无法使用自定义View是必然。而如果你有framework修改的权限,只要在类头加Annotation就可以正常使用这个RemoteView。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息