先扯兩句
這次已經(jīng)記不清距離上次發(fā)博客有多久了,總歸是好久了吧葵礼。而這次要寫的內(nèi)容槽地,之前也多次開始迁沫,不過又都隨著自己后面的應(yīng)用烹卒,重新作出了調(diào)整,如果收藏了我的demo的或許能夠看到修改的過程弯洗,說實在的旅急,現(xiàn)在的base封裝與之前的實在是相差太多,多到我自己都快找不到之前的痕跡了牡整。
雖然也不能肯定之后還會不會繼續(xù)調(diào)整藐吮,不過也不能這么無休止的拖下去了,如果有需要的可以收藏一下我的demo逃贝,我的修改都會第一時間發(fā)到上面的谣辞,以后我也盡量在修改的同時一同發(fā)博客說明。
閑言少敘沐扳,老規(guī)矩還是先上我的Git庫泥从,然后開始正文吧。
MyBaseApplication (https://github.com/BanShouWeng/MyBaseApplication)
正文
正文的開頭沪摄,我先說說自己這次修改base的初衷躯嫉,實際上目錄中的2~7篇博客,加上階段總結(jié)與修改1杨拐,已經(jīng)能夠基本實現(xiàn)了當前的功能祈餐,所以之前的博客我并沒有刪除掉,依然留給大家做個參考哄陶。
而既然基本能夠完成這功能帆阳,我這里為什么要多費事還要重新封裝底層呢?除了一些性能上的優(yōu)化以外屋吨,更重要的原因了解我的人一定都知道蜒谤,就因為一個字“懶”!至于這么個懶法至扰,下面我就為大家說明一下鳍徽。
抽象類
如果是看過我之前demo的朋友,再對比一下當前的demo渊胸,一定會發(fā)現(xiàn)現(xiàn)在的BaseActivity旬盯、BaseNetActivity、BaseFragment翎猛、BaseNetFragment以及新增加的BaseFragmentActivity都從原本的普通類變成了如今的抽象類胖翰。作為一個菜鳥,你如果讓我說明一下切厘,從性能上這么封裝會不會有什么優(yōu)勢萨咳,我還真一點也說不出來,我就只從“懶”上來做解釋了疫稿。
之前的普通類培他,當我們需要創(chuàng)建一個Activity或者Fragment去繼承Base的時候鹃两,往往需要我們自己去記,需要把setContentView改成setBaseContentView(布局嵌套)舀凛,然后還需要自己去創(chuàng)建一些initView(初始化視圖)俊扳、initData(初始化數(shù)據(jù))等方法,這些雖然說麻煩也不是很麻煩猛遍,但是對于一個懶人來說馋记,實在是太繁瑣了。而使用抽象類封裝懊烤,就可以把這些方法變成抽象方法梯醒,在繼承的時候,只需要我們實現(xiàn)即可腌紧。
BaseActivity
抽象方法如下:
/**
* 獲取布局ID
*
* @return 獲取的布局ID
*/
protected abstract int getLayoutId();
/**
* 獲取所有View信息
*/
protected abstract void findViews();
/**
* 初始化布局信息
*/
protected abstract void formatViews();
/**
* 初始化數(shù)據(jù)信息
*/
protected abstract void formatData();
/**
* 初始化Bundle
*/
protected abstract void getBundle(Bundle bundle);
除此之外茸习,這些抽象方法,需要在onCreate中進行調(diào)用:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
activities.add(this);
context = getApplicationContext();
activity = this;
event = this;
setContentView(R.layout.activity_base);
initSDK();
setBaseContentView(getLayoutId());
findViews();
getBundle(getIntent().getBundleExtra("bundle"));
formatViews();
formatData();
}
關(guān)于onCreate方法中的其他參數(shù)activities壁肋、context号胚、activity、event墩划,以及方法setBaseContentView的用處涕刚,如果不知道的,請參見我之前的博客《一個Android工程的從零開始》-5乙帮、base(四) BaseActivity——方法封裝,里面會有詳細的描述的极景。
getLayoutId()
顧名思義察净,獲取布局Id,也就是原本的setBaseContentView()方法盼樟,而實際上氢卡,我們在onCreate()方法中也很容易發(fā)現(xiàn),它就是setBaseContentView(getLayoutId());作為一個懶漢的我晨缴,在使用的時候译秦,只需要將我們的布局Id作為返回值返回即可。
例如:
@Override
protected int getLayoutId() {
return R.layout.activity_main;
}
這樣击碗,我們就實現(xiàn)了布局的設(shè)置筑悴,同時也能使用之前封裝好的Title。
findViews()
獲取所有View信息稍途,也就是剛剛前面說到的initView的一部分功能(至于為什么說是一部分功能阁吝,我們后面會說到),而在其中的操作其實也很簡單械拍,就是我們熟悉的不能在熟悉的findViewById突勇。當然装盯,這個findViewById我們也進行了進一步的封裝,具體怎么封裝的甲馋,我們這里先賣個關(guān)子埂奈,后面會為大家提到。
formatViews()
初始化布局信息定躏,這就是initView的令一部分功能挥转,比如設(shè)置圖片啊,設(shè)置文字啊共屈,設(shè)置布局適配器绑谣,設(shè)置一些監(jiān)聽接口之類的方法,我個人都將其劃分到這個部分來完成了拗引。
formatData()
初始化數(shù)據(jù)信息借宵,這里我個人的定義是設(shè)置一些數(shù)據(jù)的初始化信息,例如定時器之類的初始化矾削、數(shù)據(jù)庫工具的初始化壤玫、或者圖形驗證碼等工具類的初始化,不過該方法使用頻率較低
getBundle(Bundle bundle)
初始化Bundle哼凯,這里之所以選擇初始化bundle欲间,最重要的一個原因是我在Activity跳轉(zhuǎn)的部分做了一個封裝,所以Activity之間傳值就被約束成了通過bundle傳遞断部,而如果你是直接使用的Intent傳值猎贴,那么初始化intent也可以,這個方法就不加以限制了蝴光。而通過上面的說明她渴,想必大家也知道了,這個方法的主要用處就是Activity之間傳遞參數(shù)的一個取值方法蔑祟,當然趁耗,為了防止不必要的麻煩,在實現(xiàn)getBundle()方法的時候疆虚,還是建議大家添加一步bundle的判空以及相對應(yīng)的處理苛败。
initSDK()
當然,細心的還會發(fā)現(xiàn)径簿,在onCreate方法中罢屈,我調(diào)用的方法其實并不僅僅是前面說到的抽象方法,還有一個就是initSDK()方法牍帚,說來儡遮,這個方法的添加也是讓我很無奈啊,其實原本是沒有這個方法的暗赶,直到我使用Baidu地圖的時候鄙币。
看過百度地圖開發(fā)文檔的想必都看到過這么一段代碼:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//在使用SDK各組件之前初始化context信息肃叶,傳入ApplicationContext
//注意該方法要再setContentView方法之前實現(xiàn)
SDKInitializer.initialize(getApplicationContext());
setContentView(R.layout.activity_main);
//獲取地圖控件引用
mMapView = (MapView) findViewById(R.id.bmapView);
}
而排出掉可以移動到前面說的抽象方法中的方法后,其中一行就顯得尤為突兀:SDKInitializer.initialize(getApplicationContext()); 它的功能在官方注釋中也可以很看得出來十嘿,就是SDK的初始化因惭。這個方法,我們?nèi)绻胖迷谧覣ctivity的onCreate方法中绩衷,那你很榮幸的就會在調(diào)用mMapView = (MapView) findViewById(R.id.bmapView); 看到萬里江上一片紅蹦魔,全都是錯誤日志,翻譯過來就是讓我們?nèi)コ跏蓟俣萐DK咳燕。至于放到onCreate的super前面勿决,邏輯上確實是提前初始化了,不過依然會報錯崩潰掉招盲,至于日志是什么低缩,有興趣的話,大家可以自行嘗試一下曹货。
想必大家看到的我的順序咆繁,在看到官方的注釋就會發(fā)現(xiàn),我并沒有放到setContentView的前面顶籽,不過我的順序在執(zhí)行過程中玩般,確實也沒有報錯,同時也能夠正常使用礼饱,原因我也沒有去深究坏为,不過還是建議大家用官方建議的順序去執(zhí)行。
不過由于SDK的初始化慨仿,在使用頻率上久脯,比formatData()方法還要低,甚至一部分APP使用的都是類似于極光推送之類的SDK镰吆,只需要進行一次全局的初始化,而并不需要在使用它的Activity中進行初始化跑慕。所以這里選擇了在BaseActivity方法中寫了一個空方法万皿,在需要的時候重寫即可。
/**
* SDK初始化
*/
protected void initSDK() {
}
onCreate方法中的順序
前面的屬性部分核行,大家隨著心情來就好了牢硅,是沒有關(guān)系的,不過一定要放到這些抽象方法的調(diào)用前面芝雪,雖然setContentView()與getBundle(Bundle bundle)是不受這些參數(shù)影響的减余,但也是為了代碼排版更好看容易理解一些。
下面惩系,我這里setContentView()位岔,不過前面斜體字已經(jīng)說明了如筛,還是建議大家先使用initSDK(),在使用setContentView()抒抬。
下一個杨刨,setBaseContentView(getLayoutId())或者getBundle(Bundle bundle),這兩個方法則沒有順序前后的要求擦剑,只不過這兩個方法都需要放在findViews()方法與formatViews()方法之前妖胀,因為在formatViews()賦值時,很可能使用的是getBundle(Bundle bundle)傳遞來的值惠勒,而邏輯上findViews()最好與formatViews()相鄰赚抡。而最后,則是使用頻率相對較低的formatData()纠屋。
其中可以看得出來涂臣,有一些方法的順序并不是一成不變的,大家也是可以根據(jù)自己的需求進行增刪改
其他
首先需要說明一點巾遭,大家可以看一下標題肉康,這一篇博客所說的內(nèi)容只是BaseActivity的上,所以這個其他并不是除了上述抽象方法的其他所有灼舍,而是與抽象方法相關(guān)的一些其他方法吼和,總結(jié)起來有兩類三個方法:
/**
* 簡化獲取View
*
* @param viewId View的ID
* @param <T> 將View轉(zhuǎn)化為對應(yīng)泛型,簡化強轉(zhuǎn)的步驟
* @return ID對應(yīng)的View
*/
@SuppressWarnings("unchecked")
public <T extends View> T getView(int viewId) {
return (T) findViewById(viewId);
}
/**
* 簡化獲取View
*
* @param view 父view
* @param viewId View的ID
* @param <T> 將View轉(zhuǎn)化為對應(yīng)泛型骑素,簡化強轉(zhuǎn)的步驟
* @return ID對應(yīng)的View
*/
@SuppressWarnings("unchecked")
public <T extends View> T getView(View view, int viewId) {
return (T) view.findViewById(viewId);
}
/**
* 設(shè)置點擊事件
*
* @param layouts 點擊控件Id
*/
protected void setOnClickListener(int... layouts) {
for (int layout : layouts) {
getView(layout).setOnClickListener(this);
}
}
getView
還記得前面我說過吧炫乓,在這次BaseActivity的封裝中,我對findViewById也進行了封裝献丑,這也就是兩個getView方法末捣。
這里我們使用到了一個神奇的內(nèi)容“T”,在注釋中也能看出來创橄,這貨叫泛型箩做,當然別看我這里寫的T,大家就當成“T”就是泛型妥畏,T只不過是一個代指邦邦,就好像如果我把“T”替換成“半壽翁”,那么我“半壽翁”就成了泛型(別說你還不知道在AS中可以使用中文變量醉蚁,并嘲諷我使用中文)
/**
* 簡化獲取View
*
* @param viewId View的ID
* @param <半壽翁> 將View轉(zhuǎn)化為對應(yīng)泛型燃辖,簡化強轉(zhuǎn)的步驟
* @return ID對應(yīng)的View
*/
@SuppressWarnings("unchecked")
public <半壽翁 extends View> 半壽翁 getView(int viewId) {
return (半壽翁) findViewById(viewId);
}
其使用方法就是,找到所有泛型類的一個共同父類网棍,然后用泛型指代變量(“T”或者“半壽翁”黔龟,或者你喜歡的其他什么稱呼),<T extends View>繼承這個父類,這樣氏身,當我們用這個指代“T”表示這個父類的任何一個子類巍棱,都不需要進行強轉(zhuǎn),又或者說我們已經(jīng)這里進行了強轉(zhuǎn):
(T) findViewById(viewId);
這里需要說明一下观谦,當我們使用泛型的時候拉盾,我們可以使用TextView textView = (T)view,然后使用textView.setText()方法豁状,而不能直接使用(T)view.setText()捉偏,因為泛型并不知道你究竟要將這個父類轉(zhuǎn)換成具體的哪個子類,所以你無法直接使用子類所特有的方法泻红。所以我們需要用特有的子類去進行接收夭禽,接收后就可以使用其特有的方法了。
所以使用getView方法谊路,我們就能在formatViews()方法中獲取其對應(yīng)的控件了讹躯。至于為什么會有g(shù)etView(View view, int viewId),畢竟不是所有的情況下都可以直接使用findViewById()缠劝,至少在BaseAdapter的getView方法中潮梯,我們就難免使用到convertView.findViewById(),所以這里我也添加了一條:
return (T) view.findViewById(viewId);
當然惨恭,getView(View view, int viewId)放在demo中的Const工具類中也是可以的秉馏,又或者在我們的view只需要獲取一次的時候,我們也可以getView(View view, int viewId)方法進行進一步的封裝:
/**
* 簡化獲取View
*
* @param layoutId 父布局Id
* @param viewId View的ID
* @param <T> 將View轉(zhuǎn)化為對應(yīng)泛型脱羡,簡化強轉(zhuǎn)的步驟
* @return ID對應(yīng)的View
*/
@SuppressWarnings("unchecked")
public <T extends View> T getView(int layoutId, int viewId) {
return (T) LayoutInflater.from(context).inflate(layoutId, null).findViewById(viewId);
}
setOnClickListener
這個想必大家一眼就能看出來萝究,單純的就是一個點擊事件的設(shè)置,沒錯锉罐,它的功能同樣也是設(shè)置點擊事件帆竹,只是與普通的View.setOnClickListener()不同的是,這部分我是拿來批量設(shè)置點擊事件脓规。
首先栽连,需要我們的BaseActivity實現(xiàn)View.OnClickListener接口,不過onClick方法卻不必須實現(xiàn)侨舆,誰讓BaseActivity是抽象類呢升酣,只需要與上面那些抽象方法一樣,在繼承BaseActivity的子Activity中實現(xiàn)就好了态罪。
其次就是setOnClickListener的參數(shù)int... layouts,這個參數(shù)的意思就是傳入一個類型為int數(shù)組下面,并且這個數(shù)組的數(shù)量是不確定复颈,范圍是“大于等于0”,也就是說我們可以不傳值,也可以傳很多值耗啦,只要類型都是“...”前的即可凿菩。既然是數(shù)組,只需要對傳入的view id參數(shù)進行一個foreach循環(huán)設(shè)置點擊事件即可:
@Override
protected void formatViews() {
setOnClickListener(R.id.button1, R.id.button2, R.id.button3, R.id.button4)
}
我一般是在formatViews()方法中使用setOnClickListener的帜讲。
注意事項
由于網(wǎng)絡(luò)請求的部分我拆解出來放置在了BaseNetActivity中衅谷,所以當我們執(zhí)行網(wǎng)絡(luò)請求時,切記在onCreate之后執(zhí)行似将,如果在上述抽象方法的實現(xiàn)中執(zhí)行获黔,由于BaseNetActivity的onCreate方法要在BaseActivity的onCreate方法之后執(zhí)行,將會導(dǎo)致空指針的發(fā)生在验。
當然玷氏,這部分也可以在BaseNetActivity中添加一個網(wǎng)絡(luò)訪問的抽象方法,不過有很多Activity將會有重新獲取焦點時刷新頁面的需求腋舌,也就是網(wǎng)絡(luò)請求需要在onResume()方法中調(diào)用盏触,所以在BaseNetActivity中就沒有設(shè)置對應(yīng)的抽象方法,如果大家在使用過程中沒有這個需求块饺,不放BaseNetActivity中封裝一個網(wǎng)絡(luò)請求的方法赞辩,畢竟能偷懶何必那么勤快不是。