第一行代码读书笔记 2 -- 探究活动

本篇文章主要介绍以下几个知识点:

  • 显示、隐式 Intent 的相关内容;
  • 活动 Activity 的生命周期;
  • 活动 Activity 的启动模式;
  • 活动的最佳实践:活动管理类、启动活动的最佳写法。
图片来源于网络

2.1 活动是什么

活动(activity)是一种可以包含用户界面的组件,主要用于和用户进行交互。

2.2 使用 Intent 在活动之间穿梭

Intent 大致可分为两种:显示 Intent 和 隐式 Intent

2.2.1 使用显示 Intent

显示 Intent 有多个构造函数的重载,其中一个是 Intent(Context packageContext,Class<?>cls)

  • 第一个参数 Context 要求提供一个启动活动的上下文。
  • 第二个参数 Class 则是指定想要启动的目标活动。

代码如下所示:

// 显示Intent
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
startActivity(intent);

2.2.2 使用隐式 Intent

隐式 Intent 并不明确指出想要启动哪一个活动,而是指定了一系列更为抽象的 actioncategory 等信息,然后交由系统去分析这个 Intent,并找出合适的活动去启动。

用法:打开 AndroidManifest.xml,添加如下代码:

<activity android:name=".inquiry_activity.SecondActivity">   
      <intent-filter>       
            <!-- 指定活动能响应的action和category -->        
            <action android:name="com.wonderful.ACTION_START"/>       
            <category android:name="android.intent.category.DEFAULT"/>   
      </intent-filter>
</activity>

此时在 activity 中的代码修改为:

// 隐式Intent
Intent intent = new Intent("com.wonderful.ACTION_START");
startActivity(intent);

只有 <action><category> 中的内容同时能够匹配上 Intent 中指定的 actioncategory 时,活动才能响应该 Intent

上例中 android.intent.category.DEFAULT 是一种默认的 category,若 Intent 中增加一个 category

// 隐式Intent
Intent intent = new Intent("com.wonderful.ACTION_START");
intent.addCategory("com.wonderful.MY_CATEGORY");//增加一个category
startActivity(intent);

此时在 <intent-filter> 标签中应再添加一个 category 声明:

<activity android:name=".inquiry_activity.SecondActivity">   
      <intent-filter>       
            <!-- 指定活动能响应的action和category -->        
            <action android:name="com.wonderful.ACTION_START"/>       
            <category android:name="android.intent.category.DEFAULT"/>   
            <!-- 新添加的category -->
            <category android:name="android.intent.category.MY_CATEGORY"/>   
      </intent-filter>
</activity>

2.2.3 更多隐式 Intent 的用法

使用隐式 Intent,还可以启动其他程序的活动,比如调用系统的浏览器来打开某个网页:

// 隐式Intent:打开网页
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);

比如调用系统的拨号界面:

// 隐式Intent:打开系统拨号界面
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);

2.2.4 向下一个活动传递数据

Intent 中提供了一系列 putExtra() 方法的重载,可把要传递的数据暂存在 Intent 中,启动了另一个活动后再从 Intent 中取出。

FirstActivity 中传递一个字符串到 SecondActivity 中,FirstActivity 中的代码为:

String data = "hello SecondActivity";
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
intent.putExtra("extra_data",data);
startActivity(intent);

SecondActivity中的代码如下:

Intent intent = getIntent();
// String:getStringExtra(); int:getIntExtra(); 布尔型:getBooleanExtra() ...以此类推
String data = intent.getStringExtra("extra_data");
Log.d(TAG, data);

2.2.5 返回数据给上一个活动

Activity 中有个 startActivityForResult() 方法用于启动活动,此方法在活动销毁时能返回一个结果给上个活动。

startActivityForResult() 方法接收两个参数:Intent、请求码(用作回调时判断数据来源)。

修改 FirstActivity 中的代码如下:

Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
startActivityForResult(intent,1);//请求码只要是唯一值就行了,这里传入1

接着在 SecondActivity 中添加返回数据:

Intent intent = new Intent();
intent.putExtra("data_return","hello FirstActivity");
setResult(RESULT_OK,intent);
finish();//销毁当前活动

SecondActivity 被销毁后回调上一个活动的 onActivityResult() 方法,因此还需要在 FirstActivity 中重写此方法来得到返回数据:

/** 
 * 处理得到的返回数据 
 * @param requestCode  启动活动时传入的请求码
 * @param resultCode  返回数据时传入的处理结果
 * @param data  携带返回数据的Intent 
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {    
       switch (requestCode){       
            case 1:           
                 if (resultCode == RESULT_OK){                
                     String returnedData = data.getStringExtra("data_return");           
                     Log.d(TAG, returnedData);           
                 }            
                 break;                
            default:           
                 break;   
       }
}

若在 SecondActivity 是通过按下 Back 键回到 FirstActivity,可以在 SecondActivity 中重写 onBackPressed() 方法:

@Override
public void onBackPressed() {    
     Intent intent = new Intent();
     intent.putExtra("data_return","hello FirstActivity");
     setResult(RESULT_OK,intent);
     finish();
}

2.3 活动的生命周期

2.3.1 返回栈

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

栈是一种后进先出的数据结构,在默认情况下,每当启动了一个新的活动,它会在返回栈中入栈,并处于栈顶的位置。

每当按下 Back 键或调用 finish() 方法去销毁一个活动时,处于栈顶的活动会出栈,这时前一个入栈的活动就会重新处于栈顶的位置。系统总是会显示处于栈顶的活动给用户。

如图所示:

返回栈工作示意图

2.3.2 活动状态

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

1. 运行状态

活动位于返回栈的栈顶。系统最不愿意回收的就是处于运行状态的活动,因为这会带来非常差的用户体验。

2. 暂停状态

活动不再处于栈顶位置,但仍然可见。(并不是每一个活动都会占满整个屏幕的,如对话框形式的活动只会占用屏幕中间的部分区域)。

处于暂停状态的活动仍然是完全存活着的,系统也不愿意去回收这种活动(因为它还是可见的,回收可见的东西都会在用户体验方面有不好的影响),只有在内存极低的情况下,系统才会去考虑回收这种活动。

3. 停止状态

活动不再处于栈顶位置,并且完全不可见的时候。系统仍然会为这种活动保存相应的状态和成员变量,但这并不是完全可靠的,当其他地方需要内存时,处于停止状态的活动有可能会被系统回收。

4. 销毁状态

当一个活动从返回栈中移除后就变成了销毁状态。系统会最倾向于回收处于这种状态的活动,从而保证手机的内存充足。

2.3.3 活动的生存期

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

1. onCreate()

在活动第一次被创建的时候调用。在这个方法中完成活动的初始化操作,如加载布局、绑定事件等。

2. onStart()

在活动由不可见变为可见的时候调用。

3. onResume()

在活动准备好和用户进行交互的时候调用。此时的活动一定位于返回栈的栈顶,并且处于运行状态。

4. onPause()

在系统准备去启动或恢复另一个活动的时候调用。(释放一些消耗 CPU 的资源,保存一些关键数据)。

5. onStop()

在活动完全不可见的时候调用。它和 onPause() 方法的主要区别在于,若启动的新活动是一个对话框式的活动,那么 onPause() 方法会得到执行,而 onStop() 方法并不会执行。

6. onDestroy()

在活动被销毁之前调用,之后活动的状态将变为销毁状态。

7. onRestart()

在活动由停止状态变为运行状态之前调用,也就是活动被重新启动了。

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

  • 完整生存期: 活动在 onCreate() 方法和 onDestroy() 方法之间所经历的;
  • 可见生存期: 活动在 onStart() 方法和 onStop() 方法之间所经历的;
  • 前台生存期: 活动在 onResume() 方法和 onPause() 方法之间所经历的。

Android 官方提供了一张活动生命周期的示意图,如图所示:

活动的生命周期

2.3.4 活动被回收了怎么办

Activity 中提供了一个 onSaveInstanceState() 回调方法,这 个方法会保证一定在活动被回收之前调用。
  
onSaveInstanceState() 方法会携带一个 Bundle 类型的参数,Bundle 提供了一系列的方法用于保存数据,如用 putString() 方法保存字符串,用 putInt() 方法保存整型数据, 以此类推。每个保存方法需要传入两个参数,第一个参数是键,用于后面从 Bundle 中取值, 第二个参数是真正要保存的内容。

Activity 中添加如下代码就可以将临时数据进行保存:

@Override
protected void onSaveInstanceState(Bundle outState) {    
    super.onSaveInstanceState(outState);
    String tempData = "Something you just typed";
    outState.putString("data_key", tempData);
}

Activity 被回收后的恢复操作,修改其 onCreate() 方法,如下所示:

@Override
protected void onCreate(Bundle savedInstanceState) {    
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    if (savedInstanceState != null) { // 若不为空,取出相应的数据
        String tempData = savedInstanceState.getString("data_key"); 
        Log.d(TAG, tempData);
    }
}

2.4 活动的启动模式

启动模式一共有四种,分别是 standardsingleTopsingleTasksingleInstance,可以在 AndroidManifest.xml 中通过给 <activity> 标签指定 android:launchMode 属性来选择启动模式。

2.4.1 standard

standard 是活动默认的启动模式,每当启动一个新的活动,它就会在返回栈中入栈,并处于栈顶的位置。对于使用 standard 模式的活动,系统不会在乎这个活动是否已经在返回栈中存在,每次启动都会创建该活动的一个新的实例。
  
其原理示意图如下:

standard模式示意图

2.4.2 singleTop

当活动的启动模式指定为 singleTop,在启动活动时如果发现返回栈的栈顶已经是该活动,则认为可以直接使用它,不会再创建新的活动实例。

其原理示意图如下:


singleTop模式示意图

2.4.3 singleTask

当活动的启动模式指定为 singleTask,每次启动该活动时系统首先会在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用该实例,并把在这个活动之上的所有活动统统出栈,如果没有发现就会创建一个新的活动实例。

其原理示意图如下:


singleTask模式示意图

2.4.4 singleInstance

不同于以上三种启动模式,指定为 singleInstance 模式的活动会启用一个新的返回栈来管理这个活动(其实如果 singleTask 模式指定了不同的 taskAffinity,也会启动一个新的返回栈)。
  
其原理示意图如下:

singleInstance模式示意图

2.5 活动的最佳实践

2.5.1 随时随地退出程序

用一个专门的集合类对所有的活动进行管理,新建一个 ActivityCollector 类作为活动管理器,代码如下所示:

public class ActivityCollector {
    // 通过一个List来缓存活动
    public static List<Activity> activities = new ArrayList<Activity>();
    // 用于向List中添加一个活动
    public static void addActivity(Activity activity) {
        activities.add(activity);
    }
    // 用于从List中移除活动
    public static void removeActivity(Activity activity) {
        activities.remove(activity);
    }
    // 将List中存储的活动全部销毁掉
    public static void finishAll() {
        for (Activity activity : activities) {
           if (!activity.isFinishing()) {
               activity.finish();
           }
        }
    }
}

在活动管理器中,我们通过一个 List 来暂存活动,然后提供了一个 addActivity() 方法用于向 List 中添加一个活动,提供了一个 removeActivity() 方法用于从 List 中移除活动,最后 提供了一个 finishAll() 方法用于将 List 中存储的活动全部都销毁掉。

接下来修改 BaseActivity 中的代码如下:

public class BaseActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("BaseActivity", getClass().getSimpleName());// 知晓当前是在哪一个活动
        ActivityCollector.addActivity(this);// 将当前正在创建的活动添加到活动管理期里
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ActivityCollector.removeActivity(this);// 将一个马上要销毁的活动从管理器里移除
    }
}

BaseActivityonCreate() 方法中调用了 ActivityCollectoraddActivity() 方法,表明将当前正在创建的活动添加到活动管理器里。然后在 BaseActivity 中重写 onDestroy() 方法, 并调用了 ActivityCollectorremoveActivity() 方法,表明将一个马上要销毁的活动从活动管理器里移除。

这样,不管你想在什么地方退出程序,只需要调用 ActivityCollector.finishAll() 方法就可以了。

当然你还可以在销毁所有活动的代码后面再加上杀掉当前进程的代码,以保证程序完全退出,杀掉进程代码如下:

// killProcess()方法用于杀掉一个进程,它接收一个进程id参数,可通过myPid()方法获得当前程序的进程id
android.os.Process.killProcess(android.os.Process.myPid());

需注意:killProcess() 方法只能用于杀掉当前程序的进程,不能杀掉其他程序。

2.5.2 启动活动的最佳写法

启动活动的方法:通过 Intent 构建出当前的“意图”,然后调用 startActivity()startActivityForResult() 方法将活动启动起来,如果有数据需要从一个活 动传递到另一个活动,也可以借助 Intent 来完成。

假设 SecondActivity 中需要用到两个非常重要的字符串参数,在启动 SecondActivity 的时候必须要传递过来,那么很容易会写出如下代码:

Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
intent.putExtra("param1", "data1");
intent.putExtra("param2", "data2"); 
startActivity(intent);

但此时 SecondActivity 并不是由你开发的,但现在你负责的部分需要有启动 SecondActivity 这个功能,而你却不清楚启动这个活动需要传递哪些数据。这时要么你自己去阅读 SecondActivity中的代码,要么询问负责编写 SecondActivity 的同事。而换一种写法,就可以轻松解决掉上面的窘境。

修改 SecondActivity 中的代码如下:

public class SecondActivity extends BaseActivity {
    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 中添加了一个 actionStart() 方法,在这个方法中完成了 Intent 的构建,另外所有 SecondActivity 中需要的数据都是通过 actionStart() 方法的参数传递过来的,然后把它们存储到 Intent 中,最后调用 startActivity() 方法启动 SecondActivity

这样 SecondActivity 所需要的数据全部都在方法参数中体现出来了,即使不用阅读 SecondActivity 中的代码,也可以非常清晰地知道启动 SecondActivity 需要传递哪些数据。

另外,这样写还简化了启动活动的代码,现在只需要一行代码就可以启动 SecondActivity, 如下:

SecondActivity.actionStart(FirstActivity.this, "data1", "data2");

好了,今天就到这,下篇文章将进入第三章的学习--UI开发。

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

推荐阅读更多精彩内容