《一个Android工程的从零开始》-5、base(四) BaseActivity——方法封装

来源:互联网 发布:mac上查找我的iphone 编辑:程序博客网 时间:2024/06/06 20:38

先扯两句

昨天发了一篇GitHub版本控制的集成后,今天终于回归正事,继续我们的《一个Android工程的从零开始》,真心有点小开心呢。
今天也是base的BaseActivity完结掉了,昨天我也查了一下其他人的BaseActivity封装,发现却比我的篇幅少了不少,不过既然要从基础说起,自然废话也就多了一些,请大家见谅。
既然昨天已经发了GitHub的版本控制,那么这篇开始就发GitHub的链接了,码云的就暂停更新维护了,另外onstraintLayout的也相差不多,只是布局的部分不同,但是由于很少有用代码部分完成的,这里就不予以展示了。

[MyBaseApplication] (https://github.com/BanShouWeng/MyBaseApplication)

正文

本次的内容总共有如下四点:

  1. 常用常量、变量;
  2. 界面跳转;
  3. 网络监听;
  4. 提示框。

这个顺序很显然是本着我个人一贯偷懒成瘾的套路排列的,不过这不重要,我们还是开始今天的内容吧。

常用常量、变量

这个部分的内容放在最简单的位置上,其实主要还是由于我所能写的东西有限,但实际上就那四点而言,这个部分是最复杂的,因为不同的工程项目,对这部分的需求往往是更不相同的,我们也无法一概而论的给出一个具体的实现方式,所以就再此做一个简要的分析。
这部分如果做简单分类呢,就是两个点:其一是用户相关;其二是编程相关

  1. 用户相关
    用户相关的东西,在这里主要是一些常用的信息,例如用户id、用户联系方式、用户头像等等。这部分的做法有两种,其一,这些信息分别存储,用的时候直接调用;其二创建一个用户类,将常用的用户信息存储起来,在后面需要的时候,在用户类中去对应取值。
    原因是,一般而言,我们会将用户信息存储起来,当然,也就是有两种方式,服务器存储、数据库存储。在我们后期使用用户信息的时候,基本都是从这三种途径获取,必不可少的,自然数服务器存储,但是如果每次我们使用,都要去请求一边服务器,是很浪费资源,而且完全没必要的。所以一般情况下(个别安全性严谨的除外),都是只有第一次登录账户的时候,获取一下个人的信息,存储在本地,用的时候直接调用。而至于在本地是使用数据库存储,或许有人比较喜欢SharedPreference,不过个人不建议,毕竟存储的数据量比较大。
    但无论使用哪种,繁复访问数据库也是一个很耗时的操作,所以很容易影响到用户的体验,所以这个时候就可以选择在BaseActivity中保留一个用户信息的部分,方便之后使用,不过个人建议保留一些常用的即可,没必要将整个用户信息都暴露出来。
    由于项目不同,所需也不尽相同,这部分就并予以展示了。
  2. 编程相关
    这部分呢,我主推Context (也就是传说中的上下文),创建一个Context 的共有变量,在onCreate中赋值
public Context context;public Activity activity;//在onCreate中context = getApplicationContext();activity = this;

大家看到这部分可能或疑惑,毕竟关于BaseActivity的封装,也不是我一个人在写,之前看到的或许在BaseActivity中就不存在context,或者是使用的Activity。而无论是使用的Context还是Activity的,在onCreate中,也不过是使用的context = this;为什么到我这里却非要图个不一样,一定多要写一个context = getApplicationContext(),我们分开来阐述。

  1. 为什么定义Context
    这个部分,最开始呢,我只是为了与BaseFragment相统一,方便后续编码,至于原因,就先剧透一下了,那就是在Fragment中使用getActivity()方法,容易导致空指针异常,至于原因,我们就先买个关子,到了BaseFragment中,再为大家剖析。
    偷懒的解决方法就是在BaseFragment中获取一个上下文信息,后面需要使用的时候直接调用就可以,所以为了代码统一,在BaseActivity中就定义了一个,而且还是那句话,毕竟我们可以偷懒啊,不用每次getContext或者getApplicationContext来获取上下文了。随着查资料,我有发现了一个原因,就是为什么我写了一个Context的同时又写了一个Activity。
  2. 为什么要多出来一个context = getApplicationContext();
    先需要说明的是,其实Application与Activity都是Context的子类,下面开始正是解释。
    getApplicationContext主要的用处就是防止内存泄漏,尤其是一些静态的类需要持有context的时候,如果我们传入了Activity,那么这个静态类没有被销毁之前,对应的Activity也不会被销毁,这样就会导致内存泄漏,而getApplicationContext得到的是整个应用的Context,也就是说,只要Application还在,那么这个Context对象就一直在,哪怕是被静态或者其他可能会导致内存泄漏的方法或者类持有,也不会导致内存泄漏。
    但是,如果完全使用getApplicationContext就可以吗?你可以试试,知道遇到“Caused by: java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.”这个错误。
    这是在使用AlerDialog的时候,出现的错误,我查到的是如下原因,所以对应这部分我们就要使用activity,也就是this。

在语句 AlertDialog.Builder builder = new AlertDialog.Builder(this); 中,要求传递的 参数就是一个context,在这里传入的是this,那么这个this究竟指的是什么东东呢? 这里的this指的是Activity.this,是这个语句所在的Activity的this,是这个Activity 的上下文。网上有很多朋友在这里传入this.getApplicationContext(),这是不对的。 AlertDialog对象是依赖于一个View的,而View是和一个Activity对应的。 于是,这里涉及到一个生命周期的问题,this.getApplicationContext()取的是这个应 用程序的Context,Activity.this取的是这个Activity的Context,这两者的生命周期是不同 的,前者的生命周期是整个应用,后者的生命周期只是它所在的Activity。而AlertDialog应 该是属于一个Activity的,在Activity销毁的时候它也就销毁了,不会再存在;但是,如果传 入this.getApplicationContext(),就表示它的生命周期是整个应用程序,这显然超过了它 的生命周期了。 所以,在这里只能使用Activity的this。

所以,暂且理解为控件相关就使用activity,方法相关就使用context。不过如果实在难以区分的情况下,对与内存泄漏要求不严格,可是只使用activity,遇到内存泄漏时再替换成getApplicationContext,不过相对于这种解决方式,我还是比较倾向于,两个都创建,先使用context,直接报错的情况下,再换成activity。
再其他的与编程相关的就是一些数据库的引用、SharedPreference的引用,还有就是常量,大多数在BroadCast Reciever中使用的不较多。

Tips

这部分,除了activity与context的部分,其他均建议在utils目录下创建一个Const类,将这些存入其中调用,以减轻BaseActivity。

界面跳转

这部分说白了就是startActivity和startActivityForResult的封装,在Android开发中,这两个方法的使用频率就不用我多说了,那是相当多啊,所以发挥我一贯偷懒的准则,这么能忍每次都要创建Intent这么麻烦事呢。
所以封装了如下两个方法:

    /**     * 跳转页面     *     * @param clz 所跳转的目的Activity类     */    public void startActivity(Class<?> clz) {        startActivity(new Intent(this, clz));    }    /**     * 跳转页面     *     * @param clz         所跳转的Activity类     * @param requestCode 请求码     */    public void startActivityForResult(Class<?> clz, int requestCode) {        startActivityForResult(new Intent(this, clz), requestCode);    }

这样,我们在使用的时候,就可以传递目标Activity.class就可以实现跳转页面的目的了。
当然,我们除了这么跳转之外,还会有需要传参的时候,对于这部分内容,对于这部分,我们可以使用Bundle来完成,方法如下:

    /**     * 跳转页面     *     * @param clz    所跳转的目的Activity类     * @param bundle 跳转所携带的信息     */    public void startActivity(Class<?> clz, Bundle bundle) {        Intent intent = new Intent(this, clz);        if (bundle != null) {            intent.putExtra("bundle", bundle);        }        startActivity(intent);    }    /**     * 跳转页面     *     * @param clz         所跳转的Activity类     * @param bundle      跳转所携带的信息     * @param requestCode 请求码     */    public void startActivityForResult(Class<?> clz, int requestCode, Bundle bundle) {        Intent intent = new Intent(this, clz);        if (bundle != null) {            intent.putExtra("bundle", bundle);        }        startActivityForResult(intent, requestCode);    }

当然,大家如果认为麻烦的话,传参的部分直接使用Intent的方法也是可以的,毕竟使用Bundle就代码量上而言,并没有得到实质性的优化。

除此之外,关于跳转页面还有一些运用。

在工程中,经常会出现退出程序,或者异地登录的情况,出现这种情况的时候,就需要将当前的所打开的Activity中除MainActivity外的其他Activity都关闭。
还有就是,完成一些列可回退的操作后,需要一次性关闭掉过程中所打开的所有Activity。
以上这些操作就需要我们使用下面的部分做处理。
首先需要创建一个List存储我们过程中打开的所有Activity,并在onCreate中添加到List中。

private static List<Activity> activities = new ArrayList<>();//在onCreate添加if (!(this instanceof MainActivity)) {      activities.add(this);}//在onDestroy添加,防止空指针或者内存泄漏activities.remove(this);

可能大家会发现上面我使用了一个if判断,其作用就是防止MainActivity被添加到列表中,便于后面的运用,因为大多数的app在彻底退出app之前很少有会将MainActivity关闭的,这样就可以便于后面的运用,防止将所有的Activity都关闭了导致程序都退出了。
再就是运用部分了,首先是所有都关闭,这个比较简单,就是遍历一边,挨个关闭就可以

    /**     * 关闭所有Activity(除MainActivity以外)     */    public void finishActivity() {        for (Activity activity : activities) {            activity.finish();        }    }

可能有人会疑惑,这样关闭就不需要将被关闭的Activity从Activity集合中移除吗?这点大家可以放心,因为finish方法执行的时候,会执行对应Activity的onDestroy方法,因此,就不需要额外添加remove方法了。
最后就是跳转到指定的以打开Activity,并将其堆栈上方的Activity全关闭的方法

/**     * 跳转到指定的Activity     *     * @param clz 指定的Activity对应的class     */    public void goTo(Class<?> clz) {        if (clz.equals(MainActivity.class)) {            finishActivity();        } else {            for (int i = activities.size() - 1; i >= 0; i--) {                if (clz.equals(activities.get(i).getClass())) {                    break;                } else {                    activities.get(i).finish();                }            }        }    }

判断部分是看,是否是跳转到MainActivity,如果是,那么就将所有的都关闭即可,也就是调用finishActivity方法即可,当跳转的不是MainActivity的时候,就要遍历一下List了,for循环从i–的目的就是从上而下清理堆栈,到达我们想要跳转的目标Activity时,跳出循环即可。
而如果使用过程中,你发现,不是MainActivity却跳转到了MainActivity,那么恭喜你,你填写的Activity并不在List内,这样也是一个避免bug的检验。

网络监听

想必大家在使用APP的时候,都体会过,在忽然断网的时候,自己还没反应到,APP就已经提醒你了,现在断网了。
或者是当看视频到一半,忽然提示你,wifi断了,当前使用的是流量是否继续观看。
还有就是我之前在工作中遇到的,实时显示出当前所连wifi的wifi名。
所以无论上面的哪种情况,都需要我们对网络做一个监听,而这部分内容,十分感谢mxiaoyem的Android 实时监测(监听)网络连接状态变化帮了个大忙,按照其博客所说的内容就可以实现,这里我就不加以赘述了,在我的git上也能找到具体的应用。
只是其中有一个地方需要大家注意一下,那就是一定要在AndroidMenifest.xml中注册你所创建的NetBroadcastReceiver。当然,也可以直接复制博客中所写的内容,但是需要注意的一点是,我们的包或许与作者的不同,所以需要做修改,不然会报红,方法很简单,如下图:
这里写图片描述

找到对应的NetBroadcastReceiver回车即可。

提示框

这部分呢,一共分为了两部分,分别是:1、Toast;2、网络加载提示框。

1、Toast
Toast是简单的文本提示的时候使用,封装起来也很简单,直接上代码了。

    /**     * 消息提示框     *     * @param message 提示消息文本     */    public void showToast(String message) {        Toast.makeText(context, message, Toast.LENGTH_SHORT).show();    }/**     * 消息提示框     *     * @param messageId 提示消息文本ID     */    public void showToast(int messageId) {        Toast.makeText(context, messageId, Toast.LENGTH_SHORT).show();    }

以上两个方法,对应的分别是,直接传递字符串和传递string.xml中的string对应的id。

2、网络加载

这部分实际上就是一个网络加载的ProgressBar,但是为了方便使用,所以嵌套在了ProgressDialog内。
懂我套路的一定会猜到,我又该提出感谢了,哈哈哈。十分感谢哇牛Aaron的Android progressdialog自定义背景透明的圆形进度条类似于Dialog帮了个大忙。当然这部分我就没有上面网络监听部分那么偷懒了,而是做了一定的简化,只要了实现我预期功能的部分,其他的内容暂时没有添加到项目中,如果大家也只是想实现一个效果的话,可以看一下我下面贴出来的代码,如果想深入的了解一下的话,可以看一下上面对应的这篇博客。
先上一下效果图:
这里写图片描述

没错,就是中间的那个圈,至于那个一如既往的丑的背景,辛苦大家暂且忍受一下。
首先是一个ProgressDialog的布局文件:

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:background="@android:color/transparent">    <ProgressBar        style="@style/progress_dialog_loading"        android:id="@+id/progress_progress"        android:indeterminateDrawable="@drawable/progressbar_load"        android:layout_width="48dp"        android:layout_height="48dp"        android:padding="3dp"        android:layout_centerInParent="true"        android:visibility="visible" />    <TextView        android:id="@+id/progress_text"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_below="@+id/progress_progress"        android:layout_centerHorizontal="true"        android:layout_marginTop="@dimen/size_17"        android:textSize="@dimen/size_17" /></RelativeLayout>

先说一下比较次要的TextView吧,它的功能主要就是一个文字提示,例如比较常见的“玩命加载中。。。”之类的,只要根据id找到控件,对应setText就可以了。
再就是我们主体ProgressBar,其中有两个属性之前博客没提到过:1、style,就是一个设计风格,也是一个比较常用的属性,对应内容下面会贴出;2、android:indeterminateDrawable,设置的是非进度条(也就是那个圈)的动画Drawable,代码后面也会贴出来。
贴代码喽。
style(里面只有一条,就是背景透明,这个style很快在后面会再用到):

<style name="progress_dialog_loading" parent="@android:style/Theme.Dialog">        <item name="android:windowBackground">@android:color/transparent</item>    </style>

然后是Drawable对应的动画xml:

<?xml version="1.0" encoding="utf-8"?><rotate xmlns:android="http://schemas.android.com/apk/res/android"    android:fromDegrees="0"    android:pivotX="50%"    android:pivotY="50%"    android:toDegrees="360" >    <!-- 这里画了一个灰色的环形 -->    <shape        android:innerRadiusRatio="3"        android:shape="ring"        android:thicknessRatio="8"        android:useLevel="false" >        <!-- 径向渐变 -->        <gradient            android:centerColor="#ffffff"            android:centerX="1.0"            android:centerY="1.0"            android:endColor="@android:color/darker_gray"            android:gradientRadius="90"            android:startColor="@android:color/darker_gray"            android:type="radial"            android:useLevel="false" />    </shape></rotate>

drawable的主要内容就是绘制一个环,背景白色,渐变角度90°,开始与结束都设置成了深灰色。
以上几部分配合,就完成了我们的ProgressDialog的布局内容,当然,当然,如果大家看了上面的 哇牛Aaron 的博客就会发现,我做的化简只要将style中的属性做了删减。
下面就该进行下一部分了,就是ProgressDialog的自定义,还是先上打码在分析:

package com.mybaseapplication.widget;import android.app.ProgressDialog;import android.content.Context;import android.os.Bundle;import android.widget.TextView;import com.mybaseapplication.R;public class CustomProgressDialog extends ProgressDialog {    private String message = "";    public CustomProgressDialog(Context context, int theme) {        super(context, theme);    }    public CustomProgressDialog(Context context, int theme,                                String message) {        super(context, theme);        this.message = message;    }    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.laod_progressbar_layout);        //dialog弹出后点击物理返回键Dialog消失,但是点击屏幕不会消失        this.setCanceledOnTouchOutside(false);        //dialog弹出后点击屏幕或物理返回键,dialog都不消失        //this.setCancelable(false);        if (message != null){//message不为空,则设置            ((TextView)findViewById(R.id.progress_text)).setText(message);        }    }}

其中大家可以看到,我使用了两个构造方法,一个是:

public CustomProgressDialog(Context context, int theme)

另一个是:

public CustomProgressDialog(Context context, int theme,                                String message) 

先说一下共通的参数,context上下文信息,这个是每个控件都需要的一个参数,与当前的Activity绑定在一起,也就是前面所说的activity参数,而不能使用context。
theme主题,也就是前面我们定义的style,用途还是将ProgressDialog的背景设置为透明,如果不设置这个style的话,那么出来的就会有白色的背景,即便你将ProgressDialog解析的布局文件的背景和ProgressBar的背景都设置成了透明也一样。错误效果如图:

这里写图片描述

而第三个参数message,就是我们想要显示的文本,类似“玩命加载中。。。”通过构造方法传入进来,就可以显示了,如图:
这里写图片描述

然后是在BaseActivity中如何调用,第一步先创建一个私有的CustomProgressDialog

    /**     * 加载提示框     */    private CustomProgressDialog customProgressDialog;

第二步,在onCreate中实例化(也就是new 一下):

//不传文本customProgressDialog = new CustomProgressDialog(activity, R.style.progress_dialog_loading);//传递文本customProgressDialog = new CustomProgressDialog(activity, R.style.progress_dialog_loading, "玩命加载中。。。");

再就是显示与隐藏的方法了。

    /**     * 显示加载提示框     */    public void showLoadDialog() {        customProgressDialog.show();    }    /**     * 隐藏加载提示框     */    public void hideLoadDialog() {        if (customProgressDialog != null && customProgressDialog.isShowing()) {            customProgressDialog.dismiss();        }    }

不过这里我们使用的是直接显示的或隐藏,如果在主线程中如此使用,自然没有问题。也能正常显示,就如同我上面截图一样,但是如果你想把显示或者隐藏的方法放在子线程中使用的话,这两个方法就没法起到作用了。
原因参见WongWoo1991的子线程中progress不显示问题,里面也给出了解决方法,那就是将ProgressDialog的操作嵌套在runOnUiThread内,这样就可以使其正常运行了,修改后的方法如下:

    /**     * 显示加载提示框     */    public void showLoadDialog() {        runOnUiThread(new Runnable() {            @Override            public void run() {                customProgressDialog.show();            }        });    }    /**     * 隐藏加载提示框     */    public void hideLoadDialog() {        runOnUiThread(new Runnable() {            @Override            public void run() {                if (customProgressDialog != null && customProgressDialog.isShowing()) {                    customProgressDialog.dismiss();                }            }        });    }

如此,这篇博客的内容终于都写完了,BaseActivity的封装到此也就结束了,当然还有一些方法,例如Log日志输出,还有一些工具类的创建,不过就像是前面所说第一部分常用常量、变量中所说的一样,这些方法还是建议在Utils包下创建Const完成,免得BaseActivity中内容太过冗杂。
还有就是一些数据库访问的或者全局SharedPreference的访问接口,也可以放在BaseActivity中,但是这部分比较复杂,在对应使用的地方再加以说明。
以上内容均为我个人的粗浅理解,大家如果发现问题或者可以添加的部分,可以留言,大家一起加油进步哦。

附录

《一个Android工程的从零开始》目录

阅读全文
0 0