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

个人博客: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 static 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,我这个人有个好习惯,就是喜欢写注释,我注释写的很清楚,是干什么用的,我也衷心的希望,你能写好注释。

项目链接

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

海晨忆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值