第一行代码学习笔记第二章——探究活动

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/salmon_zhang/article/details/79221096

知识点目录

知识点回顾

2.1 活动是什么

是一种包含用户界面的组件,主要用于和用户进行交互。

2.2 活动的基本用法

Android Studio一个工作区间只允许打开一个项目,故点击导航栏File –> Close Project ,新建项目ActivityTest,选择Add No Activity 手动创建活动。

这里写图片描述

2.2.1 手动创建活动

将项目结构手动切换成Project模式:

这里写图片描述

右击com.example.activitytest –> New –> Activity –> Empty Activity ,创建FirstActivity。

这里写图片描述

任何活动都应该重写Activity的onCreate()方法。

2.2.2 创建和加载布局

Android程序设计讲究逻辑和视图分离,最好每一个活动都能对应一个布局,布局就是用来显示界面的内容。

创建布局:

右击app/src/main/res目录——>New——>Directory,弹出新建目录的窗口,创建一个名为layout的目录,然后layout目录右键——>New——>Layout resource file,弹出新建布局文件的窗口,将布局文件命名为first_layout,根元素默认选择为LinearLayout。

这里写图片描述

加载布局:

项目中添加的资源文件都会在R文件中生成一个相应的资源id。通过setContentView()方法来加载当前活动的布局,而在setContentView()方法中我们传入的是布局文件的id。

setContentView(R.layout.first_layout);
2.2.3 在AndroidManifest文件中注册

所有的活动都要在AndroidManifest.xml中进行注册才能生效,但Android Studio会自动帮我们注册。打开app/src/main/AndroidManifest.xml文件。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.activitytest">
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".FirstActivity"
            android:label="This is FirstActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

其中:

package指定了程序的包名;

name指定注册的是哪个活动;

label指定活动中标题栏的内容,还会成为启动器(Launcher)中应用程序显示的名称;

intent-filter标签指定当前活动为程序的主活动。

注:如果整个程序没有指定任何一个活动作为主活动,这个程序仍然可以正常安装,只是无法在启动器中看到或者打开这个程序。这种程序一般都是作为第三方服务提供其他应用在内部进行调用的,如支付宝快捷支付服务。

2.2.4 在活动中使用Toast

Toast是Android系统提供的一种信息提醒方式。信息在一段时间后会自动消失,并且不会占用任何屏幕空间。

在first.xml中定义一个Button,作为Toast触发点。在first.xml方法中添加如下代码:

<Button
    android:id="@+id/button_1"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Button 1"/>

在onCreate()方法中添加如下代码:

 Button button1 = (Button) findViewById(R.id.button_1);
 button1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    Toast.makeText(FirstActivity.this,"You Click Button 1",Toast.LENGTH_SHORT).show();
    }
 });

通过findViewById()方法获取到在布局文件中定义的元素,传入R.id.button_1获取到Button实例。给button1设置一个监听器,然后在onClick()方法中执行监听事件。Toast输入的快捷键是Toast+Tab键。makeText()方法中的三个参数:

第一个参数:上下文Context。

第二个参数:Toast显示的文本内容。

第三个参数:Toast显示的时长,有两个内置常量,分别是Toast.LENGTH_SHORT和Toast.LENGTH_LONG。

2.2.5 在活动中使用Menu

在res目录下新建一个menu文件夹,右击res目录——>New——>Directory,输入文件夹名menu,点击OK。再在menu文件夹下新建一个名为main的菜单文件,右击menu文件夹——>New——>Menu resource file。然后在main.xml中添加如下代码:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/add_item" android:title="Add"/>
    <item android:id="@+id/remove_item" android:title="Remove"/>
</menu>

其中:

item标签是用来创建具体的某一个菜单项;

android:id 给菜单项指定一个唯一的标识符;

android:title 给菜单项指定一个名称。

返回FirstActivity中重写onCreateOptionMenu()方法,给当前活动创建菜单。

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.main,menu);
    return true;
}

通过getMenuInflater()得到MenuInflater对象,再调用它的inflate()方法就可以给当前活动创建菜单了。

其中:

参数一:指定通过哪个资源文件来创建菜单。

参数二:指定菜单项添加到哪一个Menu对象中。

返回true,表示允许创建的菜单显示出来;返回false,创建的菜单无法显示。

重写onOptionsItemSelected()方法,定义菜单响应事件。添加如下代码:

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.add_item:
            Toast.makeText(this, "You Click Add", Toast.LENGTH_SHORT).show();
            break;
        case R.id.remove_item:
            Toast.makeText(this, "You Click Remove", Toast.LENGTH_SHORT).show();
            break;
        default:
    }
    return true;
}

通过item.getItemId()方法来判断点击的是哪一个菜单项,然后在对应的菜单项中添加自己相应的逻辑。

标题栏右侧一个三点的符号,就是菜单按钮,菜单默认是不会显示出来,只有点击一下菜单按钮才会弹出具体内容,因此它不会占用任何活动的空间。

运行效果如下:

这里写图片描述

2.2.6 销毁一个活动
  • 按Back键

  • 调用finish()方法。

2.3 使用Intent在活动之间穿梭

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

2.3.1 使用显式Intent

右击com.example.activitytest包——>New——>Activity——>Empty Activity,新建一个SecondActivity,勾选Generate Layout File,布局文件命名为second_layout,不勾选Launcher Activity选项。

编辑second_layout.xml文件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/button_2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Button 2"/>
</LinearLayout>

修改FirstActivity()方法中按钮的点击事件,代码如下:

button1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
        startActivity(intent);
    }
});

其中,Intent中的参数:

参数一:启动活动的上下文;

参数二:指定想要启动的目标活动。

按Back键可以销毁当前活动,返回上一个活动。

2.3.2 使用隐式Intent

隐式Intent不明确指出要启动的活动,而是指定一系列更为抽象的action和category等信息,然后让系统去分析这个Intent,从而找出合适的活动去启动。

通过在activity标签下配置intent-filter的内容,可以指定当前活动能响应的action和category,打开AndroidManifest.xml,添加如下代码:

<activity android:name=".SecondActivity">
    <intent-filter>
        <action android:name="com.example.activitytest.ACTION_START" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

只有action和category中的内容同时匹配Intent中指定的action和category时,这个活动才能响应该Intent。

修改FirstActivity中按钮的点击事件:

button1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent intent = new Intent("com.example.activitytest.ACTION_START");
        startActivity(intent);
    }
});

这样通过隐式的Intent也可以启动SecondActivity。

每个Intent中只能指定一个action,却可以指定多个category。

button1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
       Intent intent = new Intent("com.example.activitytest.ACTION_START");
       intent.addCategory("com.example.activitytest.MY_CATEGORY");
       startActivity(intent);
    }
});

打开AndroidManifest.xml,在SecondActivity的intent-filter中添加一个category声明

<category android:name="com.example.activitytest.MY_CATEGORY" />
2.3.3 更多隐式Intent的用法

使用隐式Intent,不仅可以启动自己程序内的活动,还可以启动其他程序的活动。例如可以调用系统的浏览器来打开一个网页。

修改FirstActivity中按钮点击事件的代码,如下:

 button1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.setData(Uri.parse("http://www.baidu.com"));
            startActivity(intent);
        }
    });

其中:

Intent的action是Intent.ACTION_VIEW,是一个Android内置的动作;

Uri.parse()方法将网址解析成一个Uri对象;

setData()方法将解析好的Uri对象传递进去。

因为intent有setData()方法,所以我们也可以在intent-filter标签中再配置一个data标签,用于更精确指定当前活动响应的是什么类型的数据。data标签中可以配置以下内容:

  • android:scheme。用于指定数据的协议部分,例如上面的http

  • android:host。用于指定数据的主机名部分,例如上面的www.baidu.com

  • anddroid:port。用于指定数据的端口部分。一般紧随主机名之后

  • android:path。用于指定主机名和端口之后的部分。如一段网址中跟在域名之后的内容

  • android:mimeType。用于指定可以处理的数据类型,允许使用通配符的方式进行指定

只有data标签中指定的内容和Intent中携带的Data完全一致时,当前的活动才能够响应该Intent。

除了http协议外,还可以指定其他协议。比如geo表示显示地理位置、tel表示拨打电话。调用系统拨号界面示例代码如下:

button1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent(Intent.ACTION_DIAL);
            intent.setData(Uri.parse("tel:10086"));
            startActivity(intent);
        }
    });
2.3.4 向下一个活动传递数据

Intent不仅可以启动一个活动,还可以在启动活动的时候传递数据,传递数据的思路如下:

  • 在启动页面中通过Intent提供的一系列putExtra()方法,将传递的数据放在Intent中

  • 在被启动的页面中通过getIntent获取Intent对象,再调用getXXXExtra()方法获取值。

比如在FirstActivity中传一个字符串到SecondActivity中,在FirstActivity中的代码如下:

 button1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            String data = "Hello SecondActivity";
            Intent intent = new Intent(FirstActivity.this, SecondActivity.class );
            intent.putExtra("extra_data", data);
            startActivity(intent);
        }
    });

这里采用的是显式Intent的方式来启动SecondActivity,并通过putExtra()方法传递一个字符串数据。putExtra()中的两个参数:

  • 参数一:键。用于在后面从Intent中获取值。

  • 参数二:要传递的数据值。

从SecondActivity中取值的代码如下:

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_second);
    Intent intent = getIntent();
    String data = intent.getStringExtra("extra_data");
}

首先通过getIntent()方法获取到启动SecondActivity的Intent,然后调用getStringExtra()方法,传入相应的键值,从而可以得到传递的数据。因为这里传入的是字符串数据,那么就使用getStringExtra()方法来获取对应的数据;如果传递的是整型数据,则使用getIntExtra()方法;如果传递的是布尔型数据,则使用getBooleanExtra()方法,以此类推。

2.3.5 返回数据给上一个活动

如果在启动另外一个活动后,想得到被启动的活动的数据反馈,那么我们在启动活动的时候就不能再用startActivity()方法了,而应该使用startActivityForResult()方法,该方法接收两个参数:
* 参数一:intent

  • 参数二:请求码。用于在之后的回调中判断数据的来源。

修改FirstActivity中按钮的点击事件:

button1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent(FirstActivity.this, SecondActivity.class );
            startActivityForResult(intent,1);
        }
    });

请求码要确定是一个唯一值,这里传入1。接着在SecondActivity中给按钮注册点击事件,并在点击事件中添加返回数据的逻辑,代码如下:

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_second);
    Button button2 = (Button) findViewById(R.id.button_2);
    button2.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent();
            intent.putExtra("data_return", "Hello FirstActivity");
            setResult(RESULT_OK, intent);
            finish();
        }
    });
}

这里我们构建了一个Intent,但这里的Intent仅仅只是用于传递数据,不启动其他任何Activity。然后将要反馈的数据放在intent中,然后再调用setResult()方法,该方法是专门用于向上一个活动返回数据的。setResult()方法有两个参数:

  • 参数一:用于向上一个活动返回处理结果。一般只使用RESULT_OK或RESULT_CANCELED。

  • 参数二:带有数据的Intent。

最后调用finish()方法来销毁当前活动。

因为前面我们使用的是startActivityForResult()方法来启动的SecondActivity,那么在SecondActivity被销毁后会回调上一个活动的onActivityResult()方法。因为我们需要在FirstActivity中重写这个方法来得到返回的数据,代码如下:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
        case 1:
            if (resultCode == RESULT_OK) {
                String returnData = data.getStringExtra("data_return");
                Log.d(TAG, returnData);
            }
            break;
        default:
    }
}

其中,onActivityResult()方法中带有三个参数:

  • 参数一:requestCode,请求码。就是在活动时传入的请求码。

  • 参数二:resultCode,结果码。就是在返回数据时传入的处理结果。

  • 参数三:data,返回数据的Intent。

由于有可能在一个活动中调用startActivityForResult()方法去启动很多不同的活动,每一个活动返回的数据都会回调onActivityResult()方法,因此我们需要通过requestCode的值来判断数据的来源,再通过resultCode的值来判断处理结果是否成功,最后再从data中取值,这样才算完成了向上一个活动返回数据的工作。

上面我们是通过点击SecondActivity中的按钮来返回上一个活动,如果我们是直接按Back键返回到FirstActivity,那么数据就没法返回了。为了处理这种情况,我们可以通过重写SecondActivity中的onBackPressed()方法来解决,代码如下:

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

这样当用户按下Back键,就会去执行onBackPressed()方法中的代码,同样可以将数据返回到FirstActivity中去。

2.4 活动的生命周期

掌握Activity的生命周期对Android开发者来说是至关重要的。

2.4.1 返回栈

Android中的活动是可以层叠的,每启动一个新的活动,就会覆盖在原活动之上,当点击Back键或执行finish操作时,就会销毁最上面的活动,下面的一个活动就会重新显示出来。而Android是使用任务(Task)来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈又被称作返回栈(Back Stack)。栈是一种后进先出的数据结构,在默认情况下,每当我们启动了一个新的活动,它都会进入返回栈,然后处于栈顶的位置。

2.4.2 活动状态

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

1. 运行状态

当Activity处于返回栈的栈顶时,这时就处于运行状态。系统最不愿意回收处于运行状态的活动。

2. 暂停状态

当Activity不再处于栈顶位置,但仍然可见时,这时活动就会进入暂停状态。只有在内存极低的情况下,系统才会去考虑回收这种活动。

3. 停止状态

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

4. 销毁状态

当Activity从返回栈中移除后,就是处于销毁状态。系统非常喜欢回收处于这种状态的活动,从而来保证机器的内存充足。

2.4.3 活动的生存期

Activity中定义了7个回调方法来阐述活动生命周期的每一个环节:

  • oncreate(),该方法在活动第一次被创建的时候调用,主要完成一些初始化操作。例如加载布局、绑定事件等。

  • onStart() ,该方法在活动由不可见到可见的时候调用。

  • onResume() ,该方法是活动准备好和用户进行交互的时候调用。此时活动一定处于栈顶,且是运行状态。

  • onPause(),该方法在系统准备去启动或恢复另一个活动的时候调用。在这个方法中我们一般会快速的释放掉一些耗CPU的资源、保存一些关键的数据。

  • onStop(),该方法在活动完全不可见时调用。与onPause()的主要区别是,如果新启动的Activity是一个对话框式的活动,那么onPause()会执行,onStop()不会执行。

  • onDestory(),该方法在活动被销毁之前调用,之后的活动状态变为销毁状态。

  • onRestart(),该方法在活动由停止状态变为运行状态之前调用,也就是活动被重新启动。

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

1. 完整生存期

onCreate() ——> onDestory()

onCreate()到onDestory()就是完整生存期。

2. 可见生存期

onstart() ——> onStop()

onstart()到onStop()就是可见生存期

3. 前台生存期

onResume() ——> onPause()

onResume()到onPause()就是前台生存期

为了更好的理解Activity的生命周期,Android官方提供了一张示意图:

这里写图片描述

2.4.4 体验活动的生命周期

创建ActivityLifeCycleTest项目,新建NormalActivity和DialogActivity,布局名分别为actvity_normal.xml和activity_dialog.xml

编辑actvity_normal.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="This is a normal Activity"/>

</LinearLayout>

编辑activity_dialog.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="This is a dialog Activity"/>

</LinearLayout>

到清单文件中设置DialogActivity的主题:

<activity android:name=".DialogActivity" android:theme="@style/Theme.AppCompat.Dialog"/>

编辑activity_main.xml文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<Button
    android:id="@+id/start_normal_activity"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Start Normal Activity" />

<Button
    android:id="@+id/start_dialog_activity"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Start Dialog Activity" />

</LinearLayout>

在布局中加入两个按钮,分别用于启动NormalActivity和DialogActivity。

修改MainActivity中的代码:

private static final String TAG = "MainActivity";

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d(TAG, "onCreate: ");
    setContentView(R.layout.activity_main);

    Button startDialogActivity = (Button) findViewById(R.id.start_dialog_activity);
    Button startNormalActivity = (Button) findViewById(R.id.start_normal_activity);

    startNormalActivity.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent(MainActivity.this, NormalActivity.class);
            startActivity(intent);
        }
    });

    startDialogActivity.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent(MainActivity.this, DialogActivity.class);
            startActivity(intent);
        }
    });
}

@Override
protected void onStart() {
    super.onStart();
    Log.d(TAG, "onStart: ");
}

@Override
protected void onResume() {
    super.onResume();
    Log.d(TAG, "onResume: ");
}

@Override
protected void onPause() {
    super.onPause();
    Log.d(TAG, "onPause: ");
}

@Override
protected void onStop() {
    super.onStop();
    Log.d(TAG, "onStop: ");
}

@Override
protected void onDestroy() {
    super.onDestroy();
    Log.d(TAG, "onDestroy: ");
}

@Override
protected void onRestart() {
    super.onRestart();
    Log.d(TAG, "onRestart: ");
}

在onCreate()方法中给两个按钮分别添加点击事件,点击第一个按钮来启动NormalActivity,点击第二个按钮来启动DialogActivity(),然后分别在7个回调方法中打印log信息,这样就可以通过观察log的方式来直观地理解活动的生命周期。

apk安装完成后,MainActivity启动时,log信息显示:

D/MainActivity: onCreate: 
D/MainActivity: onStart: 
D/MainActivity: onResume:

点击第一个按钮,启动NormalActivity,log信息显示如下:

com.example.activitylifecycletest D/MainActivity: onPause: 
com.example.activitylifecycletest D/MainActivity: onStop: 

因为MainActivity已经被NoramlActivity完全遮盖住了,所以会走onPause()和onStop()

然后按下Back键,回到MainActivity,log信息显示如下:

com.example.activitylifecycletest D/MainActivity: onRestart: 
com.example.activitylifecycletest D/MainActivity: onStart: 
com.example.activitylifecycletest D/MainActivity: onResume: 

然后再点击第二个按钮,启动DialogActivity,log信息显示如下:

com.example.activitylifecycletest D/MainActivity: onPause: 

从log中可以看出只执行了onPause()方法,onStop()并没有执行,这是因为DialogActivity并没有将MainActivity完全遮盖住。此时MainActivity并没有进入停止状态,只是进入了暂停状态。

按下Back键,log信息如下:

com.example.activitylifecycletest D/MainActivity: onResume:

此时也只有onResume()方法执行。

最后在MainActivity界面按下Back键退出程序,log信息显示如下:

com.example.activitylifecycletest D/MainActivity: onPause: 
com.example.activitylifecycletest D/MainActivity: onStop: 
com.example.activitylifecycletest D/MainActivity: onDestroy: 

依次执行了onPause()、onStop()、onDestory(),最终销毁MainActivity

2.4.5 活动被回收了怎么办

如果活动处于停止状态时,就有可能因为内存不足被系统回收掉,这是我们就需要在回收之前保存活动的一些临时数据。Activity中提供了一个onSaveInstanceState()回调方法,该方法可以保证在活动回收之前被调用。因此我们可以在该方法中保存一些重要的临时数据。

在MainActivity中添加如下代码:

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

可以看到Bundle保存数据也是通过键值对的形式保存。

数据保存下来了,那该从哪里去取呢?我们发现onCreate()方法中有一个Bundle类型的参数,这个参数保存了所有保存的数据。我们可以通过相应的键来取出对应的值。

在MainActivity的onCreate()方法中取出值:

//获取Bundle中保存的数据
    if (savedInstanceState != null) {
        String tempData = savedInstanceState.getString("data_key");
        Log.d(TAG, tempData);
    }

2.5 活动的启动模式

启动模式共有4种,分别为standard、singleTop、singleTask和singleInstance。可以在AndroidManifest.xml中通过给标签指定android:launchMode属性来选择启动模式。

2.5.1 standard

standard又称作“标准式”,在不显式指定的情况下,所有的活动都是使用这种启动模式。使用standard模式的活动,系统不会在乎这个活动是否已经在返回栈中存在,每次启动活动都会创建该活动的一个新实例。

打开ActivityTest项目,修改FirstActivity中的onCreate()方法:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d(TAG, "Task id is " + getTaskId());
    setContentView(R.layout.first_layout);
    Button button1 = (Button) findViewById(R.id.button_1);
    button1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent(FirstActivity.this, FirstActivity.class);
            startActivity(intent);
        }
    });
}

在FirstActivity的基础上启动FirstActivity,并添加一条打印信息,用于打印当前活动的实例。连续点击两次按钮,可以看到logcat中打印信息如下所示:

com.example.activitytest.FirstActivity@6eb0acd
com.example.activitytest.FirstActivity@5fbd8e1
com.example.activitytest.FirstActivity@40b5390

可以看出每点击一次就会创建一个新的FirstActivity实例。此时返回栈中就有3个FirstActivity实例,因此我们需要连续按3此Back键才能退出程序。

2.5.2 singleTop

singleTop又称作“栈顶复用式”,就是在启动活动时,如果发现返回栈的栈顶已经是该活动了,那么就直接使用它,不会再创建新的实例。

修改AndroidManifest.xml中的FirstActivity的启动模式,如下所示:

<activity
    android:name=".FirstActivity"
    android:launchMode="singleTop"
    android:label="This is FirstActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

运行程序,可以看到logcat信息如下:

com.example.activitytest/u0a63 for activity com.example.activitytest/.FirstActivity

之后无论你点击多少次都不会再有新的打印信息出现。因为此时FirstActivity处于栈顶的位置,不会再创建新的实例。但是当FirstActivity不处于栈顶的位置时,这时再启动FirstActivity是会创建新的实例的。

2.5.3 singleTask

singleTask又称作“栈内复用式”,每次启动活动时都会先在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用,并把这个活动之上的所有活动统统出栈,如果没有发现就会创建一个新的活动实例。

修改AndroidManifest.xml中的FirstActivity的启动模式,如下所示:

<activity
    android:name=".FirstActivity"
    android:launchMode="singleTask"
    android:label="This is FirstActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

并在FirstActivity中添加onRestart()方法:

@Override
protected void onRestart() {
    super.onRestart();
    Log.d(TAG, "onRestart: ");
}

在SecondActivity中添加onDestroy()方法:

@Override
protected void onDestroy() {
    super.onDestroy();
    Log.d(TAG, "onDestroy: ");
}

运行程序,在FirstActivity中点击进入SecondActivity,然后在SecondActivity中点击回到FirstActivity,查看logcat中的打印信息:

com.example.activitytest.FirstActivity@132654d
com.example.activitytest.SecondActivity@1d67686
com.example.activitytest D/FirstActivity: onRestart: 
com.example.activitytest D/SecondActivity: onDestroy:

从打印信息中可以看出,SecondActivity在启动 FirstActivity时,发现返回栈中已经有一个FirstActivity的实例,并且是在SecondActivity下面,此时SecondActivity就会出栈,让FirstActivity重新处于栈顶的位置。因此FirstActivity中的onRestart和SecondActivity中的onDestroy都会执行,现在任务栈中只剩一个FirstActivity实例,按一下Back键就可以退出程序。

2.5.4 singlestance

singlestance又称作“单例式”。指定为singlestance模式的活动会启用一个新的返回栈来管理这个活动,有一个单独的返回栈来管理这个活动,不管是哪个应用程序来访问这个活动,都共用同一个返回栈,从而达到了共享活动实例的目的。

修改AndroidManifest.xml中的SecondActivity的启动模式,如下所示:

<activity android:name=".SecondActivity"
    android:launchMode="singleInstance">
    <intent-filter>
        <action android:name="com.example.activitytest.ACTION_START" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="com.example.activitytest.MY_CATEGORY" />
    </intent-filter>
</activity>

将SecondActivity的启动模式指定为singleInstance。

分别修改FirstActivity、SecondActivity、ThirdActivity中onCreate()方法中的log打印信息:

Log.d(TAG, "Task id is " + getTaskId());

修改代码,在FirstActivity中启动SecondActivity,在SecondActivity中启动ThirdActivity,查看logcat中的打印信息,如下所示:

com.example.activitytest D/FirstActivity: Task id is 153
com.example.activitytest D/SecondActivity: Task id is 154
com.example.activitytest D/ThirdActivity: Task id is 153

从logcat信息中可以看出,SecondActivity的任务栈id与FirstActivity和ThirdActivity均不同,所以SecondActivity确实是放在一个单独的返回栈中。

2.6 活动的最佳实践

2.6.1 知晓当前是在哪一个活动

在ActivityTest项目中新建一个BaseActivity类。右击com.example.activitytest包——>New——>Java Class,BaseActivity不需要在AndroidManifest.xml中注册,所以选择创建一个普通的Java类就可以了。然后让BaseActivity继承AppCompatActivity,并重写onCreate()方法:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d("BaseActivity", getClass().getSimpleName());
}

然后分别让FirstActivity、SecondActivity、ThirdActivity继承BaseActivity,而不是直接继承AppCompatActivity。

运行程序,通过点击按钮分别进入FirstActivity、SecondActivity、ThirdActivity的界面,查看logcat中的打印信息:

com.example.activitytest D/BaseActivity: FirstActivity
com.example.activitytest D/BaseActivity: SecondActivity
com.example.activitytest D/BaseActivity: ThirdActivity

每当我们进入到一个活动的界面,该活动的类名就会被打印出来,这样我们就可以时时刻刻知晓当前界面对应的是哪一个活动了。

2.6.2 随时随地退出程序

使用一个集合类对所有的活动进行管理,这样就可以实现程序随时随地退出。

新建一个ActivityCollector类作为活动管理器:

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();
            }
        }
        activities.clear();
    }
}

定义一个addActivity()方法来向List中添加一个活动;

定义一个removeActivity()方法用于从List中移除活动;

定义一个finishAll()方法将List中存储的活动全部销毁掉。

修改BaseActivity中的代码:

public class BaseActivity extends AppCompatActivity {

    private static final String TAG = "BaseActivity";
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, getClass().getSimpleName());
        ActivityCollector.addActivity(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ActivityCollector.removeActivity(this);
    }
}

在onCreate()方法中调用addActivity()方法,表示将当前正在创建的活动添加到活动管理器中;重写onDestroy()方法,并调用removeActivity()方法,表示将一个马上要销毁的活动从活动管理器里移除。

在任何地方想要退出程序,只需要调用ActivityCollector.finishAll()方法。例如想在ThirdActivity界面通过点击按钮直接退出程序,则只需要添加如下代码:

Button button3 = (Button) findViewById(R.id.button_3);
    button3.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            ActivityCollector.finishAll();
        }
    });

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

android.os.Process.killProcess(android.os.Process.myPid());

其中,killProcess()方法用于杀掉一个进程,它接收一个进程id参数,可以通过myPid()方法获取当前进程的id。需要注意的是,killProcess()方法只能用于杀掉当前程序的进程,不能使用这个方法杀掉其他程序。

2.6.3 启动活动的最佳写法

通常情况下我们启动一个活动的写法如下:

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

但如果我们不知道启动SecondActivity需要传递哪些数据?那我们就需要去看SecondActivity的源码或问开发SecondActivity的同事,这样就显示比较繁琐。

为了优化这种问题,我们只需要在开发SecondActivity时就定义一个启动方法:

public static void actionStart(Context context, String data1, String data2) {
    Intent intent = new Intent(context, SecondActivity.class);
    intent.putExtra("parm1", data1);
    intent.putExtra("parm2", data2);
    context.startActivity(intent);
}

直接在SecondActivity方法中定义一个actionStart()方法,将需要的数据通过参数传递过来,然后将它们存储在Intent中,最后再调用startActivity()方法启动SecondActivity。

这样我们在其他地方启动SecondActivity就很方便:

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

2.7 小结与点评

本章主要学习了活动的基本用法,到启动活动和传递数据的方式,再到活动的生命周期,以及活动的启动模式等。

猜你喜欢

转载自blog.csdn.net/salmon_zhang/article/details/79221096