Widget不支持自定义View的原因

1,161 阅读3分钟

一、widget为什么没有setAnimation等设置动画的方法

RemoteViews是跨进程的,通过binder和AppWidgetServer进行通信,所以,他的方法参数,必须是实现Parcelable接口的,它可以传递int, String, Bitmap 和 Drawable等,但是不能传递动画,因为Animator类没有实现Parcelable.

二、widget为什么不支持自定义View

小部件收到ACTION_APPWIDGET_UPDATE等消息,会刷新RemoteViews,类似下面代码:

RemoteViews remoteViews = new RemoteViews(sContext.getPackageName(),R.layout.widget_container_layout);
remoteViews.setImageViewResource(R.id.center, R.drawable.ic_sunny);
ComponentName componentName = new ComponentName(sContext, SampleWidgetProvider.class);
sAppWidgetManager.updateAppWidget(componentName, remoteViews);

RemoteViews的刷新

02-07 09:25:41.161 10702 10702 W System.err: 	at android.view.LayoutInflater.createView(LayoutInflater.java:635)
02-07 09:25:41.161 10702 10702 W System.err: 	at com.android.internal.policy.PhoneLayoutInflater.onCreateView(PhoneLayoutInflater.java:58)
02-07 09:25:41.161 10702 10702 W System.err: 	at android.view.LayoutInflater.onCreateView(LayoutInflater.java:734)
02-07 09:25:41.161 10702 10702 W System.err: 	at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:802)
02-07 09:25:41.162 10702 10702 W System.err: 	at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:744)
02-07 09:25:41.162 10702 10702 W System.err: 	at android.view.LayoutInflater.inflate(LayoutInflater.java:493)
02-07 09:25:41.162 10702 10702 W System.err: 	at android.view.LayoutInflater.inflate(LayoutInflater.java:424)
02-07 09:25:41.162 10702 10702 W System.err: 	at android.widget.RemoteViews.inflateView(RemoteViews.java:3498)
02-07 09:25:41.163 10702 10702 W System.err: 	at android.widget.RemoteViews.apply(RemoteViews.java:3475)
02-07 09:25:41.163 10702 10702 W System.err: 	at android.appwidget.AppWidgetHostView.applyRemoteViews(AppWidgetHostView.java:451)
02-07 09:25:41.164 10702 10702 W System.err: 	at android.appwidget.AppWidgetHostView.updateAppWidget(AppWidgetHostView.java:380)
02-07 09:25:41.164 10702 10702 W System.err: 	at com.chehejia.m01.launcher.data.appwidget.LauncherAppWidgetHostView.updateAppWidget(LauncherAppWidgetHostView.java:96)
02-07 09:25:41.164 10702 10702 W System.err: 	at android.appwidget.AppWidgetHost.updateAppWidgetView(AppWidgetHost.java:437)
02-07 09:25:41.165 10702 10702 W System.err: 	at android.appwidget.AppWidgetHost$UpdateHandler.handleMessage(AppWidgetHost.java:132)
02-07 09:25:41.165 10702 10702 W System.err: 	at android.os.Handler.dispatchMessage(Handler.java:107)
02-07 09:25:41.165 10702 10702 W System.err: 	at android.os.Looper.loop(Looper.java:164)
02-07 09:25:41.165 10702 10702 W System.err: 	at android.app.ActivityThread.main(ActivityThread.java:6553)
02-07 09:25:41.165 10702 10702 W System.err: 	at java.lang.reflect.Method.invoke(Native Method)
02-07 09:25:41.166 10702 10702 W System.err: 	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:444)
02-07 09:25:41.166 10702 10702 W System.err: 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)

LayoutInflate的createView方法:

if (constructor == null) {
          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);
          constructor.setAccessible(true);
          sConstructorMap.put(name, constructor);

打印了mContext和mContext.getClassLoader(),如下: mContext=android.widget.RemoteViews$RemoteViewsContextWrapper@5971f82, mContext.getClassLoader()=dalvik.system.PathClassLoader[DexPathList[[zip file "/system/framework/retrofit.jar", zip file "/system/framework/gsonjar.jar",、、、, zip file "/system/framework/oss-android-sdk.jar", zip file "/data/app/com.chehejia.m01.launcher-EQ50ml8ebm4iRLzqrUnLcg==/base.apk"],nativeLibraryDirectories=[/data/app/xxx.launcher-EQ50ml8ebm4iRLzqrUnLcg==/lib/arm64, /data/app/xxx.launcher-EQ50ml8ebm4iRLzqrUnLcg==/base.apk!/lib/arm64-v8a, /system/lib64, /vendor/lib64]]]

从上面看到,类加载器会从DexPathList目录中加载类,如果你的类不在这里面,就加载不到,所以,应用自定义的view是加载不到的。而RemoteViews的context其实是从Launcher传过去的,所以,这里加载的,其实是launcher及其jar包下的类

boolean allowed = mFilter.onLoadClass(clazz);这个mFilter是在AppWidgetHostView中定义的

private static final LayoutInflater.Filter INFLATER_FILTER =
        (clazz) -> clazz.isAnnotationPresent(RemoteViews.RemoteView.class);

所以,要想自定义View能在小部件上加载,需要自定义View这个类,有RemoteViews的注解,并且,这个类要在上面那些目录下,比如framework下。系统的时钟,TextClock就是放在了framework目录下

二、对方法的要求

RemoteViews有一系列set方法,比如setInt, setString, setBundle 比如:

 public void setBundle(int viewId, String methodName, Bundle value) {
        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BUNDLE, value));
    }

ReflectionAction中的实现:

private MethodHandle getMethod(View view, String methodName, Class<?> paramType, boolean async) {
        MethodArgs result;
        Class<? extends View> klass = view.getClass();

        synchronized (sMethods) {
          、、、
                    if (!method.isAnnotationPresent(RemotableViewMethod.class)) {
                        throw new ActionException("view: " + klass.getName()
                                + " can't use method with RemoteViews: "
                                + methodName + getParameters(paramType));
                    }

              、、、
            return result.asyncMethod;
        }
    }

所以,只要你自定义的类,方法参数能够用RemoteViews的set方法传递,并且这个方法上有RemotableViewMethod的注解,就可以通过RemoteViews的setXX方法,跨进程调用