Android开发_Cursor相关的性能问题

Android开发_Cursor相关的性能问题




08-23 05:48:31.844: ERROR/CursorWindow(1805): need to grow: mSize = 1048576, size = 11499, freeSpace() = 7397, numRows = 80
08-23 05:48:31.844: ERROR/CursorWindow(1805): not growing since there are already 80 row(s), max size 1048576
08-23 05:48:31.844: ERROR/Cursor(1805): Failed allocating 11499 bytes for blob at 228,7
08-23 05:48:31.849: DEBUG/Cursor(1805): finish_program_and_get_row_count row 12
08-23 05:48:31.851: DEBUG/browser/BrowserBookmarkFoldersAdapter(1805): getView()-Position:149
08-23 05:48:32.019: DEBUG/Cursor(1805): skip_rows row 148
这表明CursorWindow中的空闲空间已经不足,已经在申请新的空间,但似乎申请失败。这个错误有时候会导致查询得到的Cursor为null,有时候不会引发太严重的问题。但是它会引起性能上的问题,不停的申请空间会占用大量的CPU时间,从而导致整个手机变卡。特别是在ListView或GridView中绑定的Cursor,会导致无法滑动,或者滑动变的十分的卡。用Android2.3的原生Browser,打开其中的历史记录,当有超过200条历史记录时,不停的滑动,特别是由下向上滑时会变的十分的卡,而对于其书签,如果条目超过100,且每个都有缩略图时,滑动会变得特别的卡,甚至都打不开,就是这个原因。
这个问题没有根本的解法,这是Android系统的限制,唯一可行的就是想办法避免,也就是尽可能让Cursor的大小 小于1M,以下是可行的方法:
1. 只查询需要的字段
这个特别重要,根据UI显示的需要,或者实际的需要查询需要的字段。就是一定要给ContentResolver.query(uri, projection)第二个参数PROJECTION,如果这个参数为null,那么就会查询表中所有的字段,那么当条数一增加Cursor的大小 会增长很快。Browser中历史记录的原因就是它在query的时候查询了所有的字段,其数所库中保存了favicon和thumbnail二进制文件,因此当包含了这二个字段时,Cursor的容量很容易就达到了限制。
2. 二进制文件不要存在数据库中
数据库仅适用于保存一些较短文字,整数,布尔,浮点数等一些,易于查询和操作的轻量级的数据,目的也是在于快速搜索和查询。对于像图片,较长的文字(如文章)等大数据,最好直接以文件形式存储在硬盘中,然后在数据库保存它们的访问路径。对于像favicon这样的小图片也可以考虑存在数据库中,但是像对于thumbnail的图片就不明智,除非整个应用在数量上有限制(比如只有几十或百级)否则很容易在查询的时候达到1M的限制。
3. 对于特别大量的数据超过5000级或万级或十万级或百万级的要分段查询
08-23 05:48:31.844: ERROR/CursorWindow(1805): not growing since there are already 80 row(s), max size 1048576
08-23 05:48:31.844: ERROR/Cursor(1805): Failed allocating 11499 bytes for blob at 228,7
这样的LOG。而书签似乎都没有办法打开和滑动,其特别的卡。
究其原因就是它们在查询的时候都用了同一个字段集Browser.HISTORY_PROJECTION这个是把bookmarks表中的所有字段都 查询出来。书签,历史记录和最多访问虽是三个不同的展示页,但它们的数据是相同的都是来自bookmarks表。Bookmarks表中存有_id,title,url,bookmark,favicon,touch_icon,thumbnail等字段,其中favicon和thumbnail是二进制图片数据(byte[])。Browser.HISTORY_PROJECTION里面包含了所有的字段,当然也包含了favicon和thumbnail,所以当条目一旦达到200多时,Cursor就会达到其1M的限制,因此会导致性能下降,滑动变卡。
事实上对于历史记录和最多访问二个页面来讲thumbnail和touch_icon根本就没有用到,它只需要_id,title,url,bookmark和favicon;对于书签页,也仅是在GRID时才用到thumbnail。所以,只需要把查询时的字段集Browser.HISTORY_PROJECTION中的THUMBNAIL去掉,即可以解决滑动变卡。
07-31 10:00:27.479: W/CursorWrapperInner(15084): Cursor finalized without prior close()
07-31 10:00:27.479: W/CursorWrapperInner(15084): Cursor finalized without prior close()
07-31 10:00:27.479: W/CursorWrapperInner(15084): Cursor finalized without prior close()
07-31 10:00:27.479: W/CursorWrapperInner(15084): Cursor finalized without prior close()
07-31 10:00:27.479: W/CursorWrapperInner(15084): Cursor finalized without prior close()
07-31 10:00:27.479: W/CursorWrapperInner(15084): Cursor finalized without prior close()
07-31 10:00:27.479: W/CursorWrapperInner(15084): Cursor finalized without prior close()
07-31 10:00:27.479: W/CursorWrapperInner(15084): Cursor finalized without prior close()
07-31 10:00:27.479: W/CursorWrapperInner(15084): Cursor finalized without prior close()
07-31 10:00:27.489: W/CursorWrapperInner(15084): Cursor finalized without prior close()
07-31 10:00:27.489: W/CursorWrapperInner(15084): Cursor finalized without prior close()
07-31 10:00:27.489: W/CursorWrapperInner(15084): Cursor finalized without prior close()
07-31 10:00:27.489: W/CursorWrapperInner(15084): Cursor finalized without prior close()
07-31 10:00:27.489: W/CursorWrapperInner(15084): Cursor finalized without prior close()
07-31 10:00:27.489: W/CursorWrapperInner(15084): Cursor finalized without prior close()
07-31 10:00:27.489: W/CursorWrapperInner(15084): Cursor finalized without prior close()
07-31 10:00:27.489: W/CursorWrapperInner(15084): Cursor finalized without prior close()
07-31 10:00:35.299: E/DatabaseUtils(1164): java.lang.IllegalStateException: Process 15084 exceeded cursor quota 100, will kill it
07-31 10:00:35.299: E/DatabaseUtils(1164):  at android.database.CursorWindow$Injector.checkQuota(CursorWindow.java:77)
07-31 10:00:35.299: E/DatabaseUtils(1164):  at android.database.CursorWindow$Injector.addQuota(CursorWindow.java:66)
07-31 10:00:35.299: E/DatabaseUtils(1164):  at android.database.CursorWindow.recordNewWindow(CursorWindow.java:783)
07-31 10:00:35.299: E/DatabaseUtils(1164):  at android.database.CursorWindow.<init>(CursorWindow.java:165)
07-31 10:00:35.299: E/DatabaseUtils(1164):  at android.database.AbstractWindowedCursor.clearOrCreateWindow(AbstractWindowedCursor.java:198)
07-31 10:00:35.299: E/DatabaseUtils(1164):  at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:139)
07-31 10:00:35.299: E/DatabaseUtils(1164):  at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:133)
07-31 10:00:35.299: E/DatabaseUtils(1164):  at android.database.CursorToBulkCursorAdaptor.getBulkCursorDescriptor(CursorToBulkCursorAdaptor.java:143)
07-31 10:00:35.299: E/DatabaseUtils(1164):  at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:118)
07-31 10:00:35.299: E/DatabaseUtils(1164):  at android.os.Binder.execTransact(Binder.java:367)
07-31 10:00:35.299: E/DatabaseUtils(1164):  at dalvik.system.NativeStart.run(Native Method)
07-31 10:00:35.309: E/AndroidRuntime(15084): FATAL EXCEPTION: Thread-1569
07-31 10:00:35.309: E/AndroidRuntime(15084): java.lang.IllegalStateException: Process 15084 exceeded cursor quota 100, will kill it
07-31 10:00:35.309: E/AndroidRuntime(15084):  at android.os.Parcel.readException(Parcel.java:1433)
07-31 10:00:35.309: E/AndroidRuntime(15084):  at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:188)
07-31 10:00:35.309: E/AndroidRuntime(15084):  at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:140)
07-31 10:00:35.309: E/AndroidRuntime(15084):  at android.content.ContentProviderProxy.query(ContentProviderNative.java:366)
07-31 10:00:35.309: E/AndroidRuntime(15084):  at android.content.ContentResolver.query(ContentResolver.java:407)
07-31 10:00:35.309: E/AndroidRuntime(15084):  at android.content.ContentResolver.query(ContentResolver.java:350)
07-31 10:00:35.309: E/AndroidRuntime(15084):  at com.xwtec.goodfriendcircle.logic.UploadAddrBookLogin.getSystemContacts(UploadAddrBookLogin.java:216)
07-31 10:00:35.309: E/AndroidRuntime(15084):  at com.xwtec.goodfriendcircle.logic.UploadAddrBookLogin$2.run(UploadAddrBookLogin.java:165)
07-31 10:00:35.309: E/AndroidRuntime(15084):  at java.lang.Thread.run(Thread.java:856) 的异常
        Cursor cursor = null;
        Cursor phoneCursor = null;
        Cursor groupCursor = null;
        int phoneNum = 0;
        JSONArray contactsArray = new JSONArray();
        try
        {
            final ContentResolver contentResolver = GoodFriendCircleApp.getInstance().getContentResolver();
            //通讯录
            cursor = contentResolver.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
            if (null != cursor)
            {
                while (cursor.moveToNext())
                {
                    JSONObject jsonObject = new JSONObject();
                    // 联系人的ID
                    long contactid = cursor.getLong(cursor.getColumnIndex(ContactsContract.Contacts._ID));
                    // 联系人的姓名
                    String name = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
                    jsonObject.put("name", name);
                    //是否有电话号码
                    String hasPhoneNumber =
                        cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER));
                    if (!StringTools.isStringEmpty(hasPhoneNumber))
                    {
                        // 获取该联系人电话
                        phoneNum = Integer.parseInt(hasPhoneNumber);
                    }
                    else
                    {
                        phoneNum = -1;
                    }
                    if (phoneNum > 0)
                    {
                        phoneCursor =
                            contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                                null,
                                ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = " + contactid,
                                null,
                                null);
                        if (null != phoneCursor)
                        {
                            while (phoneCursor.moveToNext())
                            {
                                //联系人电话的类型
                                String phoneType =
                                    phoneCursor.getString(phoneCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.TYPE));
                                //电话号码
                                String number =
                                    phoneCursor.getString(phoneCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
                                //移动手机号码
                                if (!StringTools.isStringEmpty(phoneType)
                                    && Integer.valueOf(phoneType) == android.provider.ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE)
                                {
                                    jsonObject.put("phonenum", number);
                                }
                                //家庭固定号码
                                else if (!StringTools.isStringEmpty(phoneType)
                                    && Integer.valueOf(phoneType) == android.provider.ContactsContract.CommonDataKinds.Phone.TYPE_HOME)
                                {
                                    jsonObject.put("phonenum", number);
                                }
                                //其它号码
                                else
                                {
                                    jsonObject.put("phonenum", number);
                                }
                            }
                        }
                        else
                        {
                            jsonObject.put("phonenum", "");
                        }
                    }
                    else
                    {
                        jsonObject.put("phonenum", "");
                    }
                    contactsArray.put(jsonObject);
                }
            }
        }
        catch (JSONException e)
        {
            Log.e(TAG, e.toString());
        }
        finally
        {
            if (null != cursor)
            {
                cursor.close();
                cursor = null;
            }
            if (null != phoneCursor)
            {
                phoneCursor.close();
                phoneCursor = null;
            }
            if (null != groupCursor)
            {
                groupCursor.close();
                groupCursor = null;
            }
        }
        //Log.e(TAG, "contactsArray =" + contactsArray);
        return contactsArray;
   
因为在 这个while()语句中 的 phoneCurosr 只是在finally语句中 关闭了 循环 的最后一次,没有全部 关闭。因为它是一个全局变量,只关了最后一次。
{
phoneCursor.close();
phoneCursor = null;
}
        Cursor cursor = null;
        int phoneNum = 0;
        JSONArray contactsArray = new JSONArray();
        try
        {
            final ContentResolver contentResolver = GoodFriendCircleApp.getInstance().getContentResolver();
            //通讯录
            cursor = contentResolver.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
            if (null != cursor)
            {
                while (cursor.moveToNext())
                {
                    JSONObject jsonObject = new JSONObject();
                    // 联系人的ID
                    long contactid = cursor.getLong(cursor.getColumnIndex(ContactsContract.Contacts._ID));
                    // 联系人的姓名
                    String name = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
                    jsonObject.put("name", name);
                    //是否有电话号码
                    String hasPhoneNumber =
                        cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER));
                    if (!StringTools.isStringEmpty(hasPhoneNumber))
                    {
                        // 获取该联系人电话
                        phoneNum = Integer.parseInt(hasPhoneNumber);
                    }
                    else
                    {
                        phoneNum = -1;
                    }
                    
                    if(phoneNum < 0)
                    {
                        jsonObject.put("phonenum", "");
                    }
                    else
                    {
                        readPhoneNumbers(jsonObject, contactid);
                    }
                    contactsArray.put(jsonObject);
                }
            }
        }
        catch (JSONException e)
        {
            Log.e(TAG, e.toString());
        }
        finally
        {
            if (null != cursor)
            {
                cursor.close();
                cursor = null;
            }
        }
        return contactsArray;
    {
        Cursor phoneCursor = null;
        try
        {
            phoneCursor =
                GoodFriendCircleApp.getInstance().getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                    null,
                    ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = " + contactId,
                    null,
                    null);
            if (null == phoneCursor)
            {
                jsonObject.put("phonenum", "");
                return;
            }
            while (phoneCursor.moveToNext())
            {
                //联系人电话的类型
                String phoneType =
                    phoneCursor.getString(phoneCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.TYPE));
                //电话号码
                String number =
                    phoneCursor.getString(phoneCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
                //移动手机号码
                if (!StringTools.isStringEmpty(phoneType)
                    && Integer.valueOf(phoneType) == android.provider.ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE)
                {
                    jsonObject.put("phonenum", number);
                }
                //家庭固定号码
                else if (!StringTools.isStringEmpty(phoneType)
                    && Integer.valueOf(phoneType) == android.provider.ContactsContract.CommonDataKinds.Phone.TYPE_HOME)
                {
                    jsonObject.put("phonenum", number);
                }
                //其它号码
                else
                {
                    jsonObject.put("phonenum", number);
                }
            }
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (phoneCursor != null)
            {
                phoneCursor.close();
            }
        }
    }



    当数据库中存有大量数据的时候,用Cursor查询时要注意,有可能引发性能问题。数据库查询出来的Cursor都会由一个CursorWindow来进行数据管理,包括内存空间的申请和数据的填充。CursorWindow对Cursor中的内容大小有限制,限制为1024*1024也就是1M,换句话说Cursor中数据的大小不能超过1M,如果超过1M会引发如下的错误:

08-23 05:48:31.838: DEBUG/Cursor(1805): skip_rows row 149

无论表中的一条记录数据量如何的小,当条数达到5000级或者万级或者更多的时候,还是会达到1M的限制,这时就需要分段查询,比如每次查询500个,或者1000个。另外,如果是要做展示用,这么多数据一下子出来,用户也不方便查看。


【实例】Android2.3书签,历史记录和最多访问三个页面当数据量达到300左右时,就会出现滑动很卡的现象,特别是由下向上滑动时,特别的卡,会狂打出:

08-23 05:48:31.844: ERROR/CursorWindow(1805): need to grow: mSize = 1048576, size = 11499, freeSpace() = 7397, numRows = 80

 

 

关于Cursor没有及时关闭,而闪退的问题。基于好友圈项目的总结:

7-31 10:00:27.469: W/CursorWrapperInner(15084): Cursor finalized without prior close()

 

07-31 10:00:35.299: E/DatabaseUtils(1164): Writing exception to parcel

 

问题点:

if (null != phoneCursor)

 

后来改成


//把它单独提出来,一个一个的关闭cursor

  private void readPhoneNumbers(JSONObject jsonObject, long contactId)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值