Home » Android » android – onCreateView() being called too often in custom preference

android – onCreateView() being called too often in custom preference

Posted by: admin May 14, 2020 Leave a comment

Questions:

I have created a Custom preference which has the following constructor

public CoordinatesPreference(Context context, AttributeSet attrs)
{
    super(context, attrs);
    setLayoutResource(R.layout.coordinates_preference);
}

And I have Overriden onCreateView() so it writes to the log like this:

@Override
protected View onCreateView(ViewGroup parent)
{
    Log.d("test", "Creating Preference view");
    return super.onCreateView(parent);
}

and my log is full of "Creating Preference view" messages, this creates a laggy feel to scrolling and I believe convert view is supposed to solve this, I had a look at the preference source code and if convert view is null then onCreateView() is called.

for testing purposes I added this method:

@Override
public View getView(View convertView, ViewGroup parent)
{
    if (convertView == null)
    {
        return super.getView(convertView, parent);
    }
    return super.getView(convertView, parent);
}

and set a break point. I have found that almost always my convert view is null. and therefore it must create a new view, why is this? and how can I improve this to avoid a laggy preference screen?

EDIT: Changed the way the onCreate is called, now its all android I just use setLayoutResource. but this does not solve the problem…

EDIT2: I have used Debug.StartMethodTracing() and have found as I suspected that 55% of the time spend (when I’m just scrolling up and down) is spend on the Inflation of the preference from the method onCreateView() which is called from getView() when convertView is null.

Thanks, Jason

How to&Answers:

I was running into this problem and I tracked it down to the fact that I had the layout set both in the preferences.xml file and in my Preference subclass onCreateView() method. When I removed the layout from preferences.xml, onCreateView() stopped getting called multiple times.

Answer:

I don’t know what have you implemented in this custom preference, but maybe the super class doesn’t know how create a proper view to your preference?

From the documentation:

protected View onCreateView (ViewGroup
parent)

Since:
API Level 1 Creates the View to
be shown for this Preference in the
PreferenceActivity. The default
behavior is to inflate the main layout
of this Preference (see
setLayoutResource(int). If changing
this behavior, please specify a
ViewGroup with ID widget_frame. Make
sure to call through to the
superclass’s implementation.

http://developer.android.com/reference/android/preference/Preference.html

I guess you have set that id on the horizontal layout.

Now that I’m talking about it, why don’t you include this horizontal layout in the layout that you are inflating?

Answer:

I am not sure if the code you are using is an accurate test. I have a custom preference and I only override 5 methods and three of them are constructors.

public ImageButtonPreference(Context context)
{
    this(context, null);
}

public ImageButtonPreference(Context context, AttributeSet attrs)
{
    this(context, attrs, 0);
}

public ImageButtonPreference(Context context, AttributeSet attrs, int defStyle)
{
    super(context, attrs, defStyle);
    mInflater = LayoutInflater.from(context);

    // This is where I pull all of the styleable info from the attrs
    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ImageButtonPreference, defStyle, 0);
    for(int i = a.getIndexCount(); i >= 0; i--)
    {
        int attr = a.getIndex(i); 
        switch (attr)
        {
        case R.styleable.ImageButtonPreference_logo:
            mImageResource = a.getResourceId(attr, mImageResource);
            break;
        case R.styleable.ImageButtonPreference_button_text:
            mButtonText = a.getString(attr);
            break;
        }
    }
}

@Override
protected View onCreateView(ViewGroup parent)
{
    View view = mInflater.inflate(R.layout.image_button_preference, parent, false);
    return view;
}

@Override
protected void onBindView(View view)
{
    super.onBindView(view);

    ImageView image = (ImageView)view.findViewById(R.id.Logo);
    if(image != null && mImageResource != 0) image.setImageResource(mImageResource);

    Button button = (Button)view.findViewById(R.id.ConnectButton);
    if(button != null)
    {
        button.setText(mButtonText);
        button.setOnClickListener(mButtonListener);
    }
}

I have pulled this code, almost verbatim from the Android Source, so it should be just as fast as any other preference.

Answer:

As a more general answer, not specifically relating to custom preferences:

It’s hard to see with the code you posted, but if your needs to pull a preference every time it creates a view, it will be VERY slow and laggy as you describe. Even if the view does exist, you still need to set the value, and it sounds like that needs to come from the preference. Android preference reads are incredibly slow, so they can’t be associated with UI creation if you want a good fast experience.

I think you should store the preferences in the app (perhaps in the Activity, or subclass the Application and store them in there), in order to implement some simple caching. i.e. the first time you need a preference, request it from your app’s store, if it’s not there, pull it out of the preferences. If the preference is stored in the activity/application already, use that value without reaching out to the prefs. Then, when you write prefs out, write to the store AND the preference. By doing that it doesn’t matter how often getView() needs to create new views, as the preference can be accessed quickly using the copy in the activity/application object, but also stored durably in the preferences for the future.

I don’t know whether the preferences framework has some caching in it somewhere, but my experience of loading prefs is that if a few need loading the user WILL notice a lag, so caching is essential.