從零開始搭建一個(gè)主流項(xiàng)目框架(一)—簡(jiǎn)單的框架

個(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)目。

清單文件.png

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

項(xiàng)目結(jié)構(gòu).png

??如上圖所示,首先把你的項(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的圖片

style.png

??下面有一個(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í)慣,就是喜歡寫注釋,我注釋寫的很清楚,是干什么用的,我也衷心的希望,你能寫好注釋。

項(xiàng)目鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容