已阅读:5,896 次
性能优化,永无止境:Android读取Contact
ian | Android | 2012/01/17


大多数情况下,我们都能从google大神那里请教到如何实现一个简单功能的方法,甚至有时候现成可用的代码也有,我们只需信手拈来,复制粘贴即可,为我们节省了大量的时间。

然而,不管从哪方面来说,仅仅知道如何实现功能,是远远不够的。我们需要了解为什么要这样实现、这段代码足够可靠么、这个系统函数内部是如何实现的、是否有其他替代方案、性能可以再优化么……每当头在我们耳边碎碎念这些问题时,我们都会想:为什么要考虑这么多无关大雅的因素啊?只要实现了功能、并且这个功能现在看起来还是很稳定的嘛,我为什么要了解系统API的内部实现啊,系统提供的接口我们直接用不就得了嘛!

为什么要考虑这么多?因为产品的要求,是很严苛的。一个看似毫无相关的调用,在某个你意想不到的时刻,可能就会导致软件Crash,然后用户反馈,产品经理找到你,你发现自己完全不知道为什么会发生这么神奇的事!甚至无法定位到这个Bug,然后……然后你可以卷铺盖走人了。

前段时间有做过Android系统联系人相关的东西,其中一个功能当然就是加载Contact数据库了,当时我在自己手机上简单测试了一下,160条联系人加载耗时3200ms。然后我对头儿说,这个性能确实有点不太理想哈,不过我会想办法再优化的,争取500条联系人5s内搞定。

当时我确实是信心满满的,直到测试MM们坚定的告诉我们说:最后测试的时候我们会拿一部有6K条联系人的手机来测试这个软件的!你妹啊!该有多蛋疼的人才会在手机里存6K个联系人啊!世上有这样的变态么? 测试MM邪恶的笑了一下:有的,老大的手机……所以,我只能老老实实的坐下来,为老大能在他有6K条联系人的手机上顺利体验我们的产品,而绞尽脑汁继续优化。

你不要问我第一版的代码是如何实现的,可不就是请教的google大神么!伪代码大概就是这样子的:

1
2
3
4
5
6
7
8
9
10
11
12
cursor = Query(ContactsContract.Contacts.CONTENT_URI)
while (cursor.moveToNext())
{
    int contactId = cursor.getInt();
    String name = cursor.getString();
    phonecursor = Query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI);
    while(phones.moveToNext())
    {        
    	  phoneNumber += phones.getString(phones.getColumnIndex(
                  ContactsContract.CommonDataKinds.Phone.NUMBER)) + ";";
    }
}

好吧,请问是哪个魂淡写的这样的代码?!竟然在循环里写子查询语句!有没有职业修养啊!

然后我们立刻就知道该如何优化这段代码了,将phonecursor的查询提到while循环的外面,这样原先需要N次数据库查询,现在只需要两次了。先把所有的PhoneNumber查询出来缓存着,然后根据ContactID直接从缓存里取到PhoneNumber值就好了。

好了,代码优化完成,现在我们看看效率如何。还是160条联系人,现在只需要650ms左右,性能足足提高了5倍!

然而,性能优化到这里就完成了么? 不,我们还可以继续优化下去!为什么还需要两次数据库查询呢,我们能不能一次查询就搞定?

后面的事情不想再多说了,总之最后的代码就是下面这样的,性能最后提升到300ms左右,也就是说,跟第一版的代码相比,性能足足提升了一个数量级!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
public int loadContacts()
{
	if (mParent == null)
        {
            return FConst.RESULT_FAILED;
        }
 
	Map<Integer, String> mapPhoneNumbers = new HashMap<Integer, String>();
	String[] phoneNumberProjection = new String[] {
			ContactsContract.CommonDataKinds.Phone.CONTACT_ID,
			ContactsContract.CommonDataKinds.Phone.NUMBER,
			ContactsContract.Contacts.DISPLAY_NAME,
			ContactsContract.Contacts.PHOTO_ID};
 
	Cursor phonecursor = mParent.getContentResolver().query(
			ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
			phoneNumberProjection, null, null, null);
	if (phonecursor != null)
        {
            try
            {    
            	if(mContactList == null)
            		mContactList = new ArrayList<UnifiedContact>();
 
            	mContactList.clear();
 
            	while (phonecursor.moveToNext())
            	{
            	     starttime = System.currentTimeMillis();            		 
 
            	     int contactId = phonecursor.getInt(0);
            	     String phoneNumber = phonecursor.getString(1);
            	     String name = phonecursor.getString(2);
                     int photoid = phonecursor.getInt(3);
 
                     String otherNumber = "";
 
                     if(mapPhoneNumbers.containsKey(contactId))
                     {
                    	 phoneNumber =  phoneNumber + ";" + mapPhoneNumbers.get(contactId);
                	 mapPhoneNumbers.put(contactId, phoneNumber);
 
                     	 phoneNumber = mapPhoneNumbers.get(Integer.valueOf(contactId));
                     	 if(phoneNumber.indexOf(";") > 0)
                     	 {
                     		otherNumber = phoneNumber.substring(phoneNumber.indexOf(";") + 1, phoneNumber.length());
                         	phoneNumber = phoneNumber.substring(0, phoneNumber.indexOf(";"));
                     	 }
                     	 for(PhoneContactcontact : mContactList)
                     	 {
                     		if(contact.getID() == contactId)
                     		{
                     			contact.setPhoneNumber(phoneNumber);
                     			contact.setOtherPhoneNumber(otherNumber);
                     			break;
                     		}
                     	}
                     }
                     else if(mapPhoneNumbers.containsKey(contactId) == false)
                     {
                    	 mapPhoneNumbers.put(contactId, phoneNumber);
 
                    	 PhoneContact contact = new PhoneContact();
                         contact.setID(contactId);
                         contact.setName(name);
                         contact.setPhoneNumber(phoneNumber);
                         contact.setOtherPhoneNumber(otherNumber);
                         contact.setAvatarID(photoid);
                         mContactList.add(contact);
                     }                     
            	 }
 
            }                        
            catch (Exception e)
            {
                FDebug.e(e.toString());
            }
            finally
            {
                phonecursor.close();
                phoneNumberProjection = null;
            }
        }
 
        return mContactList.size();
    }

写在最后
千万不要天真的以为这样的实现,就能对付老大那6K联系人的变态手机了。你会发现将联系人全部加载进来之后,你的UI可能直接Crash了,不要问我原因,我不会告诉你原因很有可能是OutOfMemory的!解决方案,自己慢慢想去吧。

原创文章,转载请注明:转载自ian的个人博客[http://www.icodelogic.com]
本文链接地址: http://www.icodelogic.com/?p=453

tags:

2条评论

  1. Alen 说:

    测试机性能不行,升级硬件

    • ian 说:

      跟机器性能没有关系,再高的性能也会被应用给耗尽。
      而且从测试的角度来说,一款全平台覆盖的应用,必须要考虑硬件性能最差的机型

发表评论

你需要先 登录 才能回复