第2章—探究活动

2.1活动是什么

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

2.2活动的基本用法

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

2.2.1手动创建活动

  • 将项目结构手动切换成Project模式:
    在这里插入图片描述
  • 右击com.fanzhiwei.activitytest包→New→Activity→EmptyActivity,创建FirstActivity。

在这里插入图片描述

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
}

任何活动都应该重写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文件。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.fanzhiwei.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=".MainActivity">
            <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_layout_xml中定义一个Button,作为Toast触发点。在first_layout_xml方法添加如下代码:
    <Button
        android:id="@+id/button1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textAllCaps="false"
        android:text="触发Toast"/>

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

     Button button = (Button) findViewById(R.id.button1);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(MainActivity.this,"You clicked Button 1",Toast.LENGTH_SHORT).show();
            }
        });

通过findViewById()方法获取到在布局文件中定义的元素,传入R.id.button1获取到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。接着在这个文件夹下再新建一个名叫main的菜单文件,右击menu文件夹→New→Menu resoure 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给菜单项指定一个名称。
    返回MainActivity中重写onCreateOptionMenu()方法,给当前活动创建菜单。
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main,menu);
        return true;
    }

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

  • 参数一:指定通过那个资源文件来创建菜单。
  • 参数二:制定菜单项添加到哪一个Menu对象中。
    然后给这个方法返回true,表示允许创建的菜单显示出来,如果返回了false,创建的菜单将无法显示。
    重写onOptionsItemSelected()方法,定义菜单响应事件。添加如下代码:
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId())
        {
            case R.id.add_item:
                Toast.makeText(this,"You clicked 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.fanzhiwei.activitytest包→New→Activity→Empty Activity,新建一个SecondActivity,勾选Generate Layout File,布局文件命名为activity_second,不勾选Launcher Activity选项。

编辑activity_second.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/button2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" 
        android:textAllCaps="false"
        android:text="Button2"/>

</LinearLayout>

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

   button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
//                Toast.makeText(MainActivity.this,"You clicked Button 1",Toast.LENGTH_SHORT).show();
                Intent intent = new Intent(MainActivity.this,SecondActivity.class);
                startActivity(intent);
                
            }
        });

其中,Intent中的参数:

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

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

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

2.3.3使用隐式Intent

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

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

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

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

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

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
//                Toast.makeText(MainActivity.this,"You clicked Button 1",Toast.LENGTH_SHORT).show();
        Intent intent = new Intent("com.fanzhiwei.activitytest.ACTION_START");
        startActivity(intent);

    }
});

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

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

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
//                Toast.makeText(MainActivity.this,"You clicked Button 1",Toast.LENGTH_SHORT).show();
                Intent intent = new Intent("com.fanzhiwei.activitytest.ACTION_START");
                intent.addCategory("com.fanzhiwei.activitytest.MY_CATEGORY");
                startActivity(intent);

    }
});

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

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

2.3.3更多隐式Intent的用法

使用隐式Intent,不仅可以启动自己程序内的活动,还可以启动其他程序的活动。例如可以调用系统的浏览器来打开一个网页。
修改MainActivity中按钮点击事件的代码,如下:

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
//                Toast.makeText(MainActivity.this,"You clicked Button 1",Toast.LENGTH_SHORT).show();
//                Intent intent = new Intent("com.fanzhiwei.activitytest.ACTION_START");
//                intent.addCategory("com.fanzhiwei.activitytest.MY_CATEGORY");
//                startActivity(intent);
                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表示拨打电话。调用系统拨号界面示例代码如下:

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
//                Toast.makeText(MainActivity.this,"You clicked Button 1",Toast.LENGTH_SHORT).show();
//                Intent intent = new Intent("com.fanzhiwei.activitytest.ACTION_START");
//                intent.addCategory("com.fanzhiwei.activitytest.MY_CATEGORY");
//                startActivity(intent);
                Intent intent = new Intent(Intent.ACTION_DIAL);
                intent.setData(Uri.parse("tel:123456"));
                startActivity(intent);

    }
});

在这里插入图片描述

2.3.4向下一个活动传递数据

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

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

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

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

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String data = "Hello SecondActivity";
                Intent intent = new Intent(MainActivity.this,SecondActivity.class);
                intent.putExtra("extern_data",data);
                startActivity(intent);
    }
});

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

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

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

从SecondActivity中取值的代码如下:

public class SecondActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        Intent intent = getIntent();
        String data = intent.getStringExtra("extra_data");
        Log.d("SecondActivity:",data);

    }
}

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

2.3.5返回数据给上一个活动

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

  • 参数一:intent
  • 参数二:请求码。用于在之后的回调中判断数据的来源。
    修改MainActivity中按钮的点击事件:
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(MainActivity.this,SecondActivity.class);
                startActivityForResult(intent,1);
    }
});

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

        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent();
                intent.putExtra("data_return","Hello MainActivity");
                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 returnedData = data.getStringExtra("data_return");
                    Log.d("MainActivity",returnedData);
                }
                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 MainActivity");
        setResult(RESULT_OK,intent);
        finish();
    }

2.4活动的生命周期

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

2.4.1返回栈

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

2.4.2活动状态

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

  1. 运行状态

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

  1. 暂停状态

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

  1. 停止状态

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

  1. 销毁状态

当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()就是完整生存期。

  1. 可见生存期
onstart() ——> onStop()

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

  1. 前台生存期
onResume() ——> onPause()

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

为了更好的理解Activity的生命周期,Android官方提供了一张示意图:
在这里插入图片描述

2.4.4体验活动的生命周期

创建ActivityLifeTest项目,新建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>

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

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

编辑activity_main.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"
   >
    <Button
        android:id="@+id/start_normal_activity"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start NormalActivity"
        android:textAllCaps="false"/>
    <Button
        android:id="@+id/start_dialog_activity"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start DialogActivity"
        android:textAllCaps="false"/>

</LinearLayout>

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

修改MainActivity中的代码:

public class MainActivity extends AppCompatActivity {
    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 startNormalActivity = (Button) findViewById(R.id.start_normal_activity);
        Button startDialogActivity = (Button) findViewById(R.id.start_dialog_activity);

        startNormalActivity.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(MainActivity.this,NormalActivity.class);
                startActivity(intent);
                        
            }
        });
        startDialogActivity.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                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信息显示:
在这里插入图片描述

点击第一个按钮,启动NormalActivity,log信息显示如下:
在这里插入图片描述
因为MainActivity已经被NoramlActivity完全遮盖住了,所以会走onPause()和onStop()

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

在这里插入图片描述
然后再点击第二个按钮,启动DialogActivity,log信息显示如下:
在这里插入图片描述
从log中可以看出只执行了onPause()方法,onStop()并没有执行,这是因为DialogActivity并没有将MainActivity完全遮盖住。此时MainActivity并没有进入停止状态,只是进入了暂停状态。

按下Back键,log信息如下:
在这里插入图片描述
此时也只有onResume()方法执行。

最后在MainActivity界面按下Back键退出程序,log信息显示如下:
在这里插入图片描述
依次执行了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.1standard

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

打开Demo2项目,修改MainActivity中的onCreate()方法:

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

在MainActivity的基础上启动MainActivity,并添加一条打印信息,用于打印当前活动的实例。连续点击三次按钮,可以看到logcat中打印信息如下所示:
在这里插入图片描述
可以看出每点击一次就会创建一个新的FirstActivity实例。此时返回栈中就有3个FirstActivity实例,因此我们需要连续按3此Back键才能退出程序。

2.5.2singleTop

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

<activity android:name=".MainActivity"
            android:launchMode="singleTop">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

运行程序,可以看到logcat信息如下:
在这里插入图片描述
之后无论你点击多少次都不会再有新的打印信息出现。因为此时MainActivity处于栈顶的位置,不会再创建新的实例。但是当MainActivity不处于栈顶的位置时,这时再启动MainActivity是会创建新的实例的。

2.5.3singleTask

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

<activity android:name=".MainActivity"
            android:launchMode="singleTask">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

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

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

在SecondActivity中添加onDestroy()方法:

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

运行程序,在MainActivity中点击进入SecondActivity,然后在SecondActivity中点击回到MainActivity,查看logcat中的打印信息:
在这里插入图片描述

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

2.5.4singlestance

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

   <activity android:name=".SecondActivity"
            android:launchMode="singleInstance">
            
        </activity>

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

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

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

修改代码,在MainActivity中启动SecondActivity,在SecondActivity中启动ThirdActivity,查看logcat中的打印信息,如下所示:
在这里插入图片描述
从logcat信息中可以看出,SecondActivity的任务栈id与MainActivity和ThirdActivity均不同,所以SecondActivity确实是放在一个单独的返回栈中。

2.6活动的最佳实践

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

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

 protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, getClass().getSimpleName());
        
    }

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

运行程序,通过点击按钮分别进入MainActivity、SecondActivity、ThirdActivity的界面,查看logcat中的打印信息:
在这里插入图片描述

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

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
    protected void onCreate(@Nullable 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界面通过点击按钮直接退出程序,则只需要添加如下代码:

       button3.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                ActivityCollector.finishAll();
            }
        });

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

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

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

2.6.3启动活动的最佳写法

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

Intent intent = new Intent(MainActivity.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(MainActivity.this,"data1","data2");

2.7小结与点评

本章主要学习了活动的基本用法,到启动活动和传递数据的方式,再到活动的生命周期,以及活动的启动模式等。
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

毅凡一直在进步

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值