从零开始搭建一个主流项目框架(一)—简单的框架

个人博客:haichenyi.com。感谢关注

目的

  首先先说出,最终的目的是现在主流的MVP+RxJava+Retrofit+OkHttp框架。让大家心里有底

  开发工具Android Studio3.0,还在用eclipse的同鞋,强烈推荐你跨出这一步,你会发现一个新的世界。android studio都出来这么久了,你还在远古时代做开发,说句不好听的,你完全与时代脱轨,你不适合做开发(纯属个人观点)

  本篇就只有三部分,第一部分就是新建一个Application,第二部分就是BaseActivity,第三部分就是BaseFragment

Application

  首先你得有application类,去初始化应用只用初始化一次的内容,继承Application,然后在清单文件里面注册。

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();
  }

  /**
   * 初始化内存检测工具
   */
  private void initLeakCanary() {
    if (LeakCanary.isInAnalyzerProcess(this)) {
      return;
    }
    LeakCanary.install(this);
  }
}

  如上代码,我这里就初始化了一个全局application单例对象,还初始化square公司出品的一个内存检测工具,用于检测你项目中内存泄漏情况。便于你优化项目。

清单文件.png

  如上图所示,这个就是清单文件,在application结点下面,添加name标签,内容就是你创建的application的名字。这里你还需要添加两个内存检测的依赖。

项目结构.png

  如上图所示,首先把你的项目结构视图切换到Project,打开你的app目录下的build.gradle文件,在dependencies结点下面(只要是添加开源库都是在该结点下面,后面就不说了),添加如下两行代码:

releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.4'

  最后的1.5.4是版本号,你可以在github上面搜索leakcanary,找最新的版本

BaseActivity

  创建基类BaseActivity,也就是所有Activity的父类。还有一个基类的接口BaseView,BaseActivity继承刚才添加的依赖的SupportActivity类,实现BaseView接口,并且实现点击事件的接口(选择实现,你要是不乐意在基类里面写,你可以在你自己的子类里面重新实现一遍也是可以的)。代码如下:每个方法注释写的很清楚,就不用一一解释了

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 提示内容String
   */
  @Override
  public void showTipMsg(String msg) {
    ToastUtils.showTipMsg(msg);
  }

  /**
   * Toast 提示用户
   * @param msg 提示内容res目录下面的String的int值
   */
  @Override
  public void showTipMsg(int msg) {
    ToastUtils.showTipMsg(msg);
  }

  /**
   * 网络请求的时候显示正在加载的对话框
   */
  @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();
    }
  }

  /**
   * 网络请求完成时隐藏加载对话框
   */
  @Override
  public void hideLoading() {
    if (null != loadingDialog) {
      if (loadingDialog.isShowing()) {
        loadingDialog.dismiss();
      }
      loadingDialog = null;
    }
  }

  @Override
  public void invalidToken() {
    //用于检测你当前用户的token是否有效,无效就返回登录界面,具体的业务逻辑你自己实现
    //如果需要做到实时检测,推荐用socket长连接,每隔10秒发送一个验证当前登录用户token是否过期的请求
  }

  /**
   * Finish当前页面,最好实现onBackPressedSupport(),这个方法会有一个退栈操作,
   * 开源框架实现的,我们不用管
   */
  @Override
  public void myFinish() {
    onBackPressedSupport();
  }

  @Override
  public void onBackPressedSupport() {
    super.onBackPressedSupport();
  }
}

  上面是目前BaseActivity代码,注释写的很清楚,你会发现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就是一个接口,是所有V层的基类,代码很简单,Toast方法,显示隐藏加载的对话框方法,检验token是否过期的方法,finish当前页面的方法。什么?Toast方法你没有,下面我贴出来我的Toast的工具类

/**
 * Author: 海晨忆.
 * Date: 2017/12/21
 * Desc: 实时更新的Toast工具类
 */
public final class ToastUtils {
  private static Toast toast;

  private ToastUtils() {
    throw new RuntimeException("工具类不允许创建对象");
  }

  @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();
  }
}

  上面我贴出了三个类,这里我要说明的是,我又创建了两个package,一个是base,一个是utils,我把BaseActivity,BaseView,MyApplication放在base包下面,Toast的工具类放在utils包下面

  再就是添加一些常用的东西了,这里我没有用黄油刀,用过一段时间之后,感觉他的每个控件都是全局的,有点占内存,就放弃了。我下面贴出BaseActivity新增的伪代码:

/**
   * 保存当前activity对象,在OnCreate里面添加,记得在OnDestroy里面移除
   * 有什么用呢?
   * 比方说有一个需求,让你在任意位置弹出对话框,弹对话框又需要一个context对象,这个时候,
   * 你就只用传当前list的最上层的activity对象就可以了
   * 当然还有其他需求
   */
  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);
    //强制竖屏(不强制加)
    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);
    //没有布局的时候传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);
  }

  /**
   * 设置点击事件.
   *
   * @param ids 被点击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;
  }

  /**
   * 设置点击事件.
   *
   * @param views 被点击View
   * @return {@link BaseActivity}
   */
  public BaseActivity setOnClick(View... views) {
    for (View view : views) {
      view.setOnClickListener(this);
    }
    return this;
  }

  /**
   * 获取当前布局对象
   *
   * @param savedInstanceState 这个是当前activity保存的数据,最常见的就是横竖屏切换的时候,
   *                           数据丢失问题
   * @return 当前布局的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);
  }

  /**
   * 设置状态栏背景颜色,不能改变状态栏内容的颜色
   */
  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;
    }
  }

  这里我需要说明的是,新增了一个开源框架,就是设置状态栏背景颜色的systembartint

implementation 'com.readystatesoftware.systembartint:systembartint:1.0.3'

  再就是设置activity标题内容,左边,右边的内容,左边右边可能是文字,也可能是图片。所以,我在用的时候,都是用的TextView,ImageView,不能设置文字。方法如下:

public BaseActivity setTitles(CharSequence title) {
    tvToolbarTitle.setText(title);
    return this;
  }

/**
   * 初始化toolbar的内容
   * @param isShowToolbar 是否显示toolbar
   * @param isShowBack 是否显示左边的TextView
   * @param isShowMore 是否显示右边的TextView
   * @return 当前activity对象,可以连点
   */
  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;
  }

  /**
   * 设置左边内容.
   *
   * @param leftTitle 内容
   * @return {@link BaseActivity}
   */
  public BaseActivity setLeftTitle(String leftTitle) {
    if (tvBack != null) {
      tvBack.setBackground(null);
      tvBack.setText(leftTitle);
    }
    return this;
  }

  /**
   * 设置左边内容.
   *
   * @param leftTitle 内容
   */
  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,这样做的目的就只有一个,可以连点,写一个方法之后,可以接着点写下一个方法,不用写一个方法就要加分号,就换一行写下一个方法。

  还要加一句,在你的app主题里面添加两个item,也就是你的res目录下面的style:

<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>

  我这里贴出我目前的style的图片

style.png

  下面有一个LineHorizontal样式,就是你toolbar下面的那个横线

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);
  }

  /**
   * 设置点击事件.
   *
   * @param ids 被点击View的ID
   * @return {@link BaseFragment}
   */
  public BaseFragment setOnClick(@IdRes int... ids) {
    for (int id : ids) {
      rootView.findViewById(id).setOnClickListener(this);
    }
    return this;
  }

  /**
   * 设置点击事件.
   *
   * @param views 被点击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();
  }
}

  两者在布局抽象方法里面有一点区别,Activity的传了Boundle参数,Fragment没有传,因为Fragment可以通过getArguments()方法获取到这个对象,而Activity不能获取到。

总结

  到此,一个简单的项目框架就出来了,目前还是框架的第一步,是一个雏形,还不包括MVP,dagger等等,下一篇就加上MVP,我这个人有个好习惯,就是喜欢写注释,我注释写的很清楚,是干什么用的,我也衷心的希望,你能写好注释。

项目链接

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容