2011.8.17---2011.8.18 (查询bug---contactWidget中使用自定义的view在launcher中显示不出来,报解析xml的错误)

众所周知,android2.X版本中RemoteViews只支持FrameLayout,LinearLayout,RelativeLayout三个布局类,以及下面几个widget类:

  • AnalogClock
  • Button
  • Chronometer
  • ImageButton
  • ImageView
  • ProgressBar
  • TextView
  • ViewFlipper

Descendants of these classes are not supported.(这句很关键)

如果想要使桌面widget效果炫一些,出现一些拖动效果,那么一般只能修改sdk,编译框架才可。在网上有一个开源的launcher叫做launcherPlus,这个launcherPlus对launcher的源码进行了修改用以支持ListView。这里有一个contactWidget,效果如下:

2011.8.17---2011.8.18 (查询bug---contactWidget中使用自定义的view在launcher中显示不出来,报解析xml的错误)_第1张图片

现在公司有个小需求,就是要在上下拖动listView时,使其有弹簧的效果,于是首先想到的是修改contactWidget的源码,首先定位到修改contactWidget的gridview.xml,原来的gridview.xml中示例如下:

<GridView xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="wrap_content" android:layout_height="wrap_content"
 android:cacheColorHint="#00000000" android:scrollbarStyle="outsideOverlay"
 android:scrollbars="vertical" android:scrollingCache="true"
 android:alwaysDrawnWithCache="true" android:persistentDrawingCache="scrolling"
 android:divider="@drawable/divider_horizontal_bright"
 android:fadingEdge="none" android:id="@+id/my_gridview"

 android:horizontalSpacing="5dip" android:verticalSpacing="10dip"
 android:numColumns="4" android:addStatesFromChildren="false"
 android:choiceMode="singleChoice" android:duplicateParentState="true">
</GridView>

现在我自己实现了一个GridView类MyGridView,在gridview文件中使用自己的类,如下:

 

<com.zhoujie.MyGridView

 xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="wrap_content" android:layout_height="wrap_content"
 android:cacheColorHint="#00000000" android:scrollbarStyle="outsideOverlay"
 android:scrollbars="vertical" android:scrollingCache="true"
 android:alwaysDrawnWithCache="true" android:persistentDrawingCache="scrolling"
 android:divider="@drawable/divider_horizontal_bright"
 android:fadingEdge="none" android:id="@+id/my_gridview"

 android:horizontalSpacing="5dip" android:verticalSpacing="10dip"
 android:numColumns="4" android:addStatesFromChildren="false"
 android:choiceMode="singleChoice" android:duplicateParentState="true">
</com.zhoujie.MyGridView >

编译,运行结果却没有显示出来,如下图:

2011.8.17---2011.8.18 (查询bug---contactWidget中使用自定义的view在launcher中显示不出来,报解析xml的错误)_第2张图片

 

于是看logcat,

08-18 08:13:49.794: DEBUG/NewsWidget(4606): Binary XML file line #2: Error inflating class com.zhoujie.MyGridView

从log看,显然是LayoutInflater在inflate时出的错,进行一系列跟踪,可知出错的地方是在LayoutInflater的createView()方法中,如下所示:

 public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
        Constructor constructor = sConstructorMap.get(name);
        Class clazz = null;

        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);
               
                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 {
                // If we have a filter, apply it to cached constructor
                if (mFilter != null) {
                    // Have we seen this name before?
                    Boolean allowedState = mFilterMap.get(name);
                    if (allowedState == null) {
                        // New class -- remember whether it is allowed
                        clazz = mContext.getClassLoader().loadClass(
                                prefix != null ? (prefix + name) : name);
                       
                        boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                        mFilterMap.put(name, allowed);
                        if (!allowed) {
                            failNotAllowed(name, prefix, attrs);
                        }
                    } else if (allowedState.equals(Boolean.FALSE)) {
                        failNotAllowed(name, prefix, attrs);
                    }
                }
            }

            Object[] args = mConstructorArgs;
            args[1] = attrs;
            return (View) constructor.newInstance(args);

        } catch (NoSuchMethodException e) {
            InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class "
                    + (prefix != null ? (prefix + name) : name));
            ie.initCause(e);
            throw ie;

        } catch (ClassNotFoundException e) {
            // If loadClass fails, we should propagate the exception.
            throw e;
        } catch (Exception e) {
            InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class "
                    + (clazz == null ? "<unknown>" : clazz.getName()));
            ie.initCause(e);
            throw ie;
        }
    }

因为这个方法中会抛出ClassNotFoundException 的异常,抛异常的位置已经红色标注,loadClass时出错了,已经知道,这个欲加载的类为“com.zhoujie.MyGridView”,也就是classLoader没有找到这个MyGridView类。通过分析,调试,发现这的mContext有问题。

那这个mContext是从哪里来的呢?下面就着重分析这个mContext的来源。

这个得从分析launcher入手,因为创建widget的过程是在launcher中实现的,长按桌面的空白区域,通过一系列的对话框选择,可以创建widget,通过查看launcherPlus的代码,发现Workspace继承了widgetspace这个类,显然在widget上上下拖动listView时,事件响应是在launcher中做的处理。

首先看launcher的启动过程

下面来看registerProvider()方法:

public void registerProvider() {
        final Context context = getContext();

        IntentFilter filter = new IntentFilter();
        filter.addAction(LauncherIntent.Action.ACTION_START_FRAME_ANIMATION);
        filter.addAction(LauncherIntent.Action.ACTION_STOP_FRAME_ANIMATION);
        filter.addAction(LauncherIntent.Action.ACTION_START_TWEEN_ANIMATION);
        context.registerReceiver(mAnimationProvider, filter);

        IntentFilter scrollFilter = new IntentFilter();
        scrollFilter.addAction(LauncherIntent.Action.ACTION_SCROLL_WIDGET_START);
        scrollFilter.addAction(LauncherIntent.Action.ACTION_SCROLL_WIDGET_CLOSE);
        scrollFilter.addAction(LauncherIntent.Action.ACTION_SCROLL_WIDGET_CLEAR_IMAGE_CACHE);
        scrollFilter.addAction(LauncherIntent.Action.ACTION_SCROLL_WIDGET_SELECT_ITEM);
        context.registerReceiver(mScrollViewProvider, scrollFilter);
    }

可以看到在这个方法里注册了一系列和listView有关的intentfilter到mScrollViewProvider中。

当我们长按点击创建一个contactWidget的过程中,launcher会收到来自contactWidget的广播,这里可以从代码中看到:

在contactWidget1的onReceive()中:

在CreateMakeScrollableIntent()中可以清楚地看到,创建了一个ACTION_SCROLL_WIDGET_START 类型的intent,这个intent launcher的ScrollViewProvider可以接收到,进行处理。

ScrollViewProvider.onReceive()---->makeScrollable()

在makeScrollable()中

Context remoteContext = localContext.createPackageContext(remoteContextPackageName, Context.CONTEXT_IGNORE_SECURITY);

......................

  if (intent.hasExtra(LauncherIntent.Extra.Scroll.EXTRA_LISTVIEW_REMOTEVIEWS)) {
                        SimpleRemoteViews rvs = (SimpleRemoteViews)intent.getParcelableExtra(LauncherIntent.Extra.Scroll.EXTRA_LISTVIEW_REMOTEVIEWS);
                        dummyView = rvs.apply(remoteContext, null);

...........................

通过分析可知这里的remoteContext 最终会传给LayoutInflater的mContext。

回到刚才论述的                clazz = mContext.getClassLoader().loadClass(
                        prefix != null ? (prefix + name) : name);

这里的这个mContext.getClassLoader() 得到的ClassLoader实际上是dalvik.system.PathClassLoader,这个PathClassLoader只加载已经安装到android系统中的apk文件,也就是/data/app目录下的apk文件,可问题是这个PathClassLoader的libPath=null,path=“.",因此最终导致加载com.zhoujie.MyGridView失败。我估计这个是android系统自己的bug,因为mContext中有个状态mPackageInfo中是有contactWidget 的apk的信息的,而mContext.getClassLoader()后得到的classloader中的path以及libPath都为空,这说明mContext.getClassLoader()这个方法遗失了data的apk的信息。

 

你可能感兴趣的:(xml,android,ListView,ClassLoader,animation,Constructor)