记不清是android 4.0之后还是4.1之后,浏览信息时,彩信幻灯片不再随着信息内容一并显示,而是在信息内容显示后,开启后台线程,异步加载彩信幻灯片,加载完毕之后再显示附件。为什么要这么设计那?主要是为了解决彩信显示缓慢的问题。在原先的设计中,彩信想要显示,首先要做准备工作,准备工作包括从数据库中加载信息的内容,收件人,发送时间,主题,类型,状态报告等基础内容,其中还包括了一项费时的操作,那就是加载彩信幻灯片附件。只有上述工作全部完成之后彩信才会显示在界面上,用户才可以进行浏览。这种设计非常容易卡机,有时还会引起ANR(应用程序无相应),尤其当我们的运营商要求彩信幻灯片支持20页(默认10页),这个问题就更加严重,长时间不显示信息,严重影响性能。其实从功能设计上来说,用户首先希望看到的是彩信收件人,彩信主题,发送时间,发送状态报告等基础信息,然后才会选择查看幻灯片等多媒体附件。我们完全可以将幻灯片的加载滞后。androd目前给出了这样的一个设计。
首先根据当前回话,查询数据库,加载信息条目。每一个信息条目,每一条信息条目,用视图MessageListItem来显示,信息内容数据包装在MessageItem中。
MessageListAdapter.java
public View newView(Context context, Cursor cursor, ViewGroup parent) { int boxType = getItemViewType(cursor); View view = mInflater.inflate((boxType == INCOMING_ITEM_TYPE_SMS || boxType == INCOMING_ITEM_TYPE_MMS) ? R.layout.message_list_item_recv : R.layout.message_list_item_send, parent, false); if (boxType == INCOMING_ITEM_TYPE_MMS || boxType == OUTGOING_ITEM_TYPE_MMS) { // We've got an mms item, pre-inflate the mms portion of the view view.findViewById(R.id.mms_layout_view_stub).setVisibility(View.VISIBLE); } return view; }
这个view就是MessageLIstItem。然后MessagelistAdapter会调用bindView方法,完成视图的加载,在加载中,主要是调用MessageItem的bind方法。我们现在主要关心的是MessageItem对彩信内容数据的加载,以及着重注意异步加载幻灯片附件的逻辑处理。
@Override public void bindView(View view, Context context, Cursor cursor) { if (view instanceof MessageListItem) { String type = cursor.getString(mColumnsMap.mColumnMsgType); long msgId = cursor.getLong(mColumnsMap.mColumnMsgId); MessageItem msgItem = getCachedMessageItem(type, msgId, cursor); if (msgItem != null) { MessageListItem mli = (MessageListItem) view; int position = cursor.getPosition(); mli.bind(msgItem, position == cursor.getCount() - 1, position); mli.setMsgListItemHandler(mMsgListItemHandler); } } }
根据信息的type以及msgId,通过getCachedMessageItem方法,得到一个MessageItem。
public MessageItem getCachedMessageItem(String type, long msgId, Cursor c) { MessageItem item = mMessageItemCache.get(getKey(type, msgId)); if (item == null && c != null && isCursorValid(c)) { try { item = new MessageItem(mContext, type, c, mColumnsMap, mHighlight, mFullTimestamp, mSentTimestamp); mMessageItemCache.put(getKey(item.mType, item.mMsgId), item); } catch (MmsException e) { Log.e(TAG, "getCachedMessageItem: ", e); } } return item; }在MessageItem中会对cursor中的数据进行加载。
MessgeItem.java
MessageItem(Context context, String type, final Cursor cursor, final ColumnsMap columnsMap, Pattern highlight, boolean fullTimestamp, boolean sentTimestamp) throws MmsException { mContext = context; mMsgId = cursor.getLong(columnsMap.mColumnMsgId); mHighlight = highlight; mType = type; mCursor = cursor; mColumnsMap = columnsMap; mFullTimestamp = fullTimestamp; mSentTimestamp = sentTimestamp; if ("sms".equals(type)) { mReadReport = false; // No read reports in sms long status = cursor.getLong(columnsMap.mColumnSmsStatus); if (status == Sms.STATUS_NONE) { // No delivery report requested mDeliveryStatus = DeliveryStatus.NONE; } else if (status >= Sms.STATUS_FAILED) { // Failure mDeliveryStatus = DeliveryStatus.FAILED; } else if (status >= Sms.STATUS_PENDING) { // Pending mDeliveryStatus = DeliveryStatus.PENDING; } else { // Success mDeliveryStatus = DeliveryStatus.RECEIVED; } mMessageUri = ContentUris.withAppendedId(Sms.CONTENT_URI, mMsgId); // Set contact and message body mBoxId = cursor.getInt(columnsMap.mColumnSmsType); mAddress = cursor.getString(columnsMap.mColumnSmsAddress); if (Sms.isOutgoingFolder(mBoxId)) { String meString = context.getString( R.string.messagelist_sender_self); mContact = meString; } else { // For incoming messages, the ADDRESS field contains the sender. mContact = Contact.get(mAddress, false).getName(); } mBody = cursor.getString(columnsMap.mColumnSmsBody); // Unless the message is currently in the progress of being sent, it gets a time stamp. if (!isOutgoingMessage()) { // Set "received" or "sent" time stamp long date = cursor.getLong(columnsMap.mColumnSmsDate); if (mSentTimestamp && (mBoxId == Sms.MESSAGE_TYPE_INBOX)) { date = cursor.getLong(columnsMap.mColumnSmsDateSent); } mTimestamp = MessageUtils.formatTimeStampString(context, date, mFullTimestamp); } mLocked = cursor.getInt(columnsMap.mColumnSmsLocked) != 0; mErrorCode = cursor.getInt(columnsMap.mColumnSmsErrorCode); } else if ("mms".equals(type)) { mMessageUri = ContentUris.withAppendedId(Mms.CONTENT_URI, mMsgId); mBoxId = cursor.getInt(columnsMap.mColumnMmsMessageBox); mMessageType = cursor.getInt(columnsMap.mColumnMmsMessageType); mErrorType = cursor.getInt(columnsMap.mColumnMmsErrorType); String subject = cursor.getString(columnsMap.mColumnMmsSubject); if (!TextUtils.isEmpty(subject)) { EncodedStringValue v = new EncodedStringValue( cursor.getInt(columnsMap.mColumnMmsSubjectCharset), PduPersister.getBytes(subject)); mSubject = v.getString(); } mLocked = cursor.getInt(columnsMap.mColumnMmsLocked) != 0; mSlideshow = null; mAttachmentType = ATTACHMENT_TYPE_NOT_LOADED; mDeliveryStatus = DeliveryStatus.NONE; mReadReport = false; mBody = null; mMessageSize = 0; mTextContentType = null; mTimestamp = null; mMmsStatus = cursor.getInt(columnsMap.mColumnMmsStatus); // Start an async load of the pdu. If the pdu is already loaded, the callback // will get called immediately boolean loadSlideshow = mMessageType != PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND; mItemLoadedFuture = MmsApp.getApplication().getPduLoaderManager()//异步加载幻灯片的逻辑 .getPdu(mMessageUri, loadSlideshow, new PduLoadedMessageItemCallback()); } else { throw new MmsException("Unknown type of the message: " + type); } }
在构造中,会根据信息的类别,加载短信和彩信的数据。数据包括信息的MessageUri,信息的 messageType,信息的主题,信息的boxId,信息的状态等。这些属于基础信息,这些信息是要首先展示给用户的。我们的目的是最后几行加黑的代码,他们就是我们彩信幻灯片后台加载的逻辑代码。
MmsApp.java
public PduLoaderManager getPduLoaderManager() { return mPduLoaderManager; }
PduLoadedManager.java
public class PduLoaderManager extends BackgroundLoaderManager {.....}
public PduLoaderManager(final Context context) { super(context); mSlideshowCache = new SimpleCache<Uri, SlideshowModel>(8, 16, 0.75f, true); mPduCache = PduCache.getInstance(); mPduPersister = PduPersister.getPduPersister(context); mContext = context; }方法继承子BackgroundLoadManager。
BackgroundLoadManager.java
BackgroundLoaderManager(Context context) { mPendingTaskUris = new HashSet<Uri>();//等待加载幻灯片任务集合,内容为彩信uri的集合 mCallbacks = new HashMap<Uri, Set<ItemLoadedCallback>>();//彩信uri为key,value是callback的集合,callback是在幻灯片附件加载任务完成后回调的方法对象。 final LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(); final int poolSize = MAX_THREADS; mExecutor = new ThreadPoolExecutor(//开启一个线程池,在新线程中处理幻灯片的加载等工作。 poolSize, poolSize, 5, TimeUnit.SECONDS, queue, new BackgroundLoaderThreadFactory(getTag())); mCallbackHandler = new Handler(); }在上面的MessageItem类中,会调用PduLoadedManager类的getPdu方法。
public ItemLoadedFuture getPdu(Uri uri, boolean requestSlideshow, final ItemLoadedCallback<PduLoaded> callback) { if (uri == null) { throw new NullPointerException(); } PduCacheEntry cacheEntry = null; synchronized(mPduCache) { if (!mPduCache.isUpdating(uri)) { cacheEntry = mPduCache.get(uri);//第一次会走到这里,cacheEntry为null。 } } final SlideshowModel slideshow = (requestSlideshow && !DEBUG_DISABLE_CACHE) ?//requestSlideshow为true,但mSlideshowCache.get(uri)为null。 mSlideshowCache.get(uri) : null; final boolean slideshowExists = (!requestSlideshow || slideshow != null);//判断slideshowModel是否存在,slideshowModel是幻灯片的实体。 final boolean pduExists = (cacheEntry != null && cacheEntry.getPdu() != null);//pdu是否存在。pdu是幻灯片信息的封装,基类是GenericPdu。 final boolean taskExists = mPendingTaskUris.contains(uri);//判断是否存在线程 final boolean newTaskRequired = (!pduExists || !slideshowExists) && !taskExists;//是否需要新开启线程 final boolean callbackRequired = (callback != null);//是否存在回调对象 if (pduExists && slideshowExists) {//如果pdu存在,并且slideshowModel也存在,那么不用开启线程加载了,直接返回。 if (callbackRequired) { PduLoaded pduLoaded = new PduLoaded(cacheEntry.getPdu(), slideshow); callback.onItemLoaded(pduLoaded, null); } return new NullItemLoadedFuture(); } if (callbackRequired) {//如果存在回调对象,那么将uri为key,value为callbacks的集合,加入到mCallbacks中。 addCallback(uri, callback); } if (newTaskRequired) {//是否需要开启新的线程,如果需要,那么构建一个PduTask,并且放到线程池中,执行它。 mPendingTaskUris.add(uri); Runnable task = new PduTask(uri, requestSlideshow); mExecutor.execute(task); } return new ItemLoadedFuture() { public void cancel() { cancelCallback(callback); } public boolean isDone() { return false; } }; }
PduTask.java
public class PduTask implements Runnable { private final Uri mUri; private final boolean mRequestSlideshow; public PduTask(Uri uri, boolean requestSlideshow) { if (uri == null) { throw new NullPointerException(); } mUri = uri; mRequestSlideshow = requestSlideshow; } /** {@inheritDoc} */ public void run() { if (DEBUG_DISABLE_PDUS) { return; } if (DEBUG_LONG_WAIT) { try { Thread.sleep(10000); } catch (InterruptedException e) { } } GenericPdu pdu = null;//所有彩信pdu的基类,子类中有我们熟悉的SendReq(发送的彩信所包装的pdu),RetrieveConf等 SlideshowModel slideshow = null; Throwable exception = null; try { pdu = mPduPersister.load(mUri);//特别关键的一个函数,在介绍完流程后,会介绍一下这个类的,处理的数据很多,但框架很清晰 if (pdu != null && mRequestSlideshow) { slideshow = SlideshowModel.createFromPduBody(mContext,//从pdu中解析出SlideshowModel。从SlideshowModel能够得到pdu,那么反过来也一样能够得到。 ((MultimediaMessagePdu)pdu).getBody()); } } catch (final MmsException e) { Log.e(TAG, "MmsException loading uri: " + mUri, e); exception = e; } final GenericPdu resultPdu = pdu; final SlideshowModel resultSlideshow = slideshow; final Throwable resultException = exception; mCallbackHandler.post(new Runnable() { public void run() { final Set<ItemLoadedCallback> callbacks = mCallbacks.get(mUri);//我们在上面将回调对象的集合加入到了mCallbacks中,现在我们根据uri将回调对象集合取出来 if (callbacks != null) { // Make a copy so that the callback can unregister itself for (final ItemLoadedCallback<PduLoaded> callback : asList(callbacks)) { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Invoking pdu callback " + callback); } PduLoaded pduLoaded = new PduLoaded(resultPdu, resultSlideshow);//遍历集合,然后执行回调函数的onItemLoaded。 callback.onItemLoaded(pduLoaded, resultException); } } // Add the slideshow to the soft cache if the load succeeded if (resultSlideshow != null) { mSlideshowCache.put(mUri, resultSlideshow);//将得到的slideshowModel作为value,uri作为key加入到mSlideshowCache中,下次就不需要开启线程加载就可以得到SlideshowModel } mCallbacks.remove(mUri);//回调函数执行完毕,那么从mCallbacks中移除 mPendingTaskUris.remove(mUri);//同样移除操作 if (Log.isLoggable(LogTag.PDU_CACHE, Log.DEBUG)) { Log.d(TAG, "Pdu task for " + mUri + "exiting; " + mPendingTaskUris.size() + " remain"); } } }); } }
这个执行完毕,那么我们回到MessageItem看看回调函数。
public class PduLoadedMessageItemCallback implements ItemLoadedCallback { public void onItemLoaded(Object result, Throwable exception) { if (exception != null) { Log.e(TAG, "PduLoadedMessageItemCallback PDU couldn't be loaded: ", exception); return; } PduLoaderManager.PduLoaded pduLoaded = (PduLoaderManager.PduLoaded)result;//回调的结果 long timestamp = 0L; if (PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND == mMessageType) {//根据当前信息的类型,将pdu将强转成相应的pdu封装类。这里我有一点疑惑(MessageItem中的mMessageType是会改变的,而我们的加载是异步加载,也就说加载前mMessageType == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND,如果我们加载完,mMessageType会不会变成另外一个值,比如PduHeaders.MESSAGE_TYPE_SEND_REQ等,那么我们强转会不会产生异常?就目前所知,在接收彩信时,MessageItem类型会发生改变,这种情况应该是会出现的) mDeliveryStatus = DeliveryStatus.NONE; NotificationInd notifInd = (NotificationInd)pduLoaded.mPdu;//强转类型为NotificationInd,接收彩信时,为下载彩信内容 interpretFrom(notifInd.getFrom(), mMessageUri); // Borrow the mBody to hold the URL of the message. mBody = new String(notifInd.getContentLocation()); mMessageSize = (int) notifInd.getMessageSize(); timestamp = notifInd.getExpiry() * 1000L; } else { if (mCursor.isClosed()) { return; } MultimediaMessagePdu msg = (MultimediaMessagePdu)pduLoaded.mPdu;//MultimediaMessagePdu是发送和接收彩信的pdu类型的基类型。 mSlideshow = pduLoaded.mSlideshow; mAttachmentType = MessageUtils.getAttachmentType(mSlideshow); if (mMessageType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF) {//接收彩信的类型,再往下就是继续初始化一些彩信数据 if (msg == null) { interpretFrom(null, mMessageUri); } else { RetrieveConf retrieveConf = (RetrieveConf) msg; interpretFrom(retrieveConf.getFrom(), mMessageUri); timestamp = retrieveConf.getDate() * 1000L; } } else { // Use constant string for outgoing messages mContact = mAddress = mContext.getString(R.string.messagelist_sender_self); timestamp = msg == null ? 0 : ((SendReq) msg).getDate() * 1000L; } SlideModel slide = mSlideshow == null ? null : mSlideshow.get(0); if ((slide != null) && slide.hasText()) { TextModel tm = slide.getText(); mBody = tm.getText(); mTextContentType = tm.getContentType(); } mMessageSize = mSlideshow == null ? 0 : mSlideshow.getTotalMessageSize(); String report = mCursor.getString(mColumnsMap.mColumnMmsDeliveryReport); if ((report == null) || !mAddress.equals(mContext.getString( R.string.messagelist_sender_self))) { mDeliveryStatus = DeliveryStatus.NONE; } else { int reportInt; try { reportInt = Integer.parseInt(report); if (reportInt == PduHeaders.VALUE_YES) { mDeliveryStatus = DeliveryStatus.RECEIVED; } else { mDeliveryStatus = DeliveryStatus.NONE; } } catch (NumberFormatException nfe) { Log.e(TAG, "Value for delivery report was invalid."); mDeliveryStatus = DeliveryStatus.NONE; } } report = mCursor.getString(mColumnsMap.mColumnMmsReadReport); if ((report == null) || !mAddress.equals(mContext.getString( R.string.messagelist_sender_self))) { mReadReport = false; } else { int reportInt; try { reportInt = Integer.parseInt(report); mReadReport = (reportInt == PduHeaders.VALUE_YES); } catch (NumberFormatException nfe) { Log.e(TAG, "Value for read report was invalid."); mReadReport = false; } } } if (!isOutgoingMessage()) { if (PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND == mMessageType) { mTimestamp = mContext.getString(R.string.expire_on, MessageUtils.formatTimeStampString(mContext, timestamp, mFullTimestamp)); } else { mTimestamp = MessageUtils.formatTimeStampString(mContext, timestamp, mFullTimestamp); } } if (mPduLoadedCallback != null) { mPduLoadedCallback.onPduLoaded(MessageItem.this); } } }
异步加载完幻灯片,那么就可以在彩信中显示了。我们主要看幻灯片附件的异步加载,剩下的逻辑以后在分析。
我们最后来看一下
pdu = mPduPersister.load(mUri);PduPersister.java
这个类在framework层
public GenericPdu load(Uri uri) throws MmsException { GenericPdu pdu = null; PduCacheEntry cacheEntry = null; int msgBox = 0; long threadId = -1; try { synchronized(PDU_CACHE_INSTANCE) { if (PDU_CACHE_INSTANCE.isUpdating(uri)) { if (LOCAL_LOGV) { Log.v(TAG, "load: " + uri + " blocked by isUpdating()"); } try { PDU_CACHE_INSTANCE.wait(); } catch (InterruptedException e) { Log.e(TAG, "load: ", e); } cacheEntry = PDU_CACHE_INSTANCE.get(uri); if (cacheEntry != null) { return cacheEntry.getPdu(); } } // Tell the cache to indicate to other callers that this item // is currently being updated. PDU_CACHE_INSTANCE.setUpdating(uri, true); } Cursor c = SqliteWrapper.query(mContext, mContentResolver, uri,//加载彩信数据库中的内容 PDU_PROJECTION, null, null, null); PduHeaders headers = new PduHeaders(); Set<Entry<Integer, Integer>> set; long msgId = ContentUris.parseId(uri); try { if ((c == null) || (c.getCount() != 1) || !c.moveToFirst()) { throw new MmsException("Bad uri: " + uri); } msgBox = c.getInt(PDU_COLUMN_MESSAGE_BOX); threadId = c.getLong(PDU_COLUMN_THREAD_ID); set = ENCODED_STRING_COLUMN_INDEX_MAP.entrySet();//解析数据库中的内容到PduHeaders中。 for (Entry<Integer, Integer> e : set) { setEncodedStringValueToHeaders( c, e.getValue(), headers, e.getKey()); } set = TEXT_STRING_COLUMN_INDEX_MAP.entrySet(); for (Entry<Integer, Integer> e : set) { setTextStringToHeaders( c, e.getValue(), headers, e.getKey()); } set = OCTET_COLUMN_INDEX_MAP.entrySet(); for (Entry<Integer, Integer> e : set) { setOctetToHeaders( c, e.getValue(), headers, e.getKey()); } set = LONG_COLUMN_INDEX_MAP.entrySet(); for (Entry<Integer, Integer> e : set) { setLongToHeaders( c, e.getValue(), headers, e.getKey()); } } finally { if (c != null) { c.close(); } } // Check whether 'msgId' has been assigned a valid value. if (msgId == -1L) { throw new MmsException("Error! ID of the message: -1."); } // Load address information of the MM. loadAddress(msgId, headers); int msgType = headers.getOctet(PduHeaders.MESSAGE_TYPE); PduBody body = new PduBody(); // For PDU which type is M_retrieve.conf or Send.req, we should // load multiparts and put them into the body of the PDU. if ((msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF) || (msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)) {//构建pduBody PduPart[] parts = loadParts(msgId);//这部分相信大家可以看懂,同样是加载数据库 if (parts != null) { int partsNum = parts.length; for (int i = 0; i < partsNum; i++) { body.addPart(parts[i]); } } } switch (msgType) {//根据msgType,构建GeniricPdu的实例子类型 case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND: pdu = new NotificationInd(headers); break; case PduHeaders.MESSAGE_TYPE_DELIVERY_IND: pdu = new DeliveryInd(headers); break; case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND: pdu = new ReadOrigInd(headers); break; case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF: pdu = new RetrieveConf(headers, body); break; case PduHeaders.MESSAGE_TYPE_SEND_REQ: pdu = new SendReq(headers, body); break; case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND: pdu = new AcknowledgeInd(headers); break; case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND: pdu = new NotifyRespInd(headers); break; case PduHeaders.MESSAGE_TYPE_READ_REC_IND: pdu = new ReadRecInd(headers); break; case PduHeaders.MESSAGE_TYPE_SEND_CONF: case PduHeaders.MESSAGE_TYPE_FORWARD_REQ: case PduHeaders.MESSAGE_TYPE_FORWARD_CONF: case PduHeaders.MESSAGE_TYPE_MBOX_STORE_REQ: case PduHeaders.MESSAGE_TYPE_MBOX_STORE_CONF: case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_REQ: case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_CONF: case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_REQ: case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_CONF: case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_REQ: case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_CONF: case PduHeaders.MESSAGE_TYPE_MBOX_DESCR: case PduHeaders.MESSAGE_TYPE_DELETE_REQ: case PduHeaders.MESSAGE_TYPE_DELETE_CONF: case PduHeaders.MESSAGE_TYPE_CANCEL_REQ: case PduHeaders.MESSAGE_TYPE_CANCEL_CONF: throw new MmsException( "Unsupported PDU type: " + Integer.toHexString(msgType)); default: throw new MmsException( "Unrecognized PDU type: " + Integer.toHexString(msgType)); } } finally { synchronized(PDU_CACHE_INSTANCE) { if (pdu != null) { assert(PDU_CACHE_INSTANCE.get(uri) == null); // Update the cache entry with the real info cacheEntry = new PduCacheEntry(pdu, msgBox, threadId); PDU_CACHE_INSTANCE.put(uri, cacheEntry); } PDU_CACHE_INSTANCE.setUpdating(uri, false); PDU_CACHE_INSTANCE.notifyAll(); // tell anybody waiting on this entry to go ahead } } return pdu; }
就到这里吧,很晚了。有什么问题欢迎和大家探讨。
作者:hailushijie 发表于2013-3-22 0:23:20 原文链接
阅读:88 评论:0 查看评论