第一行代码——第二章:先从看得到的地方入手——探究活动

目录:

2.1 活动是什么

2.2 活动的基本用法

2.2.1 手动创建活动

2.2.2 创建和加载布局

2.2.3 在AndroidManifest文件中注册

2.2.4 在活动中使用Toast

2.2.5 在活动中使用Menu

2.2.6 销毁一个活动

2.3 使用Intent在活动之间穿梭

2.3.1 使用显式Intent

2.3.2 使用隐式Intent

2.3.3 更多隐式Intent的用法

2.3.4 向下一个活动传递数据

2.3.5 返回数据给上一个活动

2.4 活动的生命周期

2.4.1 返回栈

2.4.2 活动状态

2.4.3 活动的生存期

2.4.4 体验活动的生命周期

2.4.5 活动被回收了怎么办

2.5 活动的启动模式

2.5.1 standard

2.5.2 singleTop

2.5.3 singleTask

2.5.4 singlestance

2.6 活动的最佳实践

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

2.6.2 随时随地退出程序

2.6.3 启动活动的最佳写法

2.7 小结与点评


知识点:

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文件。


    
        
            
                
                
            
        
    


其中:

package指定了程序的包名;

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

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

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

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

2.2.4 在活动中使用Toast

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

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

在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中添加如下代码:



    
    

其中:

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文件:


    


修改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,添加如下代码:


    
        
        
    


只有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声明

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文件:







编辑activity_dialog.xml文件:







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


编辑activity_main.xml文件





在布局中加入两个按钮,分别用于启动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的启动模式,如下所示:


    
        
        
    


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

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

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

2.5.3 singleTask

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

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


    
        
        
    


并在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的启动模式,如下所示:


    
        
        
        
    


将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 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 小结与点评

郭霖总结:

真是好疲惫啊!没错,学习了这么多的东西不疲惫才怪呢。但是,你内心那种掌握了知识的喜悦感相信也是无法掩盖的。本章的收获非常多啊,不管是理论型还是实践型的东西都涉及了,从活动的基本用法,到启动活动和传递数据的方式,再到活动的生命周期,以及活动的启动模式,你几乎已经学会了关于活动所有重要的知识点。另外在本章的最后,还学习了几种可以应用在活动中的最佳实践技巧,毫不夸张地说,你在Android活动方面已经算是一一个小高手了。

不过你的Android旅途才刚刚开始呢,后面需要学习的东西还很多,也许会比现在还累,一定要做好心理准备哦。总体来说,我给你现在的状态打满分,毕竟你已经学会了那么多的东西,也是时候放松一下了。自 己适当控制一下休息的时间,然后我们继续前进吧!

我的总结:

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

你可能感兴趣的:(第一行代码,第一行代码知识点,Android,第一行代码,第二章)