ConversationList是短信很重要的一个Activity,单布局也很简单,就是一个ListActivity。
layout.conversation_list_screen
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ListView android:id="@android:id/list"
style="?android:attr/listViewWhiteStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/banner_sms_promo"
android:drawSelectorOnTop="false"
android:scrollbarStyle="insideOverlay"
android:background="@android:color/white"
android:cacheColorHint="@android:color/white"
android:fadingEdgeLength="16dip" />
<TextView android:id="@+id/empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/loading_conversations"
android:textAppearance="?android:attr/textAppearanceMedium" />
<include layout="@layout/banner_sms_promo" />
</RelativeLayout>
layout.banner_sms_promp,貌似是给运营商做的需求,原生代码不确定有没有,当不是默认短信时,显示此View,点击可以设置为默认短信,这么一个功能,显示在ListView的上面。按照常规的写法,在RelativeLayout中,banner_sms_promo应该写在ListView之前的。
此empty TextView当ListView没有数据时显示的,还有一个功能是当加载列表比较慢的情况下,显示正在加载,但这个会话多的时候会覆盖在内容列表上面,后来的项目有提这个bug,后来改了就不提示正在加载了。
/**
* This activity provides a list view of existing conversations.
*/
public class ConversationList extends ListActivity implements DraftCache.OnDraftChangedListener {<span style="font-family: Arial, Helvetica, sans-serif;">...</span>
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.conversation_list_screen);
mSmsPromoBannerView = findViewById(R.id.banner_sms_promo);
mQueryHandler = new ThreadListQueryHandler(getContentResolver());//查询使用的Handler,经常使用
ListView listView = getListView();
listView.setOnCreateContextMenuListener(mConvListOnCreateContextMenuListener);//长按事件,弹出来的菜单,在ActionBar位置显示,比之前的,长按弹出菜单好用
listView.setOnKeyListener(mThreadListKeyListener);
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
listView.setMultiChoiceModeListener(new ModeCallback());
// Tell the list view which view to display when the list is empty
listView.setEmptyView(findViewById(R.id.empty));
initListAdapter();
setupActionBar();
setTitle(R.string.app_label);
mHandler = new Handler();
mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
boolean checkedMessageLimits = mPrefs.getBoolean(CHECKED_MESSAGE_LIMITS, false);
if (DEBUG) Log.v(TAG, "checkedMessageLimits: " + checkedMessageLimits);
if (!checkedMessageLimits) {
runOneTimeStorageLimitCheckForLegacyMessages();
}
if (savedInstanceState != null) {
mSavedFirstVisiblePosition = savedInstanceState.getInt(LAST_LIST_POS,
AdapterView.INVALID_POSITION);
mSavedFirstItemOffset = savedInstanceState.getInt(LAST_LIST_OFFSET, 0);
} else {
mSavedFirstVisiblePosition = AdapterView.INVALID_POSITION;
mSavedFirstItemOffset = 0;
}
}
<pre name="code" class="java">private final class ThreadListQueryHandler extends ConversationQueryHandler {//重点掌握
private final class ThreadListQueryHandler extends ConversationQueryHandler {
public ThreadListQueryHandler(ContentResolver contentResolver) {
super(contentResolver);
}
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
switch (token) {
case THREAD_LIST_QUERY_TOKEN:
mListAdapter.changeCursor(cursor);
case UNREAD_THREADS_QUERY_TOKEN:
...break;
case HAVE_LOCKED_MESSAGES_TOKEN:
...break;
@Override
protected void onDeleteComplete(int token, Object cookie, int result) {
super.onDeleteComplete(token, cookie, result);
switch (token) {
case DELETE_CONVERSATION_TOKEN:
...break;
case DELETE_OBSOLETE_THREADS_TOKEN:
… break;
}
======================================================================
public static class ConversationQueryHandler extends AsyncQueryHandler {…}
===========================================================================
package android.content;
/**
* A helper class to help make handling asynchronous {@link ContentResolver}
* queries easier.
*/
public abstract class AsyncQueryHandler extends Handler {
private static final int EVENT_ARG_QUERY = 1;
private static final int EVENT_ARG_INSERT = 2;
private static final int EVENT_ARG_UPDATE = 3;
private static final int EVENT_ARG_DELETE = 4;
//使用弱引用避免对象一直被引用而无法释放,导致内存泄露,WeakReference GC可以自动回收
/* package */ final WeakReference<ContentResolver> mResolver;
private static Looper sLooper = null;
private Handler mWorkerThreadHandler;
//查询等操作需要的参数
protected static final class WorkerArgs {
public Uri uri;
public Handler handler;
public String[] projection;
public String selection;
public String[] selectionArgs;
public String orderBy;
public Object result;
public Object cookie;
public ContentValues values;
}
//使用Handler去查询等耗时的操作
protected class WorkerHandler extends Handler {
public WorkerHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
//mResolver.get()使用WeakReference
final ContentResolver resolver = mResolver.get();
//如果mResolver.get()为空,表示对象已经回收了,此短信例子中对应的ConversationList Activity
if (resolver == null) return;
WorkerArgs args = (WorkerArgs) msg.obj;
int token = msg.what;
int event = msg.arg1;
switch (event) {
case EVENT_ARG_QUERY:
Cursor cursor;
try {
cursor = resolver.query(args.uri, args.projection,
args.selection, args.selectionArgs,
args.orderBy);
// Calling getCount() causes the cursor window to be filled,
// which will make the first access on the main thread a lot faster.
if (cursor != null) {
cursor.getCount();
}
} catch (Exception e) {
Log.w(TAG, "Exception thrown during handling EVENT_ARG_QUERY", e);
cursor = null;
}
args.result = cursor;
break;
case EVENT_ARG_INSERT:
args.result = resolver.insert(args.uri, args.values);
break;
case EVENT_ARG_UPDATE:
args.result = resolver.update(args.uri, args.values, args.selection,
args.selectionArgs);
break;
case EVENT_ARG_DELETE:
args.result = resolver.delete(args.uri, args.selection, args.selectionArgs);
break;
}
// passing the original token value back to the caller
// on top of the event values in arg1.
Message reply = args.handler.obtainMessage(token);
reply.obj = args;
reply.arg1 = msg.arg1;
if (localLOGV) {
Log.d(TAG, "WorkerHandler.handleMsg: msg.arg1=" + msg.arg1
+ ", reply.what=" + reply.what);
}
reply.sendToTarget();//返回给自己AsyncQueryHandler处理,因为StartQuery等方法WorkerArgs.handler传的是this
}
}
//构造函数
public AsyncQueryHandler(ContentResolver cr) {
super();
mResolver = new WeakReference<ContentResolver>(cr);
synchronized (AsyncQueryHandler.class) {
if (sLooper == null) {
HandlerThread thread = new HandlerThread("AsyncQueryWorker");
thread.start();
//使用HandlerThread的Looper而不使用主线程的Looper就不会阻塞主线程
sLooper = thread.getLooper();
}
}
mWorkerThreadHandler = createHandler(sLooper);
}
//暴露给外部调用
public void startQuery(int token, Object cookie, Uri uri,
String[] projection, String selection, String[] selectionArgs,
String orderBy) {
// Use the token as what so cancelOperations works properly
Message msg = mWorkerThreadHandler.obtainMessage(token);
msg.arg1 = EVENT_ARG_QUERY;
WorkerArgs args = new WorkerArgs();
args.handler = this;//最后WorkerHandler处理的结果还是给自己的HandleMessage()处理
args.uri = uri;
args.projection = projection;
args.selection = selection;
args.selectionArgs = selectionArgs;
args.orderBy = orderBy;
args.cookie = cookie;
msg.obj = args;
//最终给WorkerHandler,用ContentResolver查询等操作。
//这里为什么不直接查询完再调用onQueryComplete()方法,反而又加多一个WorkerHandler那么麻烦呢?如果这样,必然会阻塞主线程,因为外部调用此StartQuery的handler(即该AsyncQueryHandler的子类实例)是在主线程中执行的,而WorkerHandler使用的HanderThread就不会阻塞主线程。
mWorkerThreadHandler.sendMessage(msg);
}
public final void cancelOperation(int token) {
mWorkerThreadHandler.removeMessages(token);
}
public final void startInsert(int token, Object cookie, Uri uri,
ContentValues initialValues) {
// Use the token as what so cancelOperations works properly
Message msg = mWorkerThreadHandler.obtainMessage(token);
msg.arg1 = EVENT_ARG_INSERT;
WorkerArgs args = new WorkerArgs();
...
mWorkerThreadHandler.sendMessage(msg);
}
public final void startUpdate(int token, Object cookie, Uri uri,
ContentValues values, String selection, String[] selectionArgs) {...}
public final void startDelete(int token, Object cookie, Uri uri,
String selection, String[] selectionArgs) {...}
protected Handler createHandler(Looper looper) {
return new WorkerHandler(looper);
}
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {// Empty}
protected void onInsertComplete(int token, Object cookie, Uri uri) {// Empty}
protected void onUpdateComplete(int token, Object cookie, int result) {// Empty}
protected void onDeleteComplete(int token, Object cookie, int result) {// Empty}
@Override
public void handleMessage(Message msg) {
WorkerArgs args = (WorkerArgs) msg.obj;
if (localLOGV) {
Log.d(TAG, "AsyncQueryHandler.handleMessage: msg.what=" + msg.what
+ ", msg.arg1=" + msg.arg1);
}
int token = msg.what;
int event = msg.arg1;
// pass token back to caller on each callback.
switch (event) {
case EVENT_ARG_QUERY:
//回调给子类处理,父类AsynQueryHandler的onQueryComplete是空方法,把结果传回给子类处理想要的逻辑
onQueryComplete(token, args.cookie, (Cursor) args.result);
break;
case EVENT_ARG_INSERT:
onInsertComplete(token, args.cookie, (Uri) args.result);
break;
case EVENT_ARG_UPDATE:
onUpdateComplete(token, args.cookie, (Integer) args.result);
break;
case EVENT_ARG_DELETE:
onDeleteComplete(token, args.cookie, (Integer) args.result);
break;
}
}
}
======================================================================
package android.os;
/**
* Handy class for starting a new thread that has a looper. The looper can then be
* used to create handler classes. Note that start() must still be called.
*/
//HandlerThread 是一个Thread不是Handler
public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;//使用自己的Looper而不是主线程的
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
/**
* Constructs a HandlerThread.
* @param name
* @param priority The priority to run the thread at. The value supplied must be from
* {@link android.os.Process} and not from java.lang.Thread.
*/
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}
/**
* Call back method that can be explicitly overridden if needed to execute some
* setup before Looper loops.
*/
protected void onLooperPrepared() {
}
public void run() {
mTid = Process.myTid();
Looper.prepare();//不能漏,Looper.loop ()前先调用一次,prepare有new Looper。
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
/**
* This method returns the Looper associated with this thread. If this thread not been started
* or for any reason is isAlive() returns false, this method will return null. If this thread
* has been started, this method will block until the looper has been initialized.
* @return The looper.
*/
public Looper getLooper() {
if (!isAlive()) {
return null;
}
// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
/**
* Ask the currently running looper to quit. If the thread has not
* been started or has finished (that is if {@link #getLooper} returns
* null), then false is returned. Otherwise the looper is asked to
* quit and true is returned.
*/
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}
/**
* Returns the identifier of this thread. See Process.myTid().
*/
public int getThreadId() {
return mTid;
}
}
android.os.Looper 略
}//ConversationList class end