滚动时呼叫列表视图滞后

Call list view lag while scrolling

我正在处理呼叫日志应用程序示例。在此应用程序中,我的片段在列表中显示 Dialed Type 呼叫。在每个列表项中,它显示来自联系人、号码、姓名和时间的照片。它工作正常,但在滚动时会滞后。

片段代码:

package com.example.vl.calllogs;


import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.provider.CallLog;
import android.provider.ContactsContract;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.util.Log;
import android.view.View;
import android.widget.CursorAdapter;
import android.widget.ImageView;
import android.widget.ResourceCursorAdapter;
import android.widget.TextView;

import org.w3c.dom.Text;

import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Created by vl on 12/29/2015.
 */
public class TabDialedFragment extends ListFragment implements LoaderManager.LoaderCallbacks<Cursor> {

    CursorAdapter mAdapter;

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        setEmptyText("No Dialed Numbers");
        mAdapter = new MyCursorAdapter(getActivity(), R.layout.fragment_tab_dialed, null, 0);
        setListAdapter(mAdapter);
        getLoaderManager().initLoader(0, null, this);
    }

    private static final String[] PROJECTION = {
            CallLog.Calls._ID,
            CallLog.Calls.DATE,
            CallLog.Calls.CACHED_NAME,
            CallLog.Calls.CACHED_PHOTO_ID,
            CallLog.Calls.NUMBER,
            CallLog.Calls.DURATION
    };

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        Uri baseUri  = CallLog.Calls.CONTENT_URI;
        String selection = CallLog.Calls.TYPE + "= 2";
        return new CursorLoader(getActivity(), baseUri, PROJECTION,selection, null, CallLog.Calls.DATE + " DESC" );
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
       mAdapter.swapCursor(data);
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
       mAdapter.swapCursor(null);
    }

    class MyCursorAdapter extends ResourceCursorAdapter{

        MyCursorAdapter(Context context, int layout, Cursor cursor, int flags ){
            super(context, layout,cursor,flags);
        }

        @Override
        public void bindView(View view, Context context, Cursor cursor) {
            TextView name = (TextView) view.findViewById(R.id.name);
            String nameString = cursor.getString(cursor.getColumnIndex(CallLog.Calls.CACHED_NAME));
            if(nameString == null || "".equals(nameString.trim())){
                name.setText("Unknown");
            }else{
                name.setText(nameString);
            }

            TextView time = (TextView) view.findViewById(R.id.time);
            String timeS = cursor.getString(cursor.getColumnIndex(CallLog.Calls.DATE));
            Date callDayTime = new Date(Long.valueOf(timeS));
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("EEE, d MMM yyyy HH:mm");
            String str = simpleDateFormat.format(callDayTime);

            String durationS = cursor.getString(cursor.getColumnIndex(CallLog.Calls.DURATION));
            time.setText(String.format(getActivity().getResources().getString(R.string.thirdLine), str, durationS));
            TextView number = (TextView) view.findViewById(R.id.number);
            String numberS = cursor.getString(cursor.getColumnIndex(CallLog.Calls.NUMBER));
            number.setText(numberS);

            int contactID = getContactIDFromNumber(numberS);
            ImageView imageView = (ImageView) view.findViewById(R.id.imageView);
            imageView.setImageBitmap(getPhoto(contactID+""));
         }

        public int getContactIDFromNumber(String contactNumber)
        {
            contactNumber = Uri.encode(contactNumber);
            int phoneContactID = -1;
            Cursor contactLookupCursor = getActivity().getContentResolver().query(Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,contactNumber),new String[] {ContactsContract.PhoneLookup.DISPLAY_NAME, ContactsContract.PhoneLookup._ID}, null, null, null);
            while(contactLookupCursor.moveToNext()){
                phoneContactID = contactLookupCursor.getInt(contactLookupCursor.getColumnIndexOrThrow(ContactsContract.PhoneLookup._ID));
            }
            contactLookupCursor.close();

            return phoneContactID;
        }

        private Bitmap getPhoto(String id){
            Bitmap photo = null;
            try{
                InputStream inputStream = ContactsContract.Contacts.openContactPhotoInputStream(
                        getActivity().getContentResolver(),
                        ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, new Long(id).longValue()));
                if(inputStream != null)
                    photo= BitmapFactory.decodeStream(inputStream);
            }catch (Exception e){

            }
            return photo;
        }

    }

}

我觉得这可能是从联系人中获取照片的有效方式的问题。在这里,我首先获取 contact_id,然后使用联系人 ID 查询照片。这是正确的方法吗?

最后的查询不是异步工作的。要使异步我应该怎么做?

您的 bindView 存在三个主要的性能问题,您应该解决。

  1. 最严重的问题

private Bitmap getPhoto 的调用是一个 SQLite 操作,然后是磁盘加载操作。这需要几毫秒才能发生,并且肯定落后于 UI.

坏消息是后台线程图像加载和缓存是一个非常复杂的主题,很难正确编码。

好消息是现在我们有很多很棒的库可以为您完成这项工作。下面是使用 Picasso library

加载它的代码
Uri uri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, id);
Picasso.with(getActivity()).load(uri).into(imageView);
  1. 相当糟糕的问题:

调用 public int getContactIDFromNumber 也在执行 SQLite 查询,而且速度也很慢。

很遗憾,对于您应该如何修复它,我没有任何重要建议。这是一个缓慢的操作,你应该在后台线程中进行。让它在适配器内部工作将是一个重大的重构。

我的建议是扩展 CursorLoader 并在它完成加载游标(但仍在后台线程中)后使其执行所有这些查询并将其保存在某些 HashMap

  1. 小问题:

    • 使用 Holder Pattern 来避免对 findViewById(int).
    • 的所有调用
    • 同时避免在 bindView 中创建新对象。例如,对于整个 class,您应该只有 1 个 private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("EEE, d MMM yyyy HH:mm");,并且始终使用同一个实例。适配器也只有 1 个 Date 对象,只需调用 callDayTime.setTime(Long.valueOf(timeS));

希望对您有所帮助。