几个大类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。