Android 开发初步

0
(0)

《第一行代码Android》学习笔记,引用原书部分内容仅供学习,版权归原作者、出版社所有。

简介

Android 系统架构

Android 大致可以分为四层架构:Linux 内核层、系统运行库层、应用框架层、应用层

  1. Linux 内核层:Android 是基于 Linux 内核的,这一层为 Android 设备的各种硬件提供了底层的驱动
  2. 系统运行库层:通过一些 C/C++ 库来为 Android 提供主要的特性支持;如 SQLite 库提供了数据库支持,OpenGL|ES 库提供了 3D 绘图支持,Webkit 库提供浏览器内核支持
    这一层还有 Android 运行时库,它主要提供一些核心库允许开发者使用 Java 编写 Android 应用
    另外,Android 运行时库中还包含了 Dalvik 虚拟机(5.0 后改为 ART 运行环境),它使每个 Android 应用都运行在独立的进程中,且拥有一个自己的 Dalvik 虚拟机实例
    相较于 Java 虚拟机,Dalvik 是专门为移动设备定制的,它针对手机内存、CPU 性能有限等情况做了优化处理
  3. 应用框架层:主要提供构建 App 时可能用到的各种 API,Android 自带的一些核心应用就是使用这些 API 完成的,开发者也可以通过使用这些 API 构建自己的应用
  4. 应用层:所有安装在手机上的应用程序都属于这一层

Android 组件

Android 四大组件分别是活动(Activity)、服务(Service)、广播接收器(Broadcast Receiver)、内容提供器(Content Provider)

其中活动是所有 Android 应用的门面,在应用中看得到的东西都是放在活动中的。服务则会一直在后台允许,即使用户退出了应用,服务仍然是可以继续运行的。
广播接收器允许你的应用接收来自各处的广播消息,比如电话、短信等,当然你的应用也可以向外发出广播消息。
内容提供器则为应用间共享数据提供了可能。

Intent

Intent 是 Android 应用中各组件之间进行交互的一种重要方式,它不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据。
Intent 一般可被用于启动活动、启动服务、发送广播等场景。

活动的生命周期

返回栈

Android 是使用任务(Task)来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈也被称作返回栈(Back Stack)。

默认情况下,每当我们启动一个新的活动,它会在返回栈中入栈,并处于栈顶的位置。而每当我们按下 Back 键或调用 finish() 方法去销毁一个活动时,处于栈顶的活动会出栈,系统总是会显示处于栈顶的活动给用户。

活动状态

每个活动在其生命周期中最多可能会有 4 种状态。

  1. 运行状态:位于返回栈栈顶的活动处于运行状态。系统最不愿意回收的就是处于运行状态的活动,因为这会带来非常差的用户体验。
  2. 暂停状态:不再处于栈顶但仍然可见的活动进入暂停状态。不是每一个活动都会占满屏幕的,处于暂停状态的活动仍然是完全存活着的,系统也不愿意去回收这种活动,只有在内存极低的情况下,系统才会去考虑回收这种活动。
  3. 停止状态:不再处于栈顶且完全不可见的活动进入停止状态。系统仍然会为这种活动保存相应的状态和成员变量,但这并不是完全可靠的,当其他地方需要内存时,处于停止状态的活动有可能会被系统回收。
  4. 销毁状态:当一个活动从返回栈中移除后就变成了销毁状态。系统会最倾向于回收处于这种状态的活动,从而保证手机的内存充足。

活动的生存期

Activity 类中定义了 7 个回调方法,覆盖了活动生命周期的每一个环节。

  • onCreate:在活动第一次被创建时调用。应该在这个方法中完成活动的初始化操作,比如加载布局、绑定事件等。
  • onStart:在活动由不可见变为可见时调用。
  • onResume:在活动准备好和用户交互时调用。此时活动一定位于返回栈的栈顶,并且处于运行状态。
  • onPause:在系统准备去启动或恢复另一个活动时调用。我们通常会在这个方法中将一些消耗CPU的资源释放掉,以及保存一些关键数据,但这个方法的执行速度一定要快,否则会影响到新的栈顶活动的使用。
  • onStop:在活动完全不可见时调用。它和 onPause 方法的主要区别在,如果启动的新活动是一个对话框式的活动,那么 onPause 方法会得到执行,而 onStop 方法并不会执行。
  • onDestroy:在活动被销毁之前调用,之后活动的状态将变为销毁状态。
  • onRestart:在活动由停止状态变为运行状态之前调用,也就是活动被重新启动了。

以上 7 个方法中除了 onRestart 方法,其他都是两两相对的,从而又可以将活动分为 3 种生存期。

  1. 完整生存期:活动在 onCreate 和 onDestroy 之间所经历的,就是完整生存期。一般情况下,一个活动会在 onCreate 中完成各种初始化操作,在 onDestroy 方法中完成释放内存的操作。
  2. 可见生存期:活动在 onStart 和 onStop 之间所经历的,就是可见生存期。在可见生存期内,活动对用户总是可见的,即便有可能无法和用户交互。我们可以通过这两个方法,合理管理那些对用户可见的资源。比如在 onStart 中对资源进行加载,而在 onStop 中对资源进行释放,从而保证处于停止状态的活动不会占用过多内存。
  3. 前台生存期:活动在 onResume 和 onPause 之间所经历的,就是前台生存期。在前台生存期内,活动总是处于运行状态,此时的活动是可以和用户进行交互的。

活动被回收

若用户在活动 A 的基础上启动了活动 B,活动 A 进入停止状态,此时由于系统内存不足活动 A 被回收,然后用户按下 Back 返回活动 A,会发生什么?
此时还是会正常显示活动 A,但并不会执行 onRestart 方法,而是会执行活动 A 的 onCreate 方法,因为活动 A 在这种情况下会被重新创建一次。

别忘了活动 A 中是可能存在临时数据和状态的。如果用户在活动 A 中的一个文本框输入了一些内容,重新创建后文字就全部都没了。
Activity 中还提供一个 onSaveInstanceState 回调方法,这个方法可以保证在活动被回收之前一定会被调用,因此我们可以通过这个方法来解决活动被回收时临时数据得不到保存的问题。

onSaveInstanceState 方法会携带一个 Bundle 类型的参数,onCreate 方法也有一个 Bundle 类型的参数,它一般情况下是 null,如果在活动被系统回收之前有通过 onSaveInstanceState 方法来保存数据的话,这个参数就会带有之前所保存的全部数据。

Intent 还可以结合 Bundle 一起用于传递数据,首先可以把需要传递的数据都保存在 Bundle 对象中,然后再将 Bundle 对象存放在 Intent 里。到了目标活动后先从 Intent 中取出 Bundle,再从 Bundle 中取出数据。

活动的启动模式

在实际项目中应该根据特定的需求为每个活动指定恰当的启动模式。启动模式一共有 4 种,分别是 standard、singleTop、singleTask、singleInstance。可以在 AndroidManifest.xml 中通过给 <activity> 标签指定 android:launchMode 属性来选择启动模式。

standard

活动的默认启动模式。不进行显式指定的情况下,所有活动都会自动使用 standard 启动模式。
在 standard 模式下,每当启动一个新的活动,它就会在返回栈中入栈,并处于栈顶的位置。对于使用 standard 模式的活动,系统不会在乎这个活动是否已经在返回栈中存在,每次启动都会创建该活动的一个新的实例。

singleTop

singleTop 模式下,在启动活动时如果发现返回栈的栈顶已经是该活动,则认为可以直接使用它,不会再创建新的活动实例。
不过当活动并未处于栈顶位置时,还是会创建新的实例。

singleTask

有没有办法让某个活动在整个应用程序的上下文中只存在一个实例呢?
singleTask 模式下,每次启动该活动系统首先会在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用该实例,并把在这个活动之上的所有活动全部出栈,该活动的onRestart 方法和它之上的活动的 onDestroy 方法会得到执行,如果没有发现就会创建一个新的活动实例。

singleInstance

不同于以上 3 种启动模式,指定为 singleInstance 模式的活动会启用一个新的返回栈来管理这个活动 (其实如果 singleTask 模式指定了不同的 taskAffinity,也会启动一个新的返回栈)。假设程序中有一个活动是允许其他程序调用的,如何实现其他程序和我们的程序可以共享这个活动的实例呢?每个应用程序都会有自己的返回栈,前 3 种启动模式是做不到的。
而 singleInstance 模式就可以解决这个问题,在这个模式下会有一个单独的返回栈来管理这个活动,不管是哪个应用程序访问这个活动,都共用的同一个返回栈,也就解决了共享活动实例的问题。注意可能影响 Back 键返回的逻辑顺序,在一个栈顶活动按 Back 使当前返回栈为空后才会显示另一个返回栈的栈顶活动。

活动的最佳实践

  • 找到当前是在哪一个活动:新建一个 BaseActivity 类,继承自 AppCompatActivity 并重写 onCreate 方法,在 Log 中打印 getClass().getSimpleName();然后让项目中所有的活动不再继承 AppCompatActivity 而是继承 BaseActivity;现在项目中所有活动的现有功能并不受影响,且每个活动创建时都会打印该活动的类名。
  • 随时随地退出程序:用一个专门的集合类对所有的活动进行管理。在 BaseActivity 的 onCreate 方法中调用 ActivityCollector.addActivity(this)、在 onDestroy 方法中调用 ActivityCollector.removeActivity(this)。之后,不管你想在什么地方退出程序,只需要调用 finishAll 方法就可以了。
public class ActivityCollector {
    public static List<Activity> activities = new ArrayList<>();
    public static void addActivity(Activity activity) {
        activities.add(activity);
    }
    public static void removeActivity(Activity activity) {
        activities.remove(activity);
    }
    public static void finishAll() {
        for (Activity activity : activities) {
            if (!activity.isFinishing())
                activity.finish();
        }
        //还可以在销毁所有活动的代码后面加上杀掉当前进程的代码,以保证程序完全退出
        //killProcess 方法只能用于杀掉当前程序的进程,不能用这个方法杀掉其他程序
        android.os.Process.killProcess(Process.myPid());
    }
}
  • 启动活动的最佳写法:假设 SecondActivity 中需要用到两个非常重要的参数,在启动时必须传递过去,那你可能会写 intent.putExtra() 和 startActivity(intent)。这样写是完全正确的,但在真正的项目开发中经常会有对接的问题出现。比如 SecondActivity 并不是你开发的,你不清楚启动这个活动需要传递哪些数据。
    建议在 SecondActivity 中添加一个 actionStart 方法,在这个方法中完成 Intent 的构建,所有 SecondActivity 中需要的数据都通过 actionStart 的参数传递过去。
public static void actionStart(Context context, String data1, String data2) {
        Intent intent = new Intent(context, SecondActivity.class);
        intent.putExtra("param1", data1);
        intent.putExtra("param2", data2);
        context.startActivity(intent);
    }

这样写的好处是:一目了然,SecondActivity 需要的数据在方法参数中全部体现出来了,即使不阅读 SecondActivity 的代码,不去询问编写 SecondActivity 的同事,你也可以清晰地知道启动 SecondActivity 需要传递哪些参数。这样还简化了启动活动的代码,只需要一行 SecondActivity.actionStart(this, “data1”, “data2”) 就可以启动 SecondActivity 了。

养成一个良好的习惯,给你编写的每个活动都添加类似的启动方法,这样不仅可以让启动活动变得非常简单,还可以节省不少有同事过来询问你的时间。

参考资料

[1] 郭霖. 第一行代码Android 第2版[M]. 北京:人民邮电出版社, 2016.

这篇文章有用吗?

点击星号为它评分!

平均评分 0 / 5. 投票数: 0

到目前为止还没有投票!成为第一位评论此文章。

很抱歉,这篇文章对您没有用!

让我们改善这篇文章!

告诉我们我们如何改善这篇文章?

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注