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)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章