個(gè)人博客:haichenyi.com。感謝關(guān)注
目的
??首先先說(shuō)出,最終的目的是現(xiàn)在主流的MVP+RxJava+Retrofit+OkHttp框架。讓大家心里有底
??開發(fā)工具Android Studio3.0,還在用eclipse的同鞋,強(qiáng)烈推薦你跨出這一步,你會(huì)發(fā)現(xiàn)一個(gè)新的世界。android studio都出來(lái)這么久了,你還在遠(yuǎn)古時(shí)代做開發(fā),說(shuō)句不好聽的,你完全與時(shí)代脫軌,你不適合做開發(fā)(純屬個(gè)人觀點(diǎn))
??本篇就只有三部分,第一部分就是新建一個(gè)Application,第二部分就是BaseActivity,第三部分就是BaseFragment
Application
??首先你得有application類,去初始化應(yīng)用只用初始化一次的內(nèi)容,繼承Application,然后在清單文件里面注冊(cè)。
package com.haichenyi.myproject;
import android.app.Application;
import com.squareup.leakcanary.LeakCanary;
/**
* Author: 海晨憶
* Date: 2018/2/23
* Desc:
*/
public class MyApplication extends Application {
private static MyApplication instance;
public MyApplication getInstance() {
return instance;
}
private void setInstance(MyApplication instance) {
MyApplication.instance = instance;
}
@Override
public void onCreate() {
super.onCreate();
setInstance(this);
initLeakCanary();
}
/**
* 初始化內(nèi)存檢測(cè)工具
*/
private void initLeakCanary() {
if (LeakCanary.isInAnalyzerProcess(this)) {
return;
}
LeakCanary.install(this);
}
}
??如上代碼,我這里就初始化了一個(gè)全局application單例對(duì)象,還初始化square公司出品的一個(gè)內(nèi)存檢測(cè)工具,用于檢測(cè)你項(xiàng)目中內(nèi)存泄漏情況。便于你優(yōu)化項(xiàng)目。

??如上圖所示,這個(gè)就是清單文件,在application結(jié)點(diǎn)下面,添加name標(biāo)簽,內(nèi)容就是你創(chuàng)建的application的名字。這里你還需要添加兩個(gè)內(nèi)存檢測(cè)的依賴。

??如上圖所示,首先把你的項(xiàng)目結(jié)構(gòu)視圖切換到Project,打開你的app目錄下的build.gradle文件,在dependencies結(jié)點(diǎn)下面(只要是添加開源庫(kù)都是在該結(jié)點(diǎn)下面,后面就不說(shuō)了),添加如下兩行代碼:
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.4'
??最后的1.5.4是版本號(hào),你可以在github上面搜索leakcanary,找最新的版本
BaseActivity
??創(chuàng)建基類BaseActivity,也就是所有Activity的父類。還有一個(gè)基類的接口BaseView,BaseActivity繼承剛才添加的依賴的SupportActivity類,實(shí)現(xiàn)BaseView接口,并且實(shí)現(xiàn)點(diǎn)擊事件的接口(選擇實(shí)現(xiàn),你要是不樂意在基類里面寫,你可以在你自己的子類里面重新實(shí)現(xiàn)一遍也是可以的)。代碼如下:每個(gè)方法注釋寫的很清楚,就不用一一解釋了
package com.haichenyi.myproject.base;
import android.app.AlertDialog;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.Window;
import android.widget.ProgressBar;
import com.haichenyi.myproject.utils.ToastUtils;
import me.yokeyword.fragmentation.SupportActivity;
/**
* Author: 海晨憶
* Date: 2018/2/23
* Desc:
*/
public abstract class BaseActivity extends SupportActivity implements BaseView {
private AlertDialog loadingDialog;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
/**
* Toast 提示用戶
* @param msg 提示內(nèi)容String
*/
@Override
public void showTipMsg(String msg) {
ToastUtils.showTipMsg(msg);
}
/**
* Toast 提示用戶
* @param msg 提示內(nèi)容res目錄下面的String的int值
*/
@Override
public void showTipMsg(int msg) {
ToastUtils.showTipMsg(msg);
}
/**
* 網(wǎng)絡(luò)請(qǐng)求的時(shí)候顯示正在加載的對(duì)話框
*/
@Override
public void showLoading() {
if (null == loadingDialog) {
loadingDialog = new AlertDialog.Builder(this).setView(new ProgressBar(this)).create();
loadingDialog.setCanceledOnTouchOutside(false);
Window window = loadingDialog.getWindow();
if (null != window) {
window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
}
}
if (!loadingDialog.isShowing()) {
loadingDialog.show();
}
}
/**
* 網(wǎng)絡(luò)請(qǐng)求完成時(shí)隱藏加載對(duì)話框
*/
@Override
public void hideLoading() {
if (null != loadingDialog) {
if (loadingDialog.isShowing()) {
loadingDialog.dismiss();
}
loadingDialog = null;
}
}
@Override
public void invalidToken() {
//用于檢測(cè)你當(dāng)前用戶的token是否有效,無(wú)效就返回登錄界面,具體的業(yè)務(wù)邏輯你自己實(shí)現(xiàn)
//如果需要做到實(shí)時(shí)檢測(cè),推薦用socket長(zhǎng)連接,每隔10秒發(fā)送一個(gè)驗(yàn)證當(dāng)前登錄用戶token是否過期的請(qǐng)求
}
/**
* Finish當(dāng)前頁(yè)面,最好實(shí)現(xiàn)onBackPressedSupport(),這個(gè)方法會(huì)有一個(gè)退棧操作,
* 開源框架實(shí)現(xiàn)的,我們不用管
*/
@Override
public void myFinish() {
onBackPressedSupport();
}
@Override
public void onBackPressedSupport() {
super.onBackPressedSupport();
}
}
??上面是目前BaseActivity代碼,注釋寫的很清楚,你會(huì)發(fā)現(xiàn)BaseView你并沒有,下面我給出BaseView的代碼
package com.haichenyi.myproject.base;
import android.support.annotation.StringRes;
/**
* Author: 海晨憶
* Date: 2018/2/23
* Desc:
*/
public interface BaseView {
void showTipMsg(String msg);
void showTipMsg(@StringRes int msg);
void showLoading();
void hideLoading();
void invalidToken();
void myFinish();
}
??BaseView就是一個(gè)接口,是所有V層的基類,代碼很簡(jiǎn)單,Toast方法,顯示隱藏加載的對(duì)話框方法,檢驗(yàn)token是否過期的方法,finish當(dāng)前頁(yè)面的方法。什么?Toast方法你沒有,下面我貼出來(lái)我的Toast的工具類
/**
* Author: 海晨憶.
* Date: 2017/12/21
* Desc: 實(shí)時(shí)更新的Toast工具類
*/
public final class ToastUtils {
private static Toast toast;
private ToastUtils() {
throw new RuntimeException("工具類不允許創(chuàng)建對(duì)象");
}
@SuppressWarnings("all")
private static void init() {
if (toast == null) {
toast = Toast.makeText(MyApplication.getInstance(), "", Toast.LENGTH_SHORT);
}
}
public static void showTipMsg(String msg) {
if (null == toast) {
init();
}
toast.setText(msg);
toast.show();
}
public static void showTipMsg(@StringRes int msg) {
if (null == toast) {
init();
}
toast.setText(msg);
toast.show();
}
}
??上面我貼出了三個(gè)類,這里我要說(shuō)明的是,我又創(chuàng)建了兩個(gè)package,一個(gè)是base,一個(gè)是utils,我把BaseActivity,BaseView,MyApplication放在base包下面,Toast的工具類放在utils包下面
??再就是添加一些常用的東西了,這里我沒有用黃油刀,用過一段時(shí)間之后,感覺他的每個(gè)控件都是全局的,有點(diǎn)占內(nèi)存,就放棄了。我下面貼出BaseActivity新增的偽代碼:
/**
* 保存當(dāng)前activity對(duì)象,在OnCreate里面添加,記得在OnDestroy里面移除
* 有什么用呢?
* 比方說(shuō)有一個(gè)需求,讓你在任意位置彈出對(duì)話框,彈對(duì)話框又需要一個(gè)context對(duì)象,這個(gè)時(shí)候,
* 你就只用傳當(dāng)前l(fā)ist的最上層的activity對(duì)象就可以了
* 當(dāng)然還有其他需求
*/
public static List<BaseActivity> activities = new ArrayList<>();
private Toolbar toolbar;
private TextView tvToolbarTitle;
private TextView tvToolbarRight;
private TextView tvBack;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
activities.add(this);
//強(qiáng)制豎屏(不強(qiáng)制加)
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
int layoutId = getLayoutId(savedInstanceState);
View inflate = getLayoutInflater().inflate(R.layout.activity_base, toolbar, false);
LinearLayout rootLinearLayout = inflate.findViewById(R.id.ll_layout_base_activity);
//沒有布局的時(shí)候傳0
if (0 == layoutId) {
setContentView(rootLinearLayout);
} else {
View rootView = getLayoutInflater().inflate(layoutId, rootLinearLayout, true);
setContentView(rootView);
}
stateBar();
initView();
initData();
setOnClick(R.id.tv_back_base_activity);
}
/**
* 設(shè)置點(diǎn)擊事件.
*
* @param ids 被點(diǎn)擊View的ID
* @return {@link BaseActivity}
*/
public BaseActivity setOnClick(@IdRes int... ids) {
View view;
for (int id : ids) {
view = findViewById(id);
if (null != view) {
view.setOnClickListener(this);
}
}
return this;
}
/**
* 設(shè)置點(diǎn)擊事件.
*
* @param views 被點(diǎn)擊View
* @return {@link BaseActivity}
*/
public BaseActivity setOnClick(View... views) {
for (View view : views) {
view.setOnClickListener(this);
}
return this;
}
/**
* 獲取當(dāng)前布局對(duì)象
*
* @param savedInstanceState 這個(gè)是當(dāng)前activity保存的數(shù)據(jù),最常見的就是橫豎屏切換的時(shí)候,
* 數(shù)據(jù)丟失問題
* @return 當(dāng)前布局的int值
*/
protected abstract int getLayoutId(Bundle savedInstanceState);
@Override
protected void onDestroy() {
activities.remove(this);
super.onDestroy();
}
protected void initData() {
}
protected void initView() {
toolbar = findViewById(R.id.toolbar_base_activity);
tvToolbarTitle = findViewById(R.id.tv_title_base_activity);
tvToolbarRight = findViewById(R.id.tv_right_base_activity);
}
/**
* 設(shè)置狀態(tài)欄背景顏色,不能改變狀態(tài)欄內(nèi)容的顏色
*/
private void stateBar() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
SystemBarTintManager tintManager = new SystemBarTintManager(this);
tintManager.setStatusBarTintEnabled(true);
tintManager.setNavigationBarTintEnabled(true);
tintManager.setTintColor(Color.parseColor("#000000"));
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.tv_back_base_activity:
onBackPressedSupport();
break;
default:
break;
}
}
??這里我需要說(shuō)明的是,新增了一個(gè)開源框架,就是設(shè)置狀態(tài)欄背景顏色的systembartint。
implementation 'com.readystatesoftware.systembartint:systembartint:1.0.3'
??再就是設(shè)置activity標(biāo)題內(nèi)容,左邊,右邊的內(nèi)容,左邊右邊可能是文字,也可能是圖片。所以,我在用的時(shí)候,都是用的TextView,ImageView,不能設(shè)置文字。方法如下:
public BaseActivity setTitles(CharSequence title) {
tvToolbarTitle.setText(title);
return this;
}
/**
* 初始化toolbar的內(nèi)容
* @param isShowToolbar 是否顯示toolbar
* @param isShowBack 是否顯示左邊的TextView
* @param isShowMore 是否顯示右邊的TextView
* @return 當(dāng)前activity對(duì)象,可以連點(diǎn)
*/
protected BaseActivity initToolbar(boolean isShowToolbar, boolean isShowBack,
boolean isShowMore) {
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if (null != actionBar) {
if (isShowToolbar) {
actionBar.show();
tvBack = findViewById(R.id.tv_back_base_activity);
TextView textView = findViewById(R.id.tv_right_base_activity);
if (null != tvBack && null != textView) {
tvBack.setVisibility(isShowBack ? View.VISIBLE : View.INVISIBLE);
textView.setVisibility(isShowMore ? View.VISIBLE : View.INVISIBLE);
}
} else {
actionBar.hide();
}
}
return this;
}
public BaseActivity setToolbarBack(int colorId) {
toolbar.setBackgroundColor(getResources().getColor(colorId));
return this;
}
@SuppressWarnings("unused")
public BaseActivity setMyTitle(String title) {
tvToolbarTitle.setText(title);
return this;
}
public BaseActivity setMyTitle(@StringRes int stringId) {
tvToolbarTitle.setText(stringId);
return this;
}
public void setMoreTitle(String moreTitle) {
tvToolbarRight.setText(moreTitle);
}
public BaseActivity setMoreTitle(@StringRes int stringId) {
tvToolbarRight.setText(stringId);
return this;
}
/**
* 設(shè)置左邊內(nèi)容.
*
* @param leftTitle 內(nèi)容
* @return {@link BaseActivity}
*/
public BaseActivity setLeftTitle(String leftTitle) {
if (tvBack != null) {
tvBack.setBackground(null);
tvBack.setText(leftTitle);
}
return this;
}
/**
* 設(shè)置左邊內(nèi)容.
*
* @param leftTitle 內(nèi)容
*/
public void setLeftTitle(@StringRes int leftTitle) {
if (tvBack != null) {
tvBack.setBackground(null);
tvBack.setText(leftTitle);
}
}
@SuppressWarnings("unused")
protected BaseActivity setMoreBackground(int resId) {
tvToolbarRight.setBackgroundResource(resId);
return this;
}
??可以看到上面的方法返回值都是BaseActivity,這樣做的目的就只有一個(gè),可以連點(diǎn),寫一個(gè)方法之后,可以接著點(diǎn)寫下一個(gè)方法,不用寫一個(gè)方法就要加分號(hào),就換一行寫下一個(gè)方法。
??還要加一句,在你的app主題里面添加兩個(gè)item,也就是你的res目錄下面的style:
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
??我這里貼出我目前的style的圖片

??下面有一個(gè)LineHorizontal樣式,就是你toolbar下面的那個(gè)橫線
BaseFragment
??BaseFragment跟BaseActivity的邏輯是差不多的,我這里就貼出代碼
package com.haichenyi.myproject.base;
import android.os.Bundle;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.haichenyi.myproject.utils.ToastUtils;
import me.yokeyword.fragmentation.SupportFragment;
/**
* Author: 海晨憶
* Date: 2018/2/23
* Desc:
*/
public abstract class BaseFragment extends SupportFragment implements BaseView,
View.OnClickListener {
protected boolean isInit;
private View rootView;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
int layoutRes = layoutRes();
if (0 != layoutRes) {
return inflater.inflate(layoutRes, null);
} else {
return super.onCreateView(inflater, container, savedInstanceState);
}
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
rootView = view;
}
@Override
public void onLazyInitView(@Nullable Bundle savedInstanceState) {
super.onLazyInitView(savedInstanceState);
isInit = true;
init();
}
protected <T extends View> T findViewById(@IdRes int id) {
return rootView.findViewById(id);
}
/**
* 設(shè)置點(diǎn)擊事件.
*
* @param ids 被點(diǎn)擊View的ID
* @return {@link BaseFragment}
*/
public BaseFragment setOnClick(@IdRes int... ids) {
for (int id : ids) {
rootView.findViewById(id).setOnClickListener(this);
}
return this;
}
/**
* 設(shè)置點(diǎn)擊事件.
*
* @param views 被點(diǎn)擊View的ID
* @return {@link BaseFragment}
*/
public BaseFragment setOnClick(View... views) {
for (View view : views) {
view.setOnClickListener(this);
}
return this;
}
protected abstract void init();
@Override
public void onDestroy() {
rootView = null;
super.onDestroy();
}
protected abstract int layoutRes();
@Override
public void showTipMsg(String msg) {
ToastUtils.showTipMsg(msg);
}
@Override
public void showTipMsg(int msg) {
ToastUtils.showTipMsg(msg);
}
@Override
public void showLoading() {
BaseActivity activity = (BaseActivity) getActivity();
/*if (activity instanceof BaseMvpActivity) {
activity.showLoading();
}*/
}
@Override
public void hideLoading() {
BaseActivity activity = (BaseActivity) getActivity();
/*if (activity instanceof BaseMvpActivity) {
activity.hideLoading();
}*/
}
@Override
public void invalidToken() {
BaseActivity activity = (BaseActivity) getActivity();
/*if (activity instanceof BaseMvpActivity) {
activity.invalidToken();
}*/
}
@Override
public void onClick(View v) {
}
@Override
public void myFinish() {
onBackPressedSupport();
}
}
??兩者在布局抽象方法里面有一點(diǎn)區(qū)別,Activity的傳了Boundle參數(shù),F(xiàn)ragment沒有傳,因?yàn)镕ragment可以通過getArguments()方法獲取到這個(gè)對(duì)象,而Activity不能獲取到。
總結(jié)
??到此,一個(gè)簡(jiǎn)單的項(xiàng)目框架就出來(lái)了,目前還是框架的第一步,是一個(gè)雛形,還不包括MVP,dagger等等,下一篇就加上MVP,我這個(gè)人有個(gè)好習(xí)慣,就是喜歡寫注釋,我注釋寫的很清楚,是干什么用的,我也衷心的希望,你能寫好注釋。