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

先扯两句

昨天发了一篇GitHub版本控制的集成后,今天终于回归正事,继续我们的《一个Android工程的从零开始》,真心有点小开心呢。

今天也是base的BaseActivity完结掉了,昨天我也查了一下其他人的BaseActivity封装,发现却比我的篇幅少了不少,不过既然要从基础说起,自然废话也就多了一些,请大家见谅。

既然昨天已经发了GitHub的版本控制,那么这篇开始就发GitHub的链接了,码云的就暂停更新维护了,另外onstraintLayout的也相差不多,只是布局的部分不同,但是由于很少有用代码部分完成的,这里就不予以展示了。

正文

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

常用常量、变量;

界面跳转;

网络监听;

提示框。

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

常用常量、变量

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

这部分如果做简单分类呢,就是两个点:其一是用户相关;其二是编程相关

用户相关

用户相关的东西,在这里主要是一些常用的信息,例如用户id、用户联系方式、用户头像等等。这部分的做法有两种,其一,这些信息分别存储,用的时候直接调用;其二创建一个用户类,将常用的用户信息存储起来,在后面需要的时候,在用户类中去对应取值。

原因是,一般而言,我们会将用户信息存储起来,当然,也就是有两种方式,服务器存储、数据库存储。在我们后期使用用户信息的时候,基本都是从这三种途径获取,必不可少的,自然数服务器存储,但是如果每次我们使用,都要去请求一边服务器,是很浪费资源,而且完全没必要的。所以一般情况下(个别安全性严谨的除外),都是只有第一次登录账户的时候,获取一下个人的信息,存储在本地,用的时候直接调用。而至于在本地是使用数据库存储,或许有人比较喜欢SharedPreference,不过个人不建议,毕竟存储的数据量比较大。

但无论使用哪种,繁复访问数据库也是一个很耗时的操作,所以很容易影响到用户的体验,所以这个时候就可以选择在BaseActivity中保留一个用户信息的部分,方便之后使用,不过个人建议保留一些常用的即可,没必要将整个用户信息都暴露出来。

由于项目不同,所需也不尽相同,这部分就并予以展示了。

编程相关

这部分呢,我主推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(),我们分开来阐述。

为什么定义Context

这个部分,最开始呢,我只是为了与BaseFragment相统一,方便后续编码,至于原因,就先剧透一下了,那就是在Fragment中使用getActivity()方法,容易导致空指针异常,至于原因,我们就先买个关子,到了BaseFragment中,再为大家剖析。

偷懒的解决方法就是在BaseFragment中获取一个上下文信息,后面需要使用的时候直接调用就可以,所以为了代码统一,在BaseActivity中就定义了一个,而且还是那句话,毕竟我们可以偷懒啊,不用每次getContext或者getApplicationContext来获取上下文了。随着查资料,我有发现了一个原因,就是为什么我写了一个Context的同时又写了一个Activity。

为什么要多出来一个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 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。当然,也可以直接复制博客中所写的内容,但是需要注意的一点是,我们的包或许与作者的不同,所以需要做修改,不然会报红,方法很简单,如下图:

653335a5c63e

这里写图片描述

找到对应的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帮了个大忙。当然这部分我就没有上面网络监听部分那么偷懒了,而是做了一定的简化,只要了实现我预期功能的部分,其他的内容暂时没有添加到项目中,如果大家也只是想实现一个效果的话,可以看一下我下面贴出来的代码,如果想深入的了解一下的话,可以看一下上面对应的这篇博客。

先上一下效果图:

653335a5c63e

这里写图片描述

没错,就是中间的那个圈,至于那个一如既往的丑的背景,辛苦大家暂且忍受一下。

首先是一个ProgressDialog的布局文件:

android:layout_width="match_parent"

android:layout_height="match_parent"

android:background="@android:color/transparent">

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" />

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" />

先说一下比较次要的TextView吧,它的功能主要就是一个文字提示,例如比较常见的“玩命加载中。。。”之类的,只要根据id找到控件,对应setText就可以了。

再就是我们主体ProgressBar,其中有两个属性之前博客没提到过:1、style,就是一个设计风格,也是一个比较常用的属性,对应内容下面会贴出;2、android:indeterminateDrawable,设置的是非进度条(也就是那个圈)的动画Drawable,代码后面也会贴出来。

贴代码喽。

style(里面只有一条,就是背景透明,这个style很快在后面会再用到):

@android:color/transparent

然后是Drawable对应的动画xml:

android:fromDegrees="0"

android:pivotX="50%"

android:pivotY="50%"

android:toDegrees="360" >

android:innerRadiusRatio="3"

android:shape="ring"

android:thicknessRatio="8"

android:useLevel="false" >

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" />

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的背景都设置成了透明也一样。错误效果如图:

653335a5c63e

这里写图片描述

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

653335a5c63e

这里写图片描述

然后是在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中,但是这部分比较复杂,在对应使用的地方再加以说明。

以上内容均为我个人的粗浅理解,大家如果发现问题或者可以添加的部分,可以留言,大家一起加油进步哦。

附录

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值