編輯:關於Android編程
在Android中任何耗時的操作都不能放在UI主線程中,所以耗時的操作都需要使用異步實現。同樣的,在ContentProvider中也可能存在耗時操作,這時也該使用異步操作,而3.0之後最推薦的異步操作就是Loader。它可以方便我們在Activity和Fragment中異步加載數據,而不是用線程或AsyncTask,他的優點如下:
提供異步加載數據機制; 對數據源變化進行監聽,實時更新數據; 在Activity配置發生變化(如橫豎屏切換)時不用重復加載數據; 適用於任何Activity和Fragment;PS:由於在我們現在的多個項目中都大量的使用了Loader來處理數據加載(而且由於粗心跳過幾個坑,譬如Loader ID重復導致數據邏輯異常、多線程中restartLoader導致Loader拋出異常(最後保證都在UI線程中執行即可)等),所以接下來我們進行下使用及源碼淺析。
【工匠若水 http://blog.csdn.net/yanbober 轉載請注明出處。點我開始Android技術交流】
PPPS:前方高能,文章巨長,請做好心理准備(您可以選擇通過左上角目錄點擊索引到感興趣的章節直接查看,或者,或者,或者直接高能往下看)。
該基礎實例講解完全來自於官方文檔,詳細可以點擊我查看英文原文。
既然接下來准備要說說他的使用強大之處了,那不妨我們先來一張圖直觀的感性認識下不用Loader(左)與用Loader(右)對我們開發者及代碼復雜度和框架的影響吧,如下:
如下是我們開發中常用的一些Loader相關接口:
在我們開發的一個App裡,使用Loader時常規的步驟包含如下一些操作需求:
一個Activity或Fragment; 一個LoaderManager實例; 一個CursorLoader,從ContentProvider加載數據; 一個LoaderManager.LoaderCallbacks實現,創建新Loader及管理已存在Loader; 一個組織Loader數據的Adapter,如SimpleCursorAdapter;下面我們看下具體流程。
一個Activity或Fragment中LoaderManager管理一個或多個Loader實例,每個Activity或Fragment只有一個LoaderManager,我們可以在Activity的onCreate()或Fragment的onActivityCreated()裡初始化一個Loader。例如:
// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);
可以看見上面的initLoader()方法有三個參數:
第一個參數代表當前Loader的ID; 第二個參數代表提供給Loader構造函數的參數,可選; 第三個參數代表LoaderManager.LoaderCallbacks的回調實現;上面initLoader()方法的調用確保了一個Loader被初始化和激活的狀態,該方法的調運有如下兩種結果:
如果代表該Loader的ID已經存在,則後面創建的Loader將直接復用已經存在的; 如果代表該Loader的ID不存在,initLoader()會觸發LoaderManager.LoaderCallbacks回調的onCreateLoader()方法創建一個Loader;可以看見通過initLoader()方法可以將LoaderManager.LoaderCallbacks實例與Loader進行關聯,且當Loader的狀態變化時就被回調。所以說,如果調用者正處於其開始狀態並且被請求的Loader已經存在,且已產生了數據,那麼系統會立即調用onLoadFinished()(在initLoader()調用期間),所以你必須考慮到這種情況的發生。
當然了,intiLoader()會返回一個創建的Loader,但是你不用獲取它的引用,因為LoadeManager會自動管理該Loader的生命周期,你只用在它回調提供的生命周期方法中做自己數據邏輯的處理即可。
通過上面initLoader()方法介紹我們可以知道initLoader調運後要麼得到一個ID已存在的Loader,要麼創建一個新的Loader;但是有時我們想丟棄舊數據然後重新開始創建一個新Loader,這可怎麼辦呢?別擔心,要丟棄舊數據調用restartLoader()即可。例如,SearchView.OnQueryTextListener的實現重啟了Loader,當用戶查詢發生變化時Loader需要重啟,如下:
public boolean onQueryTextChanged(String newText) {
// Called when the action bar search text has changed. Update
// the search filter, and restart the loader to do a new query
// with this filter.
mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
getLoaderManager().restartLoader(0, null, this);
return true;
}
上面方法的參數啥的和再上面的init方法類似,就不再羅嗦了。
LoaderManager.LoaderCallbacks是LoaderManager的回調交互接口。LoaderManager.LoaderCallbacks包含如下三個方法:
onCreateLoader()當你嘗試使用一個Loader(譬如通過initLoader()方法),它會檢查給定Loader的ID是否存在,如果不存在就觸發LoaderManager.LoaderCallbacks裡的onCreateLoader()方法創建一個新Loader。創建新Loader實例典型的做法就是通過CursorLoader類創建,不過你也可以自定義一個繼承自Loader的子類來實現自己的Loader。
下面的例子中我們通過onCreateLoader()回調創建一個CursorLoader實例,使用CursorLoader的構造方法創建實例時需要一些參數去查詢一個ContentProvider。具體參數如下:
uri
// If non-null, this is the current filter the user has provided.
String mCurFilter;
...
public Loader onCreateLoader(int id, Bundle args) {
// This is called when a new Loader needs to be created. This
// sample only has one Loader, so we don't care about the ID.
// First, pick the base URI to use depending on whether we are
// currently filtering.
Uri baseUri;
if (mCurFilter != null) {
baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
Uri.encode(mCurFilter));
} else {
baseUri = Contacts.CONTENT_URI;
}
// Now create and return a CursorLoader that will take care of
// creating a Cursor for the data being displayed.
String select = (( + Contacts.DISPLAY_NAME + NOTNULL) AND (
+ Contacts.HAS_PHONE_NUMBER + =1) AND (
+ Contacts.DISPLAY_NAME + != '' ));
return new CursorLoader(getActivity(), baseUri,
CONTACTS_SUMMARY_PROJECTION, select, null,
Contacts.DISPLAY_NAME + COLLATE LOCALIZED ASC);
}
當創建好的Loader完成數據加載時回調此方法,我們要確保該方法在Loader釋放現有維持的數據之前被調用。在這裡我們應該移除所有對舊數據的使用(因為舊數據不久就會被釋放),但是不用釋放舊數據,因為Loader會幫我們完成舊數據的釋放。
Loader一旦知道App不再使用舊數據就會釋放掉。例如,如果數據來自CursorLoader裡的一個Cursor,我們不應該自己在代碼中調用close()方法;如果一個Cursor正在被放置到一個CursorAdapter時我們應當使用swapCursor()進行新數據交換,這樣正在被放置的舊的Cursor就不會被關掉,也就不會導致Adapter的加載異常。
// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter;
...
public void onLoadFinished(Loader loader, Cursor data) {
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
mAdapter.swapCursor(data);
}
當實例化好的Loader被重啟時該方法被回調,這裡會讓Loader的數據置於無效狀態。這個回調方法其實就是為了告訴我們啥時候數據要被釋放掉,所以我們應該在這個時候移除對它的引用。如下移除實例:
// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter;
...
public void onLoaderReset(Loader loader) {
// This is called when the last Cursor provided to onLoadFinished()
// above is about to be closed. We need to make sure we are no
// longer using it.
mAdapter.swapCursor(null);
}
下面這個實例是一個Fragment,模擬的是用ListView顯示通訊錄的實時匹配查詢結果,使用CursorLoader管理通訊錄Provider查詢。如下源碼,比較簡單,注釋也很豐富了,所以不過多解釋:
public static class CursorLoaderListFragment extends ListFragment
implements OnQueryTextListener, LoaderManager.LoaderCallbacks {
// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter;
// If non-null, this is the current filter the user has provided.
String mCurFilter;
@Override public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Give some text to display if there is no data. In a real
// application this would come from a resource.
setEmptyText(No phone numbers);
// We have a menu item to show in action bar.
setHasOptionsMenu(true);
// Create an empty adapter we will use to display the loaded data.
mAdapter = new SimpleCursorAdapter(getActivity(),
android.R.layout.simple_list_item_2, null,
new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS },
new int[] { android.R.id.text1, android.R.id.text2 }, 0);
setListAdapter(mAdapter);
// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);
}
@Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
// Place an action bar item for searching.
MenuItem item = menu.add(Search);
item.setIcon(android.R.drawable.ic_menu_search);
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
SearchView sv = new SearchView(getActivity());
sv.setOnQueryTextListener(this);
item.setActionView(sv);
}
public boolean onQueryTextChange(String newText) {
// Called when the action bar search text has changed. Update
// the search filter, and restart the loader to do a new query
// with this filter.
mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
getLoaderManager().restartLoader(0, null, this);
return true;
}
@Override public boolean onQueryTextSubmit(String query) {
// Don't care about this.
return true;
}
@Override public void onListItemClick(ListView l, View v, int position, long id) {
// Insert desired behavior here.
Log.i(FragmentComplexList, Item clicked: + id);
}
// These are the Contacts rows that we will retrieve.
static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
Contacts._ID,
Contacts.DISPLAY_NAME,
Contacts.CONTACT_STATUS,
Contacts.CONTACT_PRESENCE,
Contacts.PHOTO_ID,
Contacts.LOOKUP_KEY,
};
public Loader onCreateLoader(int id, Bundle args) {
// This is called when a new Loader needs to be created. This
// sample only has one Loader, so we don't care about the ID.
// First, pick the base URI to use depending on whether we are
// currently filtering.
Uri baseUri;
if (mCurFilter != null) {
baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
Uri.encode(mCurFilter));
} else {
baseUri = Contacts.CONTENT_URI;
}
// Now create and return a CursorLoader that will take care of
// creating a Cursor for the data being displayed.
String select = (( + Contacts.DISPLAY_NAME + NOTNULL) AND (
+ Contacts.HAS_PHONE_NUMBER + =1) AND (
+ Contacts.DISPLAY_NAME + != '' ));
return new CursorLoader(getActivity(), baseUri,
CONTACTS_SUMMARY_PROJECTION, select, null,
Contacts.DISPLAY_NAME + COLLATE LOCALIZED ASC);
}
public void onLoadFinished(Loader loader, Cursor data) {
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
mAdapter.swapCursor(data);
}
public void onLoaderReset(Loader loader) {
// This is called when the last Cursor provided to onLoadFinished()
// above is about to be closed. We need to make sure we are no
// longer using it.
mAdapter.swapCursor(null);
}
}
到此整個Loader基礎使用就介紹完了,關於Loader的高級功能,譬如自定義Loader等內容這裡先不貼代碼說明,因為在這裡一下子說完都會覺得蒙圈,而且接受難度也比較大,所以我們在上面這些基礎鋪墊之後乘熱先來源碼淺析,有了源碼淺析把持住全局結構後再去用Loader的高級用法就會覺得得心應手許多。
【工匠若水 http://blog.csdn.net/yanbober 轉載請注明出處。點我開始Android技術交流】
和上面的基本使用介紹一樣,關於Loader的源碼淺析過程會涉及到Activity、Fragment、LoaderManager、Loader、AsyncLoader、CursorLoader等類。所以我們分析的過程還是和以前一樣,依據使用順序進行分析。
我們在分析之前先來看一個Loader框架概要圖,如下:
通過上面圖和前面的基礎實例你會發現Loader的框架和各個類的職責都很明確。Activity和Fragment管理LoaderManager,LoaderManager管理Loader,Loader得到數據後觸發在LoaderManager中實現的Loader的callback接口,LoaderManager在接收到Loader的callback回傳調運時觸發我們Activity或Fragment中實現的LoaderManager回調callback接口,就這樣就實現了Loader的所有功能,而我們平時寫代碼一般只用關心LoaderManager的callback實現即可;對於自定義Loader可能還需要關心AsyncTaskLoader子類的實現。
首先我們都知道,在使用Loader的第一步就是在Activity或者Fragment中獲取LoaderManager實例,所以我們先來看下Activity和Fragment是如何管理這些LoaderManager的。
先來看看Fragment中的LoaderManager,如下:
final class FragmentState implements Parcelable {
......
LoaderManagerImpl mLoaderManager;
boolean mLoadersStarted;
boolean mCheckedForLoaderManager;
......
//fragment中獲取LoaderManager辦法
public LoaderManager getLoaderManager() {
//可以看見,一個Fragment只有一個LoaderManager
if (mLoaderManager != null) {
return mLoaderManager;
}
if (mActivity == null) {
throw new IllegalStateException(Fragment + this + not attached to Activity);
}
mCheckedForLoaderManager = true;
//從Activity中獲取LoaderManager,傳入的mWho為當前Fragment的識別key,然後create傳入true表示創建!!!!!!
mLoaderManager = mActivity.getLoaderManager(mWho, mLoadersStarted, true);
return mLoaderManager;
}
public void onStart() {
mCalled = true;
if (!mLoadersStarted) {
mLoadersStarted = true;
if (!mCheckedForLoaderManager) {
mCheckedForLoaderManager = true;
//如果還沒調運過getLoaderManager,那就嘗試獲取LoaderManager,傳入的create為false!!!!!
mLoaderManager = mActivity.getLoaderManager(mWho, mLoadersStarted, false);
}
//生命周期依附上LoaderManager
if (mLoaderManager != null) {
mLoaderManager.doStart();
}
}
}
public void onDestroy() {
mCalled = true;
if (!mCheckedForLoaderManager) {
mCheckedForLoaderManager = true;
//如果還沒調運過getLoaderManager,那就嘗試獲取LoaderManager,傳入的create為false!!!!!
mLoaderManager = mActivity.getLoaderManager(mWho, mLoadersStarted, false);
}
//生命周期依附上LoaderManager
if (mLoaderManager != null) {
mLoaderManager.doDestroy();
}
}
void performStart() {
......
mCalled = false;
onStart();
......
//生命周期依附上LoaderManager
if (mLoaderManager != null) {
mLoaderManager.doReportStart();
}
}
void performStop() {
......
mCalled = false;
onStop();
......
if (mLoadersStarted) {
mLoadersStarted = false;
if (!mCheckedForLoaderManager) {
mCheckedForLoaderManager = true;
//如果還沒調運過getLoaderManager,那就嘗試獲取LoaderManager,傳入的create為false!!!!!
mLoaderManager = mActivity.getLoaderManager(mWho, mLoadersStarted, false);
}
if (mLoaderManager != null) {
//生命周期依附上LoaderManager
if (mActivity == null || !mActivity.mChangingConfigurations) {
mLoaderManager.doStop();
} else {
mLoaderManager.doRetain();
}
}
}
}
void performDestroyView() {
......
mCalled = false;
onDestroyView();
......
//生命周期依附上LoaderManager
if (mLoaderManager != null) {
mLoaderManager.doReportNextStart();
}
}
}
從上面可以看出,Fragment在其生命周期內會控制LoaderManager(LoaderManager其實控制了Loader)的doStart、doDestroy等方法,也就是說我們在Fragment中只管通過getLoaderManager方法來獲取LoaderManager實例,然後使用就行,別的Fragment都會幫我們處理OK的。
接下來看看Activity中的LoaderManager,如下:
public class Activity extends ContextThemeWrapper
implements ... {
//mAllLoaderManagers保存了Activity與Fragment的所有LoaderManager
ArrayMap mAllLoaderManagers;
LoaderManagerImpl mLoaderManager;
......
//Activity中獲取LoaderManager實例的方法
public LoaderManager getLoaderManager() {
//可以看見,一個Activity只有一個LoaderManager
if (mLoaderManager != null) {
return mLoaderManager;
}
mCheckedForLoaderManager = true;
//咦?這不就是上面Fragment的getLoaderManager中調運的那個activity中的getLoaderManager嗎,只是和這裡的參數不一樣而已
mLoaderManager = getLoaderManager((root), mLoadersStarted, true);
return mLoaderManager;
}
//Activity與Fragment獲取LoaderManager實例的真正方法!!
LoaderManagerImpl getLoaderManager(String who, boolean started, boolean create) {
//可見一個Activity維護一個mAllLoaderManagers的MAP
if (mAllLoaderManagers == null) {
mAllLoaderManagers = new ArrayMap();
}
//嘗試從緩存mAllLoaderManagers的MAP中獲取已經實例化的LoaderManager實例
LoaderManagerImpl lm = mAllLoaderManagers.get(who);
if (lm == null) {
if (create) {
//如果沒有找到並且需要實例化create(切記這個create參數是很重要的),就調運LoaderManagerImpl構造方法實例化一個LoaderManager對象,然後存入緩存mAllLoaderManagers的MAP中
lm = new LoaderManagerImpl(who, this, started);
mAllLoaderManagers.put(who, lm);
}
} else {
lm.updateActivity(this);
}
return lm;
}
void invalidateFragment(String who) {
if (mAllLoaderManagers != null) {
LoaderManagerImpl lm = mAllLoaderManagers.get(who);
if (lm != null && !lm.mRetaining) {
//生命周期依附上LoaderManager
lm.doDestroy();
mAllLoaderManagers.remove(who);
}
}
}
final void performStop() {
if (mLoadersStarted) {
mLoadersStarted = false;
//生命周期依附上LoaderManager
if (mLoaderManager != null) {
//mChangingConfigurations表示如果當前發生了配置變化則為true,否則為false!!!!!!!重點,Loader特性之一
if (!mChangingConfigurations) {
//當前Activity的stop不是由配置變化引起則直接調用LoaderManager的doStop()方法!!!!!!
mLoaderManager.doStop();
} else {
//當前Activity配置變化,所以需要保存當前的loaderManager,在Activity恢復時恢復這個LoaderManager!!!!!!
mLoaderManager.doRetain();
}
}
}
......
}
final void performDestroy() {
......
onDestroy();
//生命周期依附上LoaderManager
if (mLoaderManager != null) {
mLoaderManager.doDestroy();
}
......
}
protected void onCreate(@Nullable Bundle savedInstanceState) {
if (mLastNonConfigurationInstances != null) {
//從mLastNonConfigurationInstances中恢復mAllLoaderManagers(mLastNonConfigurationInstances是從onAttach中恢復的),Activity配置變化時會走這裡!!!!
mAllLoaderManagers = mLastNonConfigurationInstances.loaders;
}
......
mCalled = true;
}
final void performStart() {
......
if (mAllLoaderManagers != null) {
final int N = mAllLoaderManagers.size();
LoaderManagerImpl loaders[] = new LoaderManagerImpl[N];
for (int i=N-1; i>=0; i--) {
loaders[i] = mAllLoaderManagers.valueAt(i);
}
//生命周期依附上LoaderManager
for (int i=0; i
通過上面的分析可以發現,Activity其實真正的管理了Activity及Fragment的LoaderManager(Fragment也會管理一部分自己LoaderManager的周期),而LoaderManager又管理了Loader,可以發現他們各自的管理范圍都是十分的清晰明了的。
3-2 LoadManager及其實現類LoadManagerImpl的淺析
上面分析Activity及Fragment中獲取LoaderManager實例時已經知道,我們獲取的LoaderManager實例其實就是LoaderManagerImpl對象,而LoaderManagerImpl又是LoaderManager類的子類,所以接下來我們來分析這兩個父子類。
先看下抽象父類LoaderManager,如下:
public abstract class LoaderManager {
//LoaderManager的回調接口定義
public interface LoaderCallbacks {
public Loader onCreateLoader(int id, Bundle args);
public void onLoadFinished(Loader loader, D data);
public void onLoaderReset(Loader loader);
}
//下面這些方法沒必要再細說了,上面介紹過的
public abstract Loader initLoader(int id, Bundle args,
LoaderManager.LoaderCallbacks callback);
public abstract Loader restartLoader(int id, Bundle args,
LoaderManager.LoaderCallbacks callback);
//會觸發回調的onLoaderReset方法
public abstract void destroyLoader(int id);
public abstract Loader getLoader(int id);
public abstract void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args);
public static void enableDebugLogging(boolean enabled) {
LoaderManagerImpl.DEBUG = enabled;
}
}
可以看見LoaderManager抽象類只是定義了一些規范接口而已,那麼接著我們看下抽象類LoaderManager的實現類LoaderManagerImpl,如下:
class LoaderManagerImpl extends LoaderManager {
static final String TAG = LoaderManager;
static boolean DEBUG = false;
//保存當前存活的Loader
final SparseArray mLoaders = new SparseArray(0);
//保存已經運行完的Loader
final SparseArray mInactiveLoaders = new SparseArray(0);
final String mWho;
Activity mActivity;
boolean mStarted;
boolean mRetaining;
boolean mRetainingStarted;
//是否正在創建Loader,多線程中同時調運創建會導致異常
boolean mCreatingLoader;
//Loader的封裝類
final class LoaderInfo implements Loader.OnLoadCompleteListener
我勒個去!好長,好累!通過上面粗略的分析你會發現和我們上面基礎實例介紹LoaderManager的方法時描述的一樣,每個方法都有自己的特點,發揮著各自的作用,LoaderManager的實質是將Loader對象轉換為LoaderInfo來進行管理,也就是管理了所有的Loader對象。
3-3 Loader及其實現類的淺析
上面分析了Activity及Fragment管理了LoaderManager的相關方法,LoaderManager管理了Loader的相關方法,那麼接下來我們就來看看這個被管理的終極目標Loader是咋回事,還有他的子類咋回事。
先來看看我畫的一張關系圖,如下:
我去,這圖現在看可能有些嚇人,我們還是先來慢慢分析一下再說吧。
3-3-1 Loader基類源碼淺析
我們先來看看這個Loader基類吧,該類核心方法及內部類結構圖如下:
代碼分析如下:
public class Loader {
int mId;
OnLoadCompleteListener mListener;
OnLoadCanceledListener mOnLoadCanceledListener;
Context mContext;
boolean mStarted = false;
boolean mAbandoned = false;
boolean mReset = true;
boolean mContentChanged = false;
boolean mProcessingChange = false;
//數據源變化監聽器(觀察者模式),實現了ContentObserver類
public final class ForceLoadContentObserver extends ContentObserver {
public ForceLoadContentObserver() {
super(new Handler());
}
@Override
public boolean deliverSelfNotifications() {
return true;
}
@Override
public void onChange(boolean selfChange) {
//實質是調運Loader的forceLoad方法
onContentChanged();
}
}
//Loader加載完成接口,當加載完成時Loader通知loaderManager,loaderManager再回調我們initLoader方法的callback
public interface OnLoadCompleteListener {
public void onLoadComplete(Loader loader, D data);
}
//LoaderManager中監聽cancel,同上類似
public interface OnLoadCanceledListener {
public void onLoadCanceled(Loader loader);
}
//構造方法
public Loader(Context context) {
//mContext持有Application的Context,防止洩露內存等
mContext = context.getApplicationContext();
}
//加載完成時回調傳遞加載數據結果,實質是對OnLoadCompleteListener接口方法的封裝
public void deliverResult(D data) {
if (mListener != null) {
mListener.onLoadComplete(this, data);
}
}
//類似同上,對OnLoadCanceledListener的方法的封裝
public void deliverCancellation() {
if (mOnLoadCanceledListener != null) {
mOnLoadCanceledListener.onLoadCanceled(this);
}
}
public Context getContext() {
return mContext;
}
public int getId() {
return mId;
}
public void registerListener(int id, OnLoadCompleteListener listener) {
mListener = listener;
mId = id;
}
public void unregisterListener(OnLoadCompleteListener listener) {
mListener = null;
}
public void registerOnLoadCanceledListener(OnLoadCanceledListener listener) {
mOnLoadCanceledListener = listener;
}
public void unregisterOnLoadCanceledListener(OnLoadCanceledListener listener) {
mOnLoadCanceledListener = null;
}
public boolean isStarted() {
return mStarted;
}
public boolean isAbandoned() {
return mAbandoned;
}
public boolean isReset() {
return mReset;
}
//開始加載數據時LoaderManager會調用該方法
public final void startLoading() {
//設置標記
mStarted = true;
mReset = false;
mAbandoned = false;
onStartLoading();
}
//真正開始加載數據的地方******空方法,子類實現!!!!!!
protected void onStartLoading() {
}
//取消Loader的方法
public boolean cancelLoad() {
return onCancelLoad();
}
//真正取消的地方******,子類實現!!!!!!return false表示取消失敗(因為已完成或未開始)
protected boolean onCancelLoad() {
return false;
}
//強制重新Loader,放棄舊數據
public void forceLoad() {
onForceLoad();
}
//真正重新Loader的地方******空方法,子類實現!!!!!!
protected void onForceLoad() {
}
//同上
public void stopLoading() {
mStarted = false;
onStopLoading();
}
protected void onStopLoading() {
}
//同上
public void abandon() {
mAbandoned = true;
onAbandon();
}
protected void onAbandon() {
}
//同上
public void reset() {
onReset();
mReset = true;
mStarted = false;
mAbandoned = false;
mContentChanged = false;
mProcessingChange = false;
}
protected void onReset() {
}
//Loader數據變化的一些標記處理
public boolean takeContentChanged() {
boolean res = mContentChanged;
mContentChanged = false;
mProcessingChange |= res;
return res;
}
public void commitContentChanged() {
mProcessingChange = false;
}
public void rollbackContentChanged() {
if (mProcessingChange) {
mContentChanged = true;
}
}
//上面ForceLoadContentObserver內部類的onChange方法調運
public void onContentChanged() {
if (mStarted) {
forceLoad();
} else {
mContentChanged = true;
}
}
//一些方便調試的方法
public String dataToString(D data)
public String toString()
public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)
}
通過上面粗略的分析可以發現,Loader基類無非也就是一個方法接口的定義類,組織預留了一些方法供LoaderManager去調運處理,同時需要子類實現其提供的一些onXXX方法,以便LoaderManager調運Loader的方法時可以觸發Loader子類的實現邏輯。
3-3-2 AsyncTaskLoader抽象子類源碼淺析
上面既然說了Loader類的作用主要是規定接口,同時供LoaderManager管理,那LoaderManager管理的Loader自然需要做一些事情,也就是說我們需要繼承Loader實現一些邏輯操作。然而好在系統API已經幫我們實現了一些簡單的封裝實現,我們這裡就先來看下Loader的直接子類AsyncTaskLoader吧,先來看下該抽象子類的方法及內部類粗略圖,如下:
代碼分析如下:
public abstract class AsyncTaskLoader extends Loader {
static final String TAG = AsyncTaskLoader;
static final boolean DEBUG = false;
//LoadTask內部類是對AsyncTask的封裝,實現了Runnable接口
final class LoadTask extends AsyncTask implements Runnable {
......
@Override
protected D doInBackground(Void... params) {
try {
//AsyncTask的子線程中執行AsyncTaskLoader的onLoadInBackground方法!!!!重點
D data = AsyncTaskLoader.this.onLoadInBackground();
//把執行結果數據D返回到UI線程
return data;
} catch (OperationCanceledException ex) {
if (!isCancelled()) {
throw ex;
}
return null;
}
}
/* Runs on the UI thread */
@Override
protected void onPostExecute(D data) {
//AsyncTask子線程執行完畢後回調AsyncTaskLoader的dispatchOnLoadComplete方法
AsyncTaskLoader.this.dispatchOnLoadComplete(this, data);
}
/* Runs on the UI thread */
@Override
protected void onCancelled(D data) {
//取消AsyncTask時調運
AsyncTaskLoader.this.dispatchOnCancelled(this, data);
}
//Runnable的實現方法
@Override
public void run() {
waiting = false;
AsyncTaskLoader.this.executePendingTask();
}
......
}
private final Executor mExecutor;
volatile LoadTask mTask;
volatile LoadTask mCancellingTask;
long mUpdateThrottle;
long mLastLoadCompleteTime = -10000;
Handler mHandler;
//public構造方法
public AsyncTaskLoader(Context context) {
this(context, AsyncTask.THREAD_POOL_EXECUTOR);
}
/** {@hide} 無法被外部調運的構造方法 */
public AsyncTaskLoader(Context context, Executor executor) {
super(context);
mExecutor = executor;
}
public void setUpdateThrottle(long delayMS) {
mUpdateThrottle = delayMS;
if (delayMS != 0) {
mHandler = new Handler();
}
}
@Override
protected void onForceLoad() {
super.onForceLoad();
//取消當前的Loader
cancelLoad();
//新建task並執行
mTask = new LoadTask();
executePendingTask();
}
@Override
protected boolean onCancelLoad() {
......
}
public void onCanceled(D data) {
}
//LoadTask的Runnable方法run中執行
void executePendingTask() {
if (mCancellingTask == null && mTask != null) {
if (mTask.waiting) {
mTask.waiting = false;
mHandler.removeCallbacks(mTask);
}
if (mUpdateThrottle > 0) {
long now = SystemClock.uptimeMillis();
if (now < (mLastLoadCompleteTime+mUpdateThrottle)) {
// Not yet time to do another load.
mTask.waiting = true;
mHandler.postAtTime(mTask, mLastLoadCompleteTime+mUpdateThrottle);
return;
}
}
//真正的觸發執行AsyncTask方法
mTask.executeOnExecutor(mExecutor, (Void[]) null);
}
}
void dispatchOnCancelled(LoadTask task, D data) {
onCanceled(data);
if (mCancellingTask == task) {
rollbackContentChanged();
mLastLoadCompleteTime = SystemClock.uptimeMillis();
mCancellingTask = null;
//觸發Loader的接口方法onLoadCanceled,在LoaderManager中實現
deliverCancellation();
executePendingTask();
}
}
void dispatchOnLoadComplete(LoadTask task, D data) {
if (mTask != task) {
dispatchOnCancelled(task, data);
} else {
if (isAbandoned()) {
// This cursor has been abandoned; just cancel the new data.
onCanceled(data);
} else {
commitContentChanged();
mLastLoadCompleteTime = SystemClock.uptimeMillis();
mTask = null;
//觸發Loader的接口方法onLoadComplete,在LoaderManager中實現
deliverResult(data);
}
}
}
//需要子類實現!!!!!在子線程中執行
public abstract D loadInBackground();
//LoadTask(AsyncTask的子線程中回調)中調運
protected D onLoadInBackground() {
return loadInBackground();
}
//LoadTask(AsyncTask的onCancelLoad中回調)調運
public void cancelLoadInBackground() {
}
public boolean isLoadInBackgroundCanceled() {
return mCancellingTask != null;
}
//鎖標記處理
public void waitForLoader() {
LoadTask task = mTask;
if (task != null) {
task.waitForLoader();
}
}
}
可以看見上面繼承Loader的AsyncTaskLoader其實質是提供了一個基於AsyncTask工作機制的Loader(子類LoadTask繼承
AsyncTask
,並且實現了Runable接口,功能十分強大。),但是不可直接用,因為其為abstract抽象類,所以我們需要繼承實現它才可以使用,然而好在系統API已經幫我們提供了他現成的子類CursorLoader,但CursorLoader同時也限制了Loader的泛型數據為Cursor類型。當然了,我們如果想要Loader自己的類型數據那也很簡單—繼承實現AsyncTaskLoader即可,後面會給出例子的。
3-3-3 CursorLoader子類源碼淺析
有了上面繼承自Loader的抽象AsyncTaskLoader,接下來我們就來看看SDK為我們提供的抽象AsyncTaskLoader實現類CursorLoader,我們先來粗略看看該類的方法圖,如下:
具體代碼分析如下:
//繼承自AsyncTaskLoader,數據類型為Cursor的Loader異步加載實現類
public class CursorLoader extends AsyncTaskLoader {
//Cursor的子類ForceLoadContentObserver
final ForceLoadContentObserver mObserver;
Uri mUri;
String[] mProjection;
String mSelection;
String[] mSelectionArgs;
String mSortOrder;
Cursor mCursor;
CancellationSignal mCancellationSignal;
/* Runs on a worker thread
最核心的實現方法,在這裡查詢獲取數據 */
@Override
public Cursor loadInBackground() {
synchronized (this) {
if (isLoadInBackgroundCanceled()) {
throw new OperationCanceledException();
}
mCancellationSignal = new CancellationSignal();
}
try {
//不過多解釋,耗時的查詢操作
Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection,
mSelectionArgs, mSortOrder, mCancellationSignal);
if (cursor != null) {
try {
// Ensure the cursor window is filled.
cursor.getCount();
//給Cursor設置觀察者;ContentProvider通知Cursor的觀察者數據發生了改變,Cursor通知CursorLoader的觀察者數據發生了改變,CursorLoader通過ContentProvider重新加載新的數據
cursor.registerContentObserver(mObserver);
} catch (RuntimeException ex) {
cursor.close();
throw ex;
}
}
return cursor;
} finally {
synchronized (this) {
mCancellationSignal = null;
}
}
}
@Override
public void cancelLoadInBackground() {
super.cancelLoadInBackground();
synchronized (this) {
if (mCancellationSignal != null) {
mCancellationSignal.cancel();
}
}
}
/* Runs on the UI thread */
@Override
public void deliverResult(Cursor cursor) {
if (isReset()) {
// An async query came in while the loader is stopped
if (cursor != null) {
cursor.close();
}
return;
}
Cursor oldCursor = mCursor;
mCursor = cursor;
if (isStarted()) {
super.deliverResult(cursor);
}
if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {
oldCursor.close();
}
}
public CursorLoader(Context context) {
super(context);
mObserver = new ForceLoadContentObserver();
}
public CursorLoader(Context context, Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
super(context);
//新建一個當前類(Loader)的內部類對象,數據庫變化時調運ForceLoadContentObserver的onChange方法,onChange調運Loader的onContentChanged方法,onContentChanged調運Loader的forceLoad方法
mObserver = new ForceLoadContentObserver();
mUri = uri;
mProjection = projection;
mSelection = selection;
mSelectionArgs = selectionArgs;
mSortOrder = sortOrder;
}
@Override
protected void onStartLoading() {
if (mCursor != null) {
deliverResult(mCursor);
}
if (takeContentChanged() || mCursor == null) {
forceLoad();
}
}
@Override
protected void onStopLoading() {
// Attempt to cancel the current load task if possible.
cancelLoad();
}
@Override
public void onCanceled(Cursor cursor) {
if (cursor != null && !cursor.isClosed()) {
cursor.close();
}
}
@Override
protected void onReset() {
super.onReset();
// Ensure the loader is stopped
onStopLoading();
if (mCursor != null && !mCursor.isClosed()) {
mCursor.close();
}
mCursor = null;
}
public Uri getUri() {
return mUri;
}
public void setUri(Uri uri) {
mUri = uri;
}
public String[] getProjection() {
return mProjection;
}
public void setProjection(String[] projection) {
mProjection = projection;
}
public String getSelection() {
return mSelection;
}
public void setSelection(String selection) {
mSelection = selection;
}
public String[] getSelectionArgs() {
return mSelectionArgs;
}
public void setSelectionArgs(String[] selectionArgs) {
mSelectionArgs = selectionArgs;
}
public String getSortOrder() {
return mSortOrder;
}
public void setSortOrder(String sortOrder) {
mSortOrder = sortOrder;
}
}
可以發現,CursorLoader的封裝大大簡化了應用開發者代碼的復雜度;它完全就是一個異步的數據庫查詢瑞士軍刀,沒有啥特別需要分析的地方,所以不再過多說明。
3-4 Loaders相關源碼淺析總結
通過上面我們的源碼分析和分析前那副圖可以總結如下結論:
一次完整的數據加載流程為Activity調用LoaderManager的doStart()方法,然後LoaderManager調用Loader的startLoading()方法,然後Loader調運AsyncTaskLoader的doingBackground()方法進行耗時數據加載,然後AsyncTaskLoader回調LoaderManager的complete數據加載完成方法,接著LoaderManager回調我們在Activity中實現的callback中的onLoadFinish()方法。
Acivity和Fragment的生命周期主動管理了LoaderManager,每個Activity用一個ArrayMap的mAllLoaderManager來保存當前Activity及其附屬Frament的唯一LoaderManager;在Activity配置發生變化時,Activity在destory前會保存mAllLoaderManager,當Activity再重新創建時,會在Activity的onAttcach()、onCreate()、performStart()方法中恢復mAllLoaderManager。
LoaderManager給Activity提供了管理自己的一些方法;同時主動管理了對應的Loader,它把每一個Loader封裝為LoadInfo對象,同時它負責主動調運管理Loader的startLoading()、stopLoading()、,forceLoad()等方法。
由於整個Activity和Fragment主動管理了Loader,所以關於Loader的釋放(譬如CursorLoader的Cursor關閉等)不需要我們人為處理,Loader框架會幫我們很好的處理的;同時特別注意,對於CursorLoader,當我們數據源發生變化時Loader框架會通過ContentObserver調用onContentChanged的forceLoad方法重新請求數據進行回調刷新。
好了,至此你會發現Loader真的很牛叉,No!應該是Google的工程師真的很牛叉,架構真的很贊,值得推薦。
【工匠若水 http://blog.csdn.net/yanbober 轉載請注明出處。點我開始Android技術交流】
4 應用層開發之Loader進階實戰
上面對於Loader的基礎使用及源碼框架都進行了簡單分析,有了上面的鋪墊我們再回過頭來看看我們開發中的一些高級技巧,通過這些高級技巧不僅是對前面源碼分析的實例驗證,也是對自己知識的積累。
4-1 ContentPorvider情況下的CurSorLoader自動刷新
在我們使用CurSorLoader時大家都會考慮一種情況的處理—–當數據庫發生變化時如何自動刷新當前UI。呵呵,我們先來說說這個原理,數據庫在數據改變時通過ContentPorvider和ContentResolver發出通知,接著ContentProvider通知Cursor的觀察者數據發生了變化,然後Cursor通知CursorLoader的觀察者數據發生了變化,接著CursorLoader通過ContentProvider加載新數據,完事調用CursorAdapter的changeCursor()用新數據替換舊數據顯示。
這個過程具體的實現步驟如下:
對獲取的Cursor數據設置需要監聽的URI(即,在ContentProvider的query()方法或者Loader的loadingBackground()方法中調用Cursor的setNotificationUri()方法);
在ContentProvider的insert()、update()、delete()等方法中調用ContentResolver的notifyChange()方法;
通過上面兩步我們就能享受CurSorLoader的自動數據刷新功能了;可以發現,所謂的CurSorLoader自動刷新無非就是觀察者模式的框架而已,所以不再過多說明。
特別注意:
有些人覺得為了方便可能會將上面第一步對於Cursor設置監聽直接寫在了ContentProvider的query()方法中,如下:
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[]
selectionArgs,String sortOrder) {
SQLiteDatabase database = sqLiteOpenHelper.getReadableDatabase();
Cursor cursor = database.query(EmailContent.CONTACT_TABLE, projection,
selection,selectionArgs, null, null, sortOrder);
//設置NotificationUri監聽
cursor.setNotificationUri(contentResolver, EmailContent.MESSAGE);
return cursor;
}
這裡要提醒的是,這種寫法在某些場合下是不值得推薦的(譬如大規模上千次並發平凡的調運query操作場合),因為效率極低,他會頻繁的通過Binder進行通信,導致system_server不停的調運GC操作,以至於會使系統卡頓。
PS:因為我以前跳過一次這個坑,平時使用應用沒啥問題,但是當進行壓力測試時卻發現LogCat一直在不停的打印GC,同時導致當前系統卡頓,殺掉應用後系統就不卡了,所以基本懷疑問題就出在了應用中,於是通過很多辦法去查找(譬如dempsys content去查看個數),最終發現罪魁禍首是這個監聽頻繁調運導致的,隨將其挪到loadingBackground中不再卡頓。
4-2 不使用ContentPorvider且自定義Loader的情況下自動刷新
我們目前的項目其實都使用了ContentPorvider實現,所以就是上面講的那些情況。但是你一定會問,如果我們應用的數據不用於應用間共享,使用ContentProvider那得多麻煩啊?我先告訴你,是很麻煩,但是Android提供的CursorLoader的API必須使用ContentProvider才能實現數據加載和自動刷新。
這時候你指定會說,那還說個屁!哎,別急,你看看下面這段代碼是否會有所感觸呢,如下:
public NoProviderLoader extends AsyncTaskLoader {
......
ForceLoadContentObserver mObserver = new ForceLoadContentObserver();
......
@Override
public Cursor loadInBackground() {
SQLiteDatabase database = sqLiteOpenHelper.getReadableDatabase();
Cursor cursor = database.query(table, columns, selection, selectionArgs, groupBy, having, orderBy);
if (cursor != null) {
//最重要的兩行代碼!!!!!!
cursor.registerContentObserver(mObserver);//給Cursor設置觀察者
cursor.setNotificationUri(getContext().getContentResolver(), otificationUri);//給Cursor設置要觀察的URI
}
return cursor;
}
......
}
咦?是不是上面代碼很奇怪,異步操作的方法中沒有使用ContentProvider,而是直接讀取了數據庫。握草!這不就是我們剛剛想要的需求麼,它沒有使用ContentProvider提供Cursor數據,同時實現了數據變化自動更新功能。
簡單解釋下上面代碼的原理吧,我們自定義的NoProviderLoader中定義的ForceLoadContentObserver是Loader的一個內部類,上面源碼分析已經解釋過了,當數據變化時會調運該類的onChange()方法,實質是調運了Loader的forceLoad()方法,所以能夠自動刷新,不多解釋了。
4-3 Loader自定義之AsyncTaskLoader衍生
可能看到這裡你更加會舉一反三的反駁一句了,上面搞了半天都是和數據庫Cursor相關的東東,難道Loader就不能異步處理別的數據結構麼?答案是能,因為你可能已經注意到了Loader和AsyncTaskLoader都是泛型類;既然這樣,那我們找貓畫虎一把呗,仿照CursorLoader自定義一個自己的異步加載試試,具體實現如下(哈哈,想了又想,這裡還是直接給出官方的自定義AsyncTaskLoader好點,畢竟權威些,詳細點我查看官方自定義實現Demo):
官方對於查詢已安裝App列表的Loader實現,支持新App安裝後自動刷新的功能,實現如下:
/**
* This class holds the per-item data in our Loader.
*/
public static class AppEntry {
public AppEntry(AppListLoader loader, ApplicationInfo info) {
mLoader = loader;
mInfo = info;
mApkFile = new File(info.sourceDir);
}
public ApplicationInfo getApplicationInfo() {
return mInfo;
}
public String getLabel() {
return mLabel;
}
public Drawable getIcon() {
if (mIcon == null) {
if (mApkFile.exists()) {
mIcon = mInfo.loadIcon(mLoader.mPm);
return mIcon;
} else {
mMounted = false;
}
} else if (!mMounted) {
// If the app wasn't mounted but is now mounted, reload
// its icon.
if (mApkFile.exists()) {
mMounted = true;
mIcon = mInfo.loadIcon(mLoader.mPm);
return mIcon;
}
} else {
return mIcon;
}
return mLoader.getContext().getResources().getDrawable(
android.R.drawable.sym_def_app_icon);
}
@Override public String toString() {
return mLabel;
}
void loadLabel(Context context) {
if (mLabel == null || !mMounted) {
if (!mApkFile.exists()) {
mMounted = false;
mLabel = mInfo.packageName;
} else {
mMounted = true;
CharSequence label = mInfo.loadLabel(context.getPackageManager());
mLabel = label != null ? label.toString() : mInfo.packageName;
}
}
}
private final AppListLoader mLoader;
private final ApplicationInfo mInfo;
private final File mApkFile;
private String mLabel;
private Drawable mIcon;
private boolean mMounted;
}
/**
* Perform alphabetical comparison of application entry objects.
*/
public static final Comparator ALPHA_COMPARATOR = new Comparator() {
private final Collator sCollator = Collator.getInstance();
@Override
public int compare(AppEntry object1, AppEntry object2) {
return sCollator.compare(object1.getLabel(), object2.getLabel());
}
};
/**
* Helper for determining if the configuration has changed in an interesting
* way so we need to rebuild the app list.
*/
public static class InterestingConfigChanges {
final Configuration mLastConfiguration = new Configuration();
int mLastDensity;
boolean applyNewConfig(Resources res) {
int configChanges = mLastConfiguration.updateFrom(res.getConfiguration());
boolean densityChanged = mLastDensity != res.getDisplayMetrics().densityDpi;
if (densityChanged || (configChanges&(ActivityInfo.CONFIG_LOCALE
|ActivityInfo.CONFIG_UI_MODE|ActivityInfo.CONFIG_SCREEN_LAYOUT)) != 0) {
mLastDensity = res.getDisplayMetrics().densityDpi;
return true;
}
return false;
}
}
/**
* Helper class to look for interesting changes to the installed apps
* so that the loader can be updated.
*/
public static class PackageIntentReceiver extends BroadcastReceiver {
final AppListLoader mLoader;
public PackageIntentReceiver(AppListLoader loader) {
mLoader = loader;
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addDataScheme(package);
mLoader.getContext().registerReceiver(this, filter);
// Register for events related to sdcard installation.
IntentFilter sdFilter = new IntentFilter();
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
mLoader.getContext().registerReceiver(this, sdFilter);
}
@Override public void onReceive(Context context, Intent intent) {
// Tell the loader about the change.
mLoader.onContentChanged();
}
}
/**
* A custom Loader that loads all of the installed applications.
*/
public static class AppListLoader extends AsyncTaskLoader
不用多說,上面Loader為Google出品,強大的不得了,我們完全可以仿寫這個例子實現自己的請求。
如下為官方對該自定義Loader調運的Demo代碼:
public static class AppListAdapter extends ArrayAdapter {
private final LayoutInflater mInflater;
public AppListAdapter(Context context) {
super(context, android.R.layout.simple_list_item_2);
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
public void setData(List data) {
clear();
if (data != null) {
addAll(data);
}
}
/**
* Populate new items in the list.
*/
@Override public View getView(int position, View convertView, ViewGroup parent) {
View view;
if (convertView == null) {
view = mInflater.inflate(R.layout.list_item_icon_text, parent, false);
} else {
view = convertView;
}
AppEntry item = getItem(position);
((ImageView)view.findViewById(R.id.icon)).setImageDrawable(item.getIcon());
((TextView)view.findViewById(R.id.text)).setText(item.getLabel());
return view;
}
}
public static class AppListFragment extends ListFragment
implements OnQueryTextListener, OnCloseListener,
LoaderManager.LoaderCallbacks
強大的一逼!這下滿技能,不解釋,自己看。
4-4 進階總結
通過前面基礎實例、源碼分析、進階演示你會發現Loader的真的非常好用,非常牛逼,牛逼的我不想再解釋啥了,自己體會吧。
PS:之前看見微博上有人討論AsyncTaskLoader與AsyncTask的區別,這下徹底明朗了,看完源碼我們再回過頭來總結性的說說他們二者區別,如下:
class
優勢
劣勢
AsyncTaskLoader
會自動刷新數據變化;會自動處理Activiy配置變化造成的影響;適合處理純數據加載;
不能實時通知UI刷新;不能在onLoadFinished時主動切換生命周期(譬如replace Fragment);
AsyncTask
可以與UI實時交互及replace操作;
不會自動處理Activiy配置變化造成的影響;
好了,該撕逼的也撕了,該裝逼的也裝了,該分析的也分析了,該學習的也學到了,接下來就是看自己如何帶著Loader去叱詫風雲了。
Android For JNI(二)——C語言中的數據類型,輸出,輸入函數以及操作內存地址,內存修改器 當我們把Hello World寫完之後,我
----下載JDK(Java Dev Kit) 官方下載:http://www.oracle.com/technetwork/java/javase/download
android開發中,對於復用率較高的多個控件,采用組件的方式可能更加方便,首先定義一個xml文件: 文件名:lyt_customer_service_phone.xml
今天我們封裝一個底部的菜單欄,這個大多數的應用都會用到,因此我們來自定義,方便以後項目的使用。該控件的實現將分上下篇來介紹,先來看一個菜單欄的子控件–MenuItemM,