开发者

NullPointerException in Drawable's mutate method Android 1.6

开发者 https://www.devze.com 2023-02-16 01:05 出处:网络
When using mutate on Android 1.6 with list state Drawable I got always a Null Pointer Exception. It works flawlessly on higher Android versions. I am using the default android list_selector_background

When using mutate on Android 1.6 with list state Drawable I got always a Null Pointer Exception. It works flawlessly on higher Android versions. I am using the default android list_selector_background as the drawable to mutate, and I need to mutate it otherwise pressed backgrounds get pretty messy -> all or some 开发者_运维技巧of them are getting highlight when one is pressed.

The project is here:

http://code.google.com/p/tree-view-list-android/

And relevant piece of code here: http://code.google.com/p/tree-view-list-android/source/browse/src/pl/polidea/treeview/AbstractTreeViewAdapter.java#205

return activity.getResources()
                    .getDrawable(android.R.drawable.list_selector_background)
                    .mutate();

Stack trace below:

3-11 11:37:39.973: ERROR/AndroidRuntime(5304): java.lang.NullPointerException
03-11 11:37:39.973: ERROR/AndroidRuntime(5304):     at android.graphics.drawable.StateListDrawable.mutate(StateListDrawable.java:227)
03-11 11:37:39.973: ERROR/AndroidRuntime(5304):     at pl.polidea.treeview.AbstractTreeViewAdapter.getDrawableOrDefaultBackground(AbstractTreeViewAdapter.java:201)
03-11 11:37:39.973: ERROR/AndroidRuntime(5304):     at pl.polidea.treeview.AbstractTreeViewAdapter.populateTreeItem(AbstractTreeViewAdapter.java:210)
03-11 11:37:39.973: ERROR/AndroidRuntime(5304):     at pl.polidea.treeview.AbstractTreeViewAdapter.getView(AbstractTreeViewAdapter.java:153)
03-11 11:37:39.973: ERROR/AndroidRuntime(5304):     at android.widget.AbsListView.obtainView(AbsListView.java:1273)
03-11 11:37:39.973: ERROR/AndroidRuntime(5304):     at android.widget.ListView.makeAndAddView(ListView.java:1658)
03-11 11:37:39.973: ERROR/AndroidRuntime(5304):     at android.widget.ListView.fillDown(ListView.java:637)
03-11 11:37:39.973: ERROR/AndroidRuntime(5304):     at android.widget.ListView.fillFromTop(ListView.java:694)
03-11 11:37:39.973: ERROR/AndroidRuntime(5304):     at android.widget.ListView.layoutChildren(ListView.java:1516)
03-11 11:37:39.973: ERROR/AndroidRuntime(5304):     at android.widget.AbsListView.onLayout(AbsListView.java:1112)
03-11 11:37:39.973: ERROR/AndroidRuntime(5304):     at android.view.View.layout(View.java:6569)
03-11 11:37:39.973: ERROR/AndroidRuntime(5304):     at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)
03-11 11:37:39.973: ERROR/AndroidRuntime(5304):     at android.widget.LinearLayout.layoutVertical(LinearLayout.java:998)
03-11 11:37:39.973: ERROR/AndroidRuntime(5304):     at android.widget.LinearLayout.onLayout(LinearLayout.java:918)
03-11 11:37:39.973: ERROR/AndroidRuntime(5304):     at android.view.View.layout(View.java:6569)
03-11 11:37:39.973: ERROR/AndroidRuntime(5304):     at android.widget.FrameLayout.onLayout(FrameLayout.java:333)
03-11 11:37:39.973: ERROR/AndroidRuntime(5304):     at android.view.View.layout(View.java:6569)
03-11 11:37:39.973: ERROR/AndroidRuntime(5304):     at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)
03-11 11:37:39.973: ERROR/AndroidRuntime(5304):     at android.widget.LinearLayout.layoutVertical(LinearLayout.java:998)
03-11 11:37:39.973: ERROR/AndroidRuntime(5304):     at android.widget.LinearLayout.onLayout(LinearLayout.java:918)
03-11 11:37:39.973: ERROR/AndroidRuntime(5304):     at android.view.View.layout(View.java:6569)
03-11 11:37:39.973: ERROR/AndroidRuntime(5304):     at android.widget.FrameLayout.onLayout(FrameLayout.java:333)
03-11 11:37:39.973: ERROR/AndroidRuntime(5304):     at android.view.View.layout(View.java:6569)
03-11 11:37:39.973: ERROR/AndroidRuntime(5304):     at android.view.ViewRoot.performTraversals(ViewRoot.java:979)
03-11 11:37:39.973: ERROR/AndroidRuntime(5304):     at android.view.ViewRoot.handleMessage(ViewRoot.java:1613)
03-11 11:37:39.973: ERROR/AndroidRuntime(5304):     at android.os.Handler.dispatchMessage(Handler.java:99)
03-11 11:37:39.973: ERROR/AndroidRuntime(5304):     at android.os.Looper.loop(Looper.java:123)
03-11 11:37:39.973: ERROR/AndroidRuntime(5304):     at android.app.ActivityThread.main(ActivityThread.java:4203)
03-11 11:37:39.973: ERROR/AndroidRuntime(5304):     at java.lang.reflect.Method.invokeNative(Native Method)
03-11 11:37:39.973: ERROR/AndroidRuntime(5304):     at java.lang.reflect.Method.invoke(Method.java:521)
03-11 11:37:39.973: ERROR/AndroidRuntime(5304):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:791)
03-11 11:37:39.973: ERROR/AndroidRuntime(5304):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:549)
03-11 11:37:39.973: ERROR/AndroidRuntime(5304):     at dalvik.system.NativeStart.main(Native Method)


It seems that there is a bug in Android 1.6. When you dive into Android 1.6 sources you can find following code:

android.graphics.drawable.StateListDrawable.java:
...
public Drawable mutate() {
    if (!mMutated && super.mutate() == this) {
        final int[][] sets = mStateListState.mStateSets;
        final int count = sets.length;
        mStateListState.mStateSets = new int[count][];
        for (int i = 0; i < count; i++) {
            mStateListState.mStateSets[i] = sets[i].clone(); //NPE causing line
        }
        mMutated = true;
    }
    return this;
}
...

in line

mStateListState.mStateSets[i] = sets[i].clone(); //NPE causing line

clone() method is sometimes called on null object and it causes NullPointerException

in Android > 1.6 it has been fixed:

public Drawable mutate() {
        if (!mMutated && super.mutate() == this) {
            final int[][] sets = mStateListState.mStateSets;
            final int count = sets.length;
            mStateListState.mStateSets = new int[count][];
            for (int i = 0; i < count; i++) {
                final int[] set = sets[i];
                if (set != null) {
                    mStateListState.mStateSets[i] = set.clone();
                }
            }
            mMutated = true;
        }
        return this;
    }

but in Android 1.6 we need to do some workaround. Let's see why mStateSets[i] sometimes contains nulls:

android.graphics.drawable.DrawableContainer.DrowableContainerState:
... 
public final int addChild(Drawable dr) {
        final int pos = mNumChildren;

        if (pos >= mDrawables.length) {
            growArray(pos, pos+10); //Interesting line
        }

        dr.setVisible(false, true);
        dr.setCallback(mOwner);

        mDrawables[pos] = dr;
        mNumChildren++;
        mChildrenChangingConfigurations |= dr.getChangingConfigurations();
        mHaveOpacity = false;
        mHaveStateful = false;

        mConstantPadding = null;
        mPaddingChecked = false;
        mComputedConstantSize = false;

        return pos;
    }
...

above method is called during inflating object from xml. So the size of mStateListState.mStateSets is N*10.

Now let's see body of list_selector_background.xml that you are trying to inflate from resources refering by android.R.drawable.list_selector_background:

<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2008 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

          http://www.apache.org/licenses/LICENSE-2.0

     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->

<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:state_window_focused="false"
        android:drawable="@color/transparent" />

    <!-- Even though these two point to the same resource, have two states so the drawable will invalidate itself when coming out of pressed state. -->
    <item android:state_focused="true" android:state_enabled="false"
        android:state_pressed="true"
        android:drawable="@drawable/list_selector_background_disabled" />
    <item android:state_focused="true" android:state_enabled="false"
        android:drawable="@drawable/list_selector_background_disabled" />

    <item android:state_focused="true" android:state_pressed="true"
        android:drawable="@drawable/list_selector_background_transition" />
    <item android:state_focused="false" android:state_pressed="true"
        android:drawable="@drawable/list_selector_background_transition" />

    <item android:state_focused="true"
        android:drawable="@drawable/list_selector_background_focus" />

</selector>

this file contains only 6 items! So how can we solve the issue? 1. Create list_selector_background.xml in {Project}/res/drowable/ you can copy Android one and change only one line:

<item android:state_window_focused="false"
    android:drawable="@color/transparent" />

to

<item android:state_window_focused="false" android:drawable="@android:color/transparent" />
  1. Copy Android or create own files:

    list_selector_background_disabled.9.png list_selector_background_focus.9.png list_selector_background_longpress.9.png list_selector_background_pressed.9.png list_selector_background_transition.xml

  2. Modify list_selector_background.xml adding dummy items to match exactly (6+4=10) items condidtion:

    <item android:animationCache="true"
        android:drawable="@android:color/transparent" />
    
    <item android:animationCache="false"
        android:drawable="@android:color/transparent" />
    
    <item android:alwaysDrawnWithCache="false"
        android:drawable="@android:color/transparent" />
    
    <item android:alwaysDrawnWithCache="true"
        android:drawable="@android:color/transparent" />
    

Voilà. Compile and run. It should work now.

0

精彩评论

暂无评论...
验证码 换一张
取 消