那些我们解过的bug之listview滑动不流畅

这里写图片描述

这是一个一年前的bug,因为我要写一篇专利,想到了他,就整理出来!

bug描述

客户提出了一个问题: (android 5.0 高通平台)

【压力测试】:

通话记录中有500个时滑动不流畅 通话记录中有500个通话记录,上下滑动时,不流畅

一看,是一个性能优化问题,这种问题有点不是怎么好解决的。

bug重现

我想先看一下这个现象是什么情况,问题来了,如何在通话记录中添加500条记录信息啊,显然手动添加是不太科学的。那么我就应该先把这个工作做完。

本来想找测试要一个apk,直接写入500条通话日志就可以,坑人的是他们要说什么签名,把apk在代码中编译,还要找对应的项目组……,好吧,大公司,果然是非常规范,一切都是那么的井井有条,算了,对于通话Log,我有一点印象,记得这个db数据库非常简单,那就自己写了。

通话日志的db数据库是在:

/data/data/com.android.providers.contacts/databases/contacts2.db

表calls
这里写图片描述

uri为 CallLog.Calls.CONTENT_URI

够了,就要这么多,我们就可以完成了一个插入500条通话日志的操作了。

核心代码:

    public void onAsyncInsertDataLister(CallLogInfo myCallLogInfo) {
        // TODO Auto-generated method stub
        Log.i(TAG, "Controller--onAsyncInsertDataLister");
        ContentValues values = new ContentValues();
        values.put(CALL_LOG_PROJECTION[1], myCallLogInfo.getNumber());
        values.put(CALL_LOG_PROJECTION[2], myCallLogInfo.getName());
        values.put(CALL_LOG_PROJECTION[3], myCallLogInfo.getDuration());
        values.put(CALL_LOG_PROJECTION[4], myCallLogInfo.getType());
        asyncQuery.startInsert(0, null, CallLog.Calls.CONTENT_URI, values);
    }

好了,现在,我们可以插入500条通话日志了,我们进入通话日志界面,发现确实是如果通话日志数量一多,listview滑动确实是卡:

bug初步分析

我看了一下代码,在CallLogAdapter.java文件中找到了view显示的bindView代码:

    private void bindView(View view, Cursor c, int count) {

        view.setAccessibilityDelegate(mAccessibilityDelegate);
        final CallLogListItemView callLogItemView = (CallLogListItemView) view;
        final CallLogListItemViews views = (CallLogListItemViews) view.getTag();

        // Default case: an item in the call log.
        views.primaryActionView.setVisibility(View.VISIBLE);

        final String number = c.getString(CallLogQuery.NUMBER);
        final int numberPresentation = c.getInt(CallLogQuery.NUMBER_PRESENTATION);
        final long date = c.getLong(CallLogQuery.DATE);
        final long duration = c.getLong(CallLogQuery.DURATION);
        final int callType = c.getInt(CallLogQuery.CALL_TYPE);
        final PhoneAccountHandle accountHandle = PhoneAccountUtils.getAccount(
                c.getString(CallLogQuery.ACCOUNT_COMPONENT_NAME),
                c.getString(CallLogQuery.ACCOUNT_ID));
        final Drawable accountIcon = PhoneAccountUtils.getAccountIcon(mContext,
                accountHandle);
        final String countryIso = c.getString(CallLogQuery.COUNTRY_ISO);

        final long rowId = c.getLong(CallLogQuery.ID);
        views.rowId = rowId;

        String accId = c.getString(CallLogQuery.ACCOUNT_ID);
        long subId = SubscriptionManager.DEFAULT_SUB_ID;
        if (accId!= null && !accId.equals("E") && !accId.toLowerCase().contains("sip")) {
             subId = Long.parseLong(accId);
        }

        // For entries in the call log, check if the day group has changed and display a header
        // if necessary.
        if (mIsCallLog) {
            int currentGroup = getDayGroupForCall(rowId);
            int previousGroup = getPreviousDayGroup(c);
            if (currentGroup != previousGroup) {
                views.dayGroupHeader.setVisibility(View.VISIBLE);
                views.dayGroupHeader.setText(getGroupDescription(currentGroup));
            } else {
                views.dayGroupHeader.setVisibility(View.GONE);
            }
        } else {
            views.dayGroupHeader.setVisibility(View.GONE);
        }

        // Store some values used when the actions ViewStub is inflated on expansion of the actions
        // section.
        views.number = number;
        views.numberPresentation = numberPresentation;
        views.callType = callType;
        // NOTE: This is currently not being used, but can be used in future versions.
        views.accountHandle = accountHandle;
        views.voicemailUri = c.getString(CallLogQuery.VOICEMAIL_URI);
        // Stash away the Ids of the calls so that we can support deleting a row in the call log.
        views.callIds = getCallIds(c, count);

        final ContactInfo cachedContactInfo = getContactInfoFromCallLog(c);

        final boolean isVoicemailNumber =
                PhoneNumberUtilsWrapper.INSTANCE.isVoicemailNumber(subId, number);

        // Where binding and not in the call log, use default behaviour of invoking a call when
        // tapping the primary view.
        if (!mIsCallLog) {
            views.primaryActionView.setOnClickListener(this.mActionListener);

            // Set return call intent, otherwise null.
            if (PhoneNumberUtilsWrapper.canPlaceCallsTo(number, numberPresentation)) {
                // Sets the primary action to call the number.
                views.primaryActionView.setTag(IntentProvider.getReturnCallIntentProvider(number));
            } else {
                // Number is not callable, so hide button.
                views.primaryActionView.setTag(null);
            }
        } else {
            // In the call log, expand/collapse an actions section for the call log entry when
            // the primary view is tapped.
            views.primaryActionView.setOnClickListener(this.mExpandCollapseListener);

            // Note: Binding of the action buttons is done as required in configureActionViews
            // when the user expands the actions ViewStub.
        }

        // Lookup contacts with this number
        final ContactInfo info = mAdapterHelper.lookupContact(
                number, numberPresentation, countryIso, cachedContactInfo);
        final Uri lookupUri = info.lookupUri;
        final String name = info.name;
        final int ntype = info.type;
        final String label = info.label;
        final long photoId = info.photoId;
        final Uri photoUri = info.photoUri;
        CharSequence formattedNumber = info.formattedNumber;
        final int[] callTypes = getCallTypes(c, count);
        final String geocode = c.getString(CallLogQuery.GEOCODED_LOCATION);
        final int sourceType = info.sourceType;
        final int features = getCallFeatures(c, count);
        final String transcription = c.getString(CallLogQuery.TRANSCRIPTION);
        final String operator = c.getString(CallLogQuery.OPERATOR);
        Long dataUsage = null;
        if (!c.isNull(CallLogQuery.DATA_USAGE)) {
            dataUsage = c.getLong(CallLogQuery.DATA_USAGE);
        }

        final PhoneCallDetails details;
        final String accountName = info.accountName;
        final String accountType = info.accountType;
        Account contactAccount;

        views.reported = info.isBadData;

        // The entry can only be reported as invalid if it has a valid ID and the source of the
        // entry supports marking entries as invalid.
        views.canBeReportedAsInvalid = mContactInfoHelper.canReportAsInvalid(info.sourceType,
                info.objectId);

        // Restore expansion state of the row on rebind.  Inflate the actions ViewStub if required,
        // and set its visibility state accordingly.
        expandOrCollapseActions(callLogItemView, isExpanded(rowId));

        if (TextUtils.isEmpty(name)) {
            if (mContext.getResources().getBoolean(R.bool.mark_emergency_call_in_call_log) &&
                    PhoneNumberUtils.isLocalEmergencyNumber(mContext, number)) {
                String emergencyName = mContext.getString(
                        com.android.internal.R.string.emergency_call_dialog_number_for_display);
                details = new PhoneCallDetails(number, numberPresentation,
                        formattedNumber, countryIso, geocode, callTypes, date, duration,
                        emergencyName, 0, "", null, null, 0, null, accountIcon, features,
                        dataUsage, transcription, Calls.DURATION_TYPE_ACTIVE, subId, operator);
            } else {
                details = new PhoneCallDetails(number, numberPresentation,
                        formattedNumber, countryIso, geocode, callTypes, date, duration,
                        null, accountIcon, features, dataUsage, transcription, subId, operator);
            }
        } else {
            details = new PhoneCallDetails(number, numberPresentation,
                    formattedNumber, countryIso, geocode, callTypes, date,
                    duration, name, ntype, label, lookupUri, photoUri, sourceType,
                    null, accountIcon, features, dataUsage, transcription,
                    Calls.DURATION_TYPE_ACTIVE, subId, operator);
        }

        int contactType = ContactPhotoManager.TYPE_DEFAULT;

        if (isVoicemailNumber) {
            contactType = ContactPhotoManager.TYPE_VOICEMAIL;
        } else if (mContactInfoHelper.isBusiness(info.sourceType)) {
            contactType = ContactPhotoManager.TYPE_BUSINESS;
        }

        String lookupKey = lookupUri == null ? null
                : ContactInfoHelper.getLookupKeyFromUri(lookupUri);

        String nameForDefaultImage = null;
        if (TextUtils.isEmpty(name)) {
            nameForDefaultImage = mPhoneNumberHelper.getDisplayNumber(details.accountId,
                    details.number, details.numberPresentation,
                    details.formattedNumber).toString();
        } else {
            nameForDefaultImage = name;
        }

        if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) {
            contactAccount = new Account(accountName, accountType);
        } else {
            contactAccount = null;
        }
        if (photoId == 0 && photoUri != null) {
            setPhoto(views, photoUri, lookupUri, nameForDefaultImage, lookupKey, contactType, contactAccount);
        } else {
            setPhoto(views, photoId, lookupUri, nameForDefaultImage, lookupKey, contactType, contactAccount);
        }

        // Listen for the first draw
        mAdapterHelper.registerOnPreDrawListener(view);

        bindBadge(view, info, details, callType);
    }

好了,到现在,我们看到了没有,如此多的与db数据库操作,这速度能快吗。

那问题如何解决了,我一开始看了一下小米机器,发现他的通话log界面,通话人的信息是没有联系人的头像信息,我就简单的把联系人的头像隐藏,但是没有发现性能有明显的提升,想想,其实也是,我们没有把耗时的从数据库中读取头像的操作去掉,效果当然是不明显的。

bug解决

我同时给高通提了一个case,第二天高通给了一个patch,我把patch打上,乖乖,速度明显的得到了提升。

那么,我们看看此patch:

CallLogAdapter.java

public class CallLogAdapter extends GroupingListAdapter
-        implements CallLogAdapterHelper.Callback, CallLogGroupBuilder.GroupCreator {
+        implements CallLogAdapterHelper.Callback, CallLogGroupBuilder.GroupCreator,
+        OnScrollListener {

+        if (!mAdapterHelper.isBusy()) {
+            // Only update views when ListView's scroll state is not SCROLL_STATE_FLING.
+            mCallLogViewsHelper.setPhoneCallDetails(mContext, views, details);
+        }


+        if (!mAdapterHelper.isBusy()) {
+            // Only update views when ListView's scroll state is not SCROLL_STATE_FLING.
+            mCallLogViewsHelper.setPhoneCallDetails(mContext, views, details);
+        }


+    @Override
+    public void onScrollStateChanged(AbsListView view, int scrollState) {
+        switch (scrollState) {
+            case OnScrollListener.SCROLL_STATE_IDLE:
+                mAdapterHelper.setBusy(false);
+                dataSetChanged();
+                break;
+            case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
+                mAdapterHelper.setBusy(false);
+                break;
+            case OnScrollListener.SCROLL_STATE_FLING:
+                // Do not update views when scroll state is SCROLL_STATE_FLING
+                mAdapterHelper.setBusy(true);
+                break;
+        }
+    }
+
+    @Override
+    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
+                         int totalItemCount) {
+        // no-op
+    }
CallLogAdapterHelper.java

+    private boolean mBusy;

+    public void setBusy(boolean isBusy) {
+        mBusy = isBusy;
+    }
+
+    public boolean isBusy(){
+        return mBusy;
+    }
    private class QueryThread extends Thread {
        private volatile boolean mDone = false;

        public QueryThread() {
            super("CallLogAdapter.QueryThread");
        }

        public void stopProcessing() {
            mDone = true;
        }

        @Override
        public void run() {
            boolean needRedraw = false;
            while (true) {
                // Check if thread is finished, and if so return immediately.
                if (mDone) return;
//BEGIN< DATE20150804> <patch for Dialer: FPS is low when scrolling call logs> hexiaoming
                // only update contact info when scroll state is not fling.
                if (mBusy) continue;
//END< DATE20150804> <patch for Dialer: FPS is low when scrolling call logs> hexiaoming
                // Obtain next request, if any is available.
                // Keep synchronized section small.
                ContactInfoRequest req = null;
                synchronized (mRequests) {
                    if (!mRequests.isEmpty()) {
                        req = mRequests.removeFirst();
                    }
                }

                if (req != null) {
                    // Process the request. If the lookup succeeds, schedule a
                    // redraw.
                    needRedraw |= queryContactInfo(req.number, req.countryIso, req.callLogInfo);
                } else {
                    // Throttle redraw rate by only sending them when there are
                    // more requests.
                    if (needRedraw) {
                        needRedraw = false;
                        mHandler.sendEmptyMessage(REDRAW);
                    }

                    // Wait until another request is available, or until this
                    // thread is no longer needed (as indicated by being
                    // interrupted).
                    try {
                        synchronized (mRequests) {
                            mRequests.wait(1000);
                        }
                    } catch (InterruptedException ie) {
                        // Ignore, and attempt to continue processing requests.
                    }
                }
            }
        }
    }



CallLogFragment.java
+        getListView().setOnScrollListener(mAdapter);

看到代码,明白了没,他的优化原理,其实是非常的简单,核心是加了一个标志位:mBusy

先开始给listview加了一个滑动监听器OnScrollListener,当我们手机在滑动列表时,列表的状态为SCROLL_STATE_FLING,就会将标志位mBusy设为true,不让列表刷新界面,同时也让读取数据库的操作不进行,就解决了滑动列表时滑动不流畅的问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hfreeman2008

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值