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

狄飞鹏
2023-12-01

为何AppWidget不支持自定义View

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

 

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

贴一段代码先,来自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;

        }

    }

以下如何解析AppWidgetView的具体函数。

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

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。然后addViewHostView去。

 

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。

 类似资料: