这里并不打算对整个短信源码进行分析,完全是看了某部分代码后的自我总结。我从GIT上clone了Conversation(即短信程序)的所有源码,结果编译不过。不过这对分析它的源码并不造成太大的阻碍。
这里主要对短信主界面的数据和UI的交互角度进行分析,因为我自己写的短信程序在加入获取联系人头像功能后,程序启动时花费的查询时间太长。虽然我也觉得系统默认的短信程序,甚至HandcentSMS,启动时间都不是很快。(大概是我的机器性能太差)
一、代码结构
Conversation中整体结构主要包括com.Android.mms.data和com.android.mms.ui,如名字所示,大概就是数据处理部分和UI部分。数据部分主要是获取/缓存联系人信息、获取/缓存会话信息等。
ConversationList类是程序的主activity,派生于ListActivity,就是一个大的列表。此外:
ConversationListAdapter是这个ListView的adapter,派生于CursorAdapter;
ConversationListItem是一个自定义的ViewGroup,派生于RelativeLayout,用于表示会话列表的每一个item;
Conversation表示一个会话数据;Contact表示一个联系人;ContactList维护一个联系人列表;
RecipientIdCache用于开线程读取一个特殊的表,该表映射会话数据到联系人信息,也就是通过Recipient就可以获取联系人信息。
二、UI结构
这里的UI主要就是ConversationList/ConversationListAdapter/ConversationListItem三者之间的交互。
在layout中,conversation_list_item.xml作为这个ListView(ConversationList)的item定义,直接使用了ConversationListItem这个view:
Java代码
、
<com.android.mms.ui.ConversationListItem xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:background="@drawable/conversation_item_background_unread"
android:paddingRight="10dip" >
这个自定义item最重要的工作,就是将会话数据绑定到UI控件上,例如QuickContactBadge。在ListView的使用中,要绑定数据,还有个方法就是自写adapter,在构造adapter时就传入所有数据。但是如你所见,这种方法需要先读取出所有的数据。
而这个系统自带的短信程序,则没有一次读入。这个自定义item还有个功能就是,作为一条联系人信息的更新监听器。读取联系人信息是非常慢的,因为会涉及到几个表的查询。在构造这个item时,程序在另一个线程中异步读取联系人信息,而item只有一个联系人的简要信息(电话号码)。当联系人读出来后,再通知它的监听器,也就是这个item,然后更新UI显示。
ConversationListAdapter中只实现了bindView和newView这两个函数,此外,它作为listView的AbsListView.RecyclerListener,还实现了onMovedToScrapHeap函数。
关于RecyclerListener,这里有篇文章从源码级角度分析了下,大概意思就是ListView在处理item时,有个缓存机制。
三、数据与UI的映射
这部分才是重要的分析部分,也是我需要学习的部分。
ConversationList的onStart中,开启了一个异步查询,查询所有的会话:
Java代码 @Override
protected void onStart() {
super.onStart();
.......
startAsyncQuery();
startyAsyncQuery调用了Conversation.startQueryForAll函数,该函数说白了还是调用AsyncQueryHandler.startQuery函数:
Java代码
public static void startQueryForAll(AsyncQueryHandler handler, int token) {
handler.cancelOperation(token);
handler.startQuery(token, null, sAllThreadsUri,
ALL_THREADS_PROJECTION, null, null, Conversations.DEFAULT_SORT_ORDER);
}
关于如何获取会话列表,其实就是个SQL的连表查询,可以参见这里:获取短信会话列表
当查询完后,android回调到自己实现的AsyncQueryHandler.onQueryComplete,该函数主要就是告诉adapter,we have done!:
Java代码
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
switch (token) {
case THREAD_LIST_QUERY_TOKEN:
mListAdapter.changeCursor(cursor);
一旦adapter获得了一个cursor后,就会主动去取得listview的各项数据。以上便是获取会话列表的大致流程。
接下来看看联系人获取的流程:
adapter获得数据后,会调用bindView来绑定数据到UI的item:
Java代码 @Override
public void bindView(View view, Context context, Cursor cursor) {
if (!(view instanceof ConversationListItem)) {
Log.e(TAG, "Unexpected bound view: " + view);
return;
}
ConversationListItem headerView = (ConversationListItem) view;
Conversation conv = Conversation.from(context, cursor);
ConversationListItemData ch = new ConversationListItemData(context, conv);
headerView.bind(context, ch);
}
Conversation.from函数会先检查Conversation缓存中是否有该cursor对应的数据,没有的话则会从cursor中取:
Java代码
public static Conversation from(Context context, Cursor cursor) {
// First look in the cache for the Conversation and return that one. That way, all the
// people that are looking at the cached copy will get updated when fillFromCursor() is
// called with this cursor.
long threadId = cursor.getLong(ID);
if (threadId > 0) {
Conversation conv = Cache.get(threadId);
if (conv != null) {
fillFromCursor(context, conv, cursor, false); // update the existing conv in-place
return conv;
}
}
Conversation conv = new Conversation(context, cursor, false);
try {
Cache.put(conv);
} catch (IllegalStateException e) {
LogTag.error("Tried to add duplicate Conversation to Cache");
}
return conv;
}
然后主要是fillFromCursor函数(如果是创建新的Conversation,其构造函数中也是调用了该函数),该函数就是简单地从cursor中getXXXX获取各个数据,并且,最重要的,获取联系人信息:
Java代码 private static void fillFromCursor(Context context, Conversation conv,
Cursor c, boolean allowQuery) {
...
ContactList recipients = ContactList.getByIds(recipientIds, allowQuery);
注意这里allowQuery参数为false。
ContactList.getByIds函数根据Conversation中recipientIds获取出对应的address,然后根据address从联系人URI中进一步获取联系人信息。
Java代码 public static ContactList getByIds(String spaceSepIds, boolean canBlock) {
ContactList list = new ContactList();
for (RecipientIdCache.Entry entry : RecipientIdCache.getAddresses(spaceSepIds)) {
if (entry != null && !TextUtils.isEmpty(entry.number)) {
Contact contact = Contact.get(entry.number, canBlock);
contact.setRecipientId(entry.id);
list.add(contact);
}
}
return list;
}
最终的contact被生成于Contact.get(entry.number, canBlock)中。该函数在canBlock为false的情况下,会push一个异步执行体(Runnable)到一个线程中。然后将contact返回。最终返回到adapter那一层的函数。
这个异步查询线程,会真正地去查询联系人信息。在此之前,外界获取出来的联系人不过是一个很简单的信息:只有电话号码。
adapter的bindView中,紧接着:
Java代码 ConversationListItemData ch = new ConversationListItemData(context, conv);
headerView.bind(context, ch);
}
bind函数中很重要的操作,就是建立该会话对应的联系人对象的监听:
Java代码
ContactList contacts = ch.getContacts();
if (DEBUG) Log.v(TAG, "bind: contacts.addListeners " + this);
Contact.addListener(this);
以上过程,即展示了会话数据是如何映射到ListView的item,及联系人信息是如何与会话和listview item建立联系(即异步查询,然后同步)。
值得一提的是:查询联系人信息都是在bindView时发生,当一个listview被显示出来后,未显示的item是不会被触发bind的,也就是说:在listview显示时,不会触发查询整个会话对应的所有联系人,只有显示出来的item才会涉及到查询联系人。
基于以上,我写了一个测试程序。结果似乎很好,就像系统自带的短信程序一样。启动速度依然不算快。问题的所在,似乎并不在于查询时间花费的很长。listview倒是早就显示出来了(看见了程序标题),但是items却要花些时间才能显示。
而且,我的测试程序更为恶心的是,listview在上下滚动时,会显得有点卡。经过一番折腾,发现是bindView里花的时间过长,后来加了Conversation的缓存,甚至去掉了日志,依然有点卡。不知道有否高手帮我解决下。
测试例子见附件。
1.10.2011 update
之前例子程序中ListView上下滚动时,会显得有点卡。本来都放弃不管了,结果今天打开eclipse时莫名其妙就想起这事:也许应该用ListActivity!结果一改,还真不卡了。注:系统的短信程序用的就是ListActivity。
A.逻辑推理
1、你让工人为你工作7天,给工人的回报是一根金条。金条平分成相连的7段
,你必须在每天结束时给他们一段金条,如果只许你两次把金条弄断,你如何给你
的工人付费?
2、请把一盒蛋糕切成8份,分给8个人,但蛋糕盒里还必须留有一份。
3、小明一家过一座桥,过桥时是黑夜,所以必须有灯。现在小明过桥要1秒,
小明的弟弟要3秒,小明的爸爸要6秒,小明的妈妈要8秒,小明的爷爷要12秒。每
次此桥最多可过两人,而过桥的速度依过桥最慢者而定,而且灯在点燃后30秒就会
熄灭。问:小明一家如何过桥?
4、一群人开舞会,每人头上都戴着一顶帽子。帽子只有黑白两种,黑的至少
有一顶。每个人都能看到其他人帽子的颜色,却看不到自己的。主持人先让大家看
看别人头上戴的是什么帽子,然后关灯,如果有人认为自己戴的是黑帽子,就打自
己一个耳光。第一次关灯,没有声音。于是再开灯,大家再看一遍,关灯时仍然鸦
雀无声。一直到第三次关灯,才有劈劈啪啪打耳光的声音响起。问有多少人戴着黑 帽子?
5、请估算一下CN TOWER电视塔的质量。
6、一楼到十楼的每层电梯门口都放着一颗钻石,钻石大小不一。你乘坐电梯 从一楼到十楼,每层楼电梯门都会打开一次,只能拿一次钻石,问怎样才能拿到最大的一颗?
7、U2合唱团在17分钟内得赶到演唱会场,途中必需跨过一座桥,四个人从桥 的同一端出发,你得帮助他们到达另一端,天色很暗,而他们只有一只手电筒。一次同时最多可以有两人一起过桥,而过桥的时候必须持有手电筒,所以就得有人把 手电筒带来带去,来回桥两端。手电筒是不能用丢的方式来传递的。四个人的步行
速度各不同,若两人同行则以较慢者的速度为准。Bono需花1分钟过桥,Edge需花 2分钟过桥,Adam需花5分钟过桥,Larry需花10分钟过桥。他们要如何在17分钟内 过桥呢?
8、烧一根不均匀的绳要用一个小时,如何用它来判断半个小时 ?
9、为什么下水道的盖子是圆的?
10、美国有多少辆加油站(汽车)?
11、有7克、2克砝码各一个,天平一只,如何只用这些物品三次将140克的盐 分成50、90克各一份?
12、有一辆火车以每小时15公里的速度离开洛杉矶直奔纽约,另一辆火车以第小时20公里的速度从纽约开往洛杉矶。如果有一只鸟,以外30公里每小时的速度和 两辆火车现时启动,从洛杉矶出发,碰到另辆车后返回,依次在两辆火车来回的飞行,直道两面辆火车相遇,请问,这只小鸟飞行了多长距离?
13、你有两个罐子,50个红色弹球,50个蓝色弹球,随机选出一个罐子,随机 选取出一个弹球放入罐子,怎么给红色弹球最大的选中机会?在你的计划中,得到 红球的准确几率是多少?
14、想象你在镜子前,请问,为什么镜子中的影像可以颠倒左右,却不能颠倒 上下?
15、你有四人装药丸的罐子,每个药丸都有一定的重量,被污染的药丸是没被 污染的重量+1.只称量一次,如何判断哪个罐子的药被污染了?
16、如果你有无穷多的水,一个3夸脱的和一个5夸脱的提桶,你如何准确称出 4夸脱的水?
17、你有一桶果冻,其中有黄色,绿色,红色三种,,闭上眼睛选出同样颜色 的两个,抓取同种颜色的两个。抓取多少个就可以确定你肯定有两个同一颜色的果冻?
18、将汽车钥匙插入车门,向哪个方向旋转就可以打开车锁?
19、如果要你能去掉50个州的任何一个,那你去掉哪一个,为什么?
20、对一批编号为1~100 全部开关朝上开的灯进行以下操作
凡是1 的倍数反方向拨一次开关2 的倍数反方向又拨一次开关3 的倍数反方向 又拨一次开关。
问最后为关熄状态的灯的编号。
21、假设一张圆盘像唱机上的唱盘那样转动。这张盘一半是黑色,一半是白色 。假设你有数量不限的一些颜色传感器。要想确定圆盘转动的方向,你需要在它周围摆多少个颜色传感器?它们应该被摆放在什么位置?
22、假设时钟到了12点。注意时针和分针重叠在一起。在一天之中,时针和分针共重叠多少次?你知道它们重叠时的具体时间吗?
23、中间只隔一个数字的两个奇数被称为奇数对,比如17和19。证明奇数对之 间的数字总能被6整除(假设这两个奇数都大于6)。现在证明没有由三个奇数组成 的奇数对。
24、一个屋子有一个门(门是关闭的)和3盏电灯。屋外有3个开关,分别与这 3盏灯相连。你可以随意操纵这些开关,可一旦你将门打开,就不能变换开关了。确定每个开关具体管哪盏灯。
25、假设你有8个球,其中一个略微重一些,但是找出这个球的惟一方法是将两个球放在天平上对比。最少要称多少次才能找出这个较重的球?
26、下面玩一个拆字游戏,所有字母的顺序都被打乱。你要判断这个字是什么 。假设这个被拆开的字由5个字母组成:
1.共有多少种可能的组合方式?
2.如果我们知道是哪5个字母,那会怎么样?
3.找出一种解决这个问题的方法。
27、有4个女人要过一座桥。她们都站在桥的某一边,要让她们在17分钟内全 部通过这座桥。这时是晚上。她们只有一个手电筒。最多只能让两个人同时过桥。不管是谁过桥,不管是一个人还是两个人,必须要带着手电筒。手电筒必须要传来传去,不能扔过去。每个女人过桥的速度不同,两个人的速度必须以较慢的那个人 的速度过桥。
第一个女人:过桥需要1分钟;
第二个女人:过桥需要2分钟;
第三个女人:过桥需要5分钟;
第四个女人:过桥需要10分钟。
比如,如果第一个女人与第4个女人首先过桥,等她们过去时,已经过去了10 分钟。如果让第4个女人将手电筒送回去,那么等她到达桥的另一端时,总共用去了20分钟,行动也就失败了。怎样让这4个女人在17分钟内过桥?还有别的什么方 法?
28、如果你有两个桶,一个装的是红色的颜料,另一个装的是蓝色的颜料。你 从蓝色颜料桶里舀一杯,倒入红色颜料桶,再从红色颜料桶里舀一杯倒入蓝颜料桶。两个桶中红蓝颜料的比例哪个更高?通过算术的方式来证明这一点。
B:疯狂计算
29、已知两个1~30之间的数字,甲知道两数之和,乙知道两数之积。
甲问乙:"你知道是哪两个数吗?"乙说:"不知道";
乙问甲:"你知道是哪两个数吗?"甲说:"也不知道";
于是,乙说:"那我知道了";
随后甲也说:"那我也知道了";
这两个数是什么?
30、4,4,10,10,加减乘除,怎么出24点?
31、1000!有几位数,为什么?
32、F(n)=1 n>8 n<12
F(n)=2 n<2
F(n)=3 n=6
F(n)=4 n=other
使用+ - * /和sign(n)函数组合出F(n)函数
sign(n)=0 n=0
sign(n)=-1 n<0
sign(n)=1 n>0
33、编一个程序求质数的和例如F(7)=1+3+5+7+11+13+17=58
34、。。。
请仅用一支笔画四根直线将上图9 各点全部连接
35、三层四层二叉树有多少种
36、1--100000 数列按一定顺序排列,有一个数字排错,如何纠错?写出最好方法。两个数字呢?
参考答案:
1、day1 给1 段,
day2 让工人把1 段归还给2 段,
day3 给1 段,
day4 归还1 2 段,给4 段。
day5 依次类推……
2、面对这样的怪题,有些应聘者绞尽脑汁也无法分成;而有些应聘者却感到
此题实际很简单,把切成的8份蛋糕先拿出7份分给7人,剩下的1份连蛋糕盒一起分
给第8个人。
4、假如只有一个人戴黑帽子,那他看到所有人都戴白帽,在第一次关灯时就
应自打耳光,所以应该不止一个人戴黑帽子;如果有两顶黑帽子,第一次两人都只
看到对方头上的黑帽子,不敢确定自己的颜色,但到第二次关灯,这两人应该明白
,如果自己戴着白帽,那对方早在上一次就应打耳光了,因此自己戴的也是黑帽子
,于是也会有耳光声响起;可事实是第三次才响起了耳光声,说明全场不止两顶黑
帽,依此类推,应该是关了几次灯,有几顶黑帽。
5、比如你怎样快速估算支架和柱子的高度、球的半径,算出各部分的体积等
等。招聘官的说法:"就CNTOWER这道题来说,它和一般的谜语或智力题还是有区别
的。我们称这类题为’快速估算题’,主要考的是快速估算的能力,这是开发软件
必备的能力之一。当然,题目只是手段,不是目的,最终得到一个结果固然是需要
的,但更重要的是对考生得出这个结果的过程也就是方法的考察。"Mr Miller为记
者举例说明了一种比较合理的答法,他首先在纸上画出了CN TOWER的草图,然后快
速估算支架和各柱的高度,以及球的半径,算出各部分体积,然后和各部分密度运
算,最后相加得出一个结果。
这一类的题目其实很多,如:"估算一下密西西比河里的水的质量。""如果你
是田纳西州州长,请估算一下治理好康柏兰河的污染需要多长时间。"
"估算一下一个行进在小雨中的人5分钟内身上淋到的雨的质量。"
Mr Miller接着解释道:"像这样的题目,包括一些推理题,考的都是人的
ProblemSolving(解决问题的能力),不是哪道题你记住了答案就可以了的。"
对于公司招聘的宗旨,Mr Miller强调了四点,这些是有创造性的公司普遍注
重的员工素质,是想要到知名企业实现自己的事业梦想的人都要具备的素质和能力 。
要求一:RawSmart(纯粹智慧),与知识无关。
要求二:Long-termPotential(长远学习能力)。
要求三:TechnicSkills(技能)。
要求四:Professionalism(职业态度)。
6、她的回答是:选择前五层楼都不拿,观察各层钻石的大小,做到心中有数
。后五层楼再选择,选择大小接近前五层楼出现过最大钻石大小的钻石。她至今也
不知道这道题的准确答案,"也许就没有准确答案,就是考一下你的思路,"她如是
说。
7、分析:有个康奈尔的学生写文章说他当时在微软面试时就是碰到了这道题
,最短只能做出在19分钟内过桥。
8、两边一起烧。
9、答案之一:从麻省理工大学一位计算机系教授那里听来的答案,首先在同
等用材的情况下他的面积最大。第二因为如果是方的、长方的或椭圆的,那无聊之
徒拎起来它就可以直接扔进地下道啦!但圆形的盖子嘛,就可以避免这种情况了
10、这个乍看让人有些摸不着头脑的问题时,你可能要从问这个国家有多少小
汽车入手。面试者也许会告诉你这个数字,但也有可能说:"我不知道,你来告诉
我。"那么,你对自己说,美国的人口是2.75亿。你可以猜测,如果平均每个家庭
(包括单身)的规模是2.5人,你的计算机会告诉你,共有1.1亿个家庭。你回忆起
在什么地方听说过,平均每个家庭拥有1.8辆小汽车,那么美国大约会有1.98亿辆
小汽车。接着,只要你算出替1.98亿辆小汽车服务需要多少加油站,你就把问题解
决了。重要的不是加油站的数字,而是你得出这个数字的方法。
12、答案很容易计算的:
假设洛杉矶到纽约的距离为s
那小鸟飞行的距离就是(s/(15+20))*30。
13、无答案,看你有没有魄力坚持自己的意见。
14、因为人的两眼在水平方向上对称。
15、从第一盒中取出一颗,第二盒中取出2 颗,第三盒中取出三颗。
依次类推,称其总量。
16、比较复杂:
A、先用3 夸脱的桶装满,倒入5 夸脱。以下简称3->5)
在5 夸脱桶中做好标记b1,简称b1)。
B、用3 继续装水倒满5 空3 将5 中水倒入3 直到b1 在3 中做标记b2
C、用5 继续装水倒满3 空5 将3 中水倒入5 直到b2
D、空3 将5 中水倒入3 标记为b3
E、装满5 空3 将5 中水倒入3 直到3 中水到b3
结束了,现在5 中水为标准的4 夸脱水。
20、素数是关,其余是开。
29、允许两数重复的情况下
答案为x=1,y=4;甲知道和A=x+y=5,乙知道积B=x*y=4
不允许两数重复的情况下有两种答案
答案1:为x=1,y=6;甲知道和A=x+y=7,乙知道积B=x*y=6
答案2:为x=1,y=8;甲知道和A=x+y=9,乙知道积B=x*y=8
解:
设这两个数为x,y.
甲知道两数之和 A=x+y;
乙知道两数之积 B=x*y;
该题分两种情况 :
允许重复, 有(1 <= x <= y <= 30);
不允许重复,有(1 <= x < y <= 30);
当不允许重复,即(1 <= x < y <= 30);
1)由题设条件:乙不知道答案
<=> B=x*y 解不唯一
=> B=x*y 为非质数
又∵ x ≠ y
∴ B ≠ k*k (其中k∈N)
结论(推论1):
B=x*y 非质数且 B ≠ k*k (其中k∈N)
即:B ∈(6,8,10,12,14,15,18,20...)
证明过程略。
2)由题设条件:甲不知道答案
<=> A=x+y 解不唯一
=> A >= 5;
分两种情况:
A=5,A=6时x,y有双解
A>=7 时x,y有三重及三重以上解
假设 A=x+y=5
则有双解
x1=1,y1=4;
x2=2,y2=3
代入公式B=x*y:
B1=x1*y1=1*4=4;(不满足推论1,舍去)
B2=x2*y2=2*3=6;
得到唯一解x=2,y=3即甲知道答案。
与题设条件:"甲不知道答案"相矛盾,
故假设不成立,A=x+y≠5
假设 A=x+y=6
则有双解。
x1=1,y1=5;
x2=2,y2=4
代入公式B=x*y:
B1=x1*y1=1*5=5;(不满足推论1,舍去)
B2=x2*y2=2*4=8;
得到唯一解x=2,y=4
即甲知道答案
与题设条件:"甲不知道答案"相矛盾
故假设不成立,A=x+y≠6
当A>=7时
∵ x,y的解至少存在两种满足推论1的解
B1=x1*y1=2*(A-2)
B2=x2*y2=3*(A-3)
∴ 符合条件
结论(推论2):A >= 7
3)由题设条件:乙说"那我知道了"
=>乙通过已知条件B=x*y及推论(1)(2)可以得出唯一解
即:
A=x+y, A >= 7
B=x*y, B ∈(6,8,10,12,14,15,16,18,20...)
1 <= x < y <= 30
x,y存在唯一解
当 B=6 时:有两组解
x1=1,y1=6
x2=2,y2=3 (∵ x2+y2=2+3=5 < 7∴不合题意,舍去)
得到唯一解 x=1,y=6
当 B=8 时:有两组解
x1=1,y1=8
x2=2,y2=4 (∵ x2+y2=2+4=6 < 7∴不合题意,舍去)
得到唯一解 x=1,y=8
当 B>8 时:容易证明均为多重解
结论:
当B=6时有唯一解 x=1,y=6当B=8时有唯一解 x=1,y=8
4)由题设条件:甲说"那我也知道了"
=> 甲通过已知条件A=x+y及推论(3)可以得出唯一解
综上所述,原题所求有两组解:
x1=1,y1=6
x2=1,y2=8
当x<=y时,有(1 <= x <= y <= 30);
同理可得唯一解 x=1,y=4
31、 解:1000
Lg(1000!)=sum(Lg(n))
n=1
用3 段折线代替曲线可以得到
10(0+1)/2+90(1+2)/2+900(2+3)/2=2390
作为近似结果,好象1500~3000 都算对
32、F(n)=1 n>8 n<12
F(n)=2 n<2
F(n)=3 n=6
F(n)=4 n=other
使用+ - * /和sign(n)函数组合出F(n)函数
sign(n)=0 n=0
sign(n)=-1 n<0
:sign(n)=1 n>0
解:只要注意[sign(n-m)*sign(m-n)+1]在n=m 处取1 其他点取0 就可以了
34、米字形的画就行了
http://blog.csdn.net/iefreer/archive/2009/08/17/4456376.aspx