第2章 先从看得到的入手—探究活动
本章围绕着活动(Activity) 进行了详细的讲解,从活动的基本用法,到启动活动和传递数据的方式,再到活动的生命周期,以及活动的启动模式。既有理论又结合实践,不但告诉你是什么,还告诉你怎么用,什么情况用,学习完几乎可以掌握了关于活动的所有重要知识点。下面的笔记是我根据个人水平进行的记录。
活动是什么?
活动(Activity)是最容易吸引用户的地方,它是一种可以包含用户界面的组件,主要用于和用户进行交互。
活动的手动创建
在手动创建活动时,有三个勾选框,分别是:
- Generate Layout File:表示会自动为当前创建的activity创建对应的布局文件
- LauncherActivity :表示设置为当前项目的主活动
- Backwards Compatibility:表示会为项目启用向下兼容模式
在AndroidManifest文件中注册
所有的活动都要在AndroidManifest.xml文件中进行注册才能生效。
<activity android:name =".FirstActivity"></activity>
- .FirstActivity:就是‘包名.FirstActivity’的缩写而已,由于最外层的
<manifest>
标签中已通过package属性指定了程序的包名,因此在注册活动时,直接使用.FirstActivity就可以。配置主活动,代码如下:
<activity android:name = ".FirstActivity"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name = "android.intent.category.LAUNCHER"/> </intent-filter> </activity>
设置活动标题的内容:使用
andoird:label
,注意:给主活动指定的label不仅会成为标题栏中的内容,还会成为启动器(Launcher)中应用程序显示的名称。- 没有声明任何一个活动作为主活动,这个程序是可以正常安装的,只是你无法在启动器中看到或打开这个程序,这种程序一般都是作为第三方服务供其他应用在内部进行调用的,如支付宝快捷支付服务。
Toast用法:
Toast.makeText(FirstActvity.this,"BaLaBaLa",Toast.LENGTH_SHORT).show();
Toast通过静态方法makeText()创建出一个Toast对象,然后调用show()将Toast显示出来就可以了。
第一个参数:Toast要求的上下文;第二个参数是Toast显示的文本内容;第三个参数是Toast显示的时长。
在活动中使用Menu
创建菜单布局文件
首先在res目录下创建一个menu文件夹,右击res目录->New->Directory,接着再文件夹下创建菜单文件,右击menu文件夹->New->Menu resource file
<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
给这个菜单项指定一个名称。初始化菜单
回到需要使用菜单的activity中,重写
onCreateOptionsMenu(Menu menu)
方法,快捷键Ctrl+O
public boolean onCreateOptionsMenu(Menu menu){ //使用getMenuInflate()获取到MenuInflater对象,再调用inflate()方法来给当前活动创建菜单。 //param1:创建菜单的资源文件,param2:指菜单项添加到哪一个menu对象中,直接使用传入的menu getMenuInflater().inflate(R.menu.main,menu); //返回ture,表示允许创建的菜单显示出来,如果是false,创建的菜单无法显示 return true; }
定义菜单的响应事件
在使用菜单的activity中,重写
onOptionsItemSelected(MenuItem item)
方法:
public boolean onOptionsItemSelected(MenuItem item){ switch(item.getItemId()){ //通过item.getItemId()来判断我们点击的是哪一个菜单项 case R.id.add_item: //然后给每一个菜单项加上自己的处理逻辑 //处理逻辑 break; case R.id.remove_item: //处理逻辑 break; default: } return true; }
销毁一个活动
下需要执行销毁操作的地方使用
finish()
方法,即可是当前的活动被成功销毁,效果和按下Back键是一样的。
使用Intent在活动间穿梭
Intent :Android程序中各个组件之间交互的一种重要方式,它不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据。
显示Intent
Intent有多个构造函数的重载,其中一个是
Intent(Context packageContext,Class<?>)
,param1:要求提供一个启动活动的上下文,param2:指定想要启动的目标活动(这里只针对activity进行介绍),通过这个构造函数就可以构建出Intent的“意图”。
Intent intent = new Intent(FirstActivity.this,SecondActivity.class); startActivity(); //专门用于启动活动
隐式Intent
隐式Intent指定了一系列更为抽象的action和category等信息,然后交由系统去分析这个intent,并找出可以响应这个隐式intent的活动。
从下面的代码中可以看出,在FirstActivity中使用了intent的另一个构造方法,直接将action的字符串传入了进去,表明我们想要启动能够响应com.example.activitytest.ACTION_START这个action的活动。在SecondActivity注册时设置了<action>
和<category>
,此时FirstActivity的startActivity(intent)方法执行后,SecondActivity就会被启动。注意:只有当<action>
和<category>
中的内容同时能够匹配上intent中指定的action和category时,这个活动(SecondActivity)才能响应该intent。
android.intent.category.DEFAULT是一种默认的category,调用startActivity()方法时,会自动将这个category添加到intent中。
可以调用addCategory()
方法来添加一个category
//FirstActivity Intent intent = new Intent("com.example.activitytest.ACTION_START"); startActivity(intent) --------------------------------------------------------------------------------------------- //SecondActivity在Manifest.xml文件中的注册 <activity android:name=".SecondActivity"> <intent-filter> <action android:name="comm.example.activitytest.ACTION_START"/> //指明当前活动可以响应comm.example.activitytest.ACTION_START这个action <category android:name="android.intent.category.DEFAULT" /> //<category>包含一些附加信息,更精确地指明了当前的活动能够响应的Intent中还可能带有的category。 </intent-filter> </activity>
- 每一个intent中只能指定一个action,但却能指定多个category。
更多隐式Intent的用法
使用隐式Intent不仅能启动自己程序内的活动,也可以启动其他程序的活动,这使得Android多个应用程序之间的功能共享成为了可能。
使用隐式intent调用系统浏览器打开网页:
Intent i = new Intent(Intent.ACTION_VIEW); //系统内置的动作,其常量值为android.intent.action.View i.setData(Uri.parse("http://www.baidu.com"));//主要指当前Intent正在操作的数据,而这些数据通常都是以字符串的形式传入到Uri.parse()方法中解析产生的 startActivity(i);
与此对应,可以在
<intent-filter>
中再配置一个<data>
标签,用于更精确地指定当前活动能响应什么类型的数据。<data>
标签中主要可以配置以下内容:
- android:scheme:用于指定数据的协议部分,如http部分
- android:host:用于指定数据的主机名部分,如上例的www.baidu.com部分
- android:port:用于指数据的端口部分,一般紧随在主机名之后。
- android:path:用于指定主机名和端口之后的部分,如一段网址中跟在域名之后的部分。
- android:mimeType:用于指定可以处理的数据类型,允许使用通配符的方式进行指定。
只有
<data>
标签中指定的内容和Intent中携带的Data完全一致时,当前活动才能够响应该Intent。
一般在<data>
标签中都不会制定过多的内容,如调用系统浏览器的例子,其实只需要指定android:scheme为http,就可以相应所有的http协议的Intent了。
除了http协议外,还可以指定很多其他协议,比如geo表示显示地理位置,tel表示拨打电话
//调用系统拨号界面 Intent i = new Intent(Intent.ACTION_DIAL); //系统内置动作 i.setData(Uri.parse("tel:10086")); //指定协议时tel,号码是10086
向下传递数据
Intent提供了一系列putExtra()方法的重载,使用这个方法将想要传递的数据暂存在Intent中,启动另一个活动后,只需把数据从Intent中取出来。
FirstActivity省略代码如下:
String data = "Hello SecondActivity"; Intent intent = new Intent(FirstActivity.this,SecondActivity.class); intent.putExtra("extra_data",data); //第一个参数是键,第二个是真正要传递的数据 startActivity(intent);
SecondActivity省略代码如下:
Intent intent = getIntent(); //通过getIntent()方法获取到启动SecondActivity的Intent String data = intent.getStringExtra("extra_data");//得到传递的数据,如果传递整形就用getIntExtra();以此类推 Log.d("SecondActivity",data);
返回数据给上一个活动
使用StartActivityForResult()方法启动活动,当被启动的活动销毁时可以返回一个结果给上一个活动。这个方法接受两个参数,第一个是Intent,第二个参数是请求码,用于在之后的回调判断数据的来源。
FirstActivity省略代码如下:
Intent intent = new Intent(FirstActivity.this,SecondActivity.class); StartActivityForResult(intent,1); //请求码只要是唯一值就行,这里传入了1。
SecondActivity添加返回数据的逻辑,省略代码如下:
Intent intent = new Intent();//此intent只用于传递数据,所以没有任何“意图” intent.putExtra("data_return","Hello FirstActivity");//把要返回的数据存放在intent中 setResult(RESULT_OK,intent);//param1:用于向上一个活动返回处理结果,一般只用RESULT_OK或RESULT_CANCELED;param2:把带有数据的intent传回去。 finish();//销毁当前活动
在SecondAcitivit销毁之后会回调上一个活动的onActivityResult()方法,FirstActivity()省略代码如下:
//requestCode:启动活动时的请求代码 //resultCode:返回数据时传入的处理结果 //data:携带着返回数据的intent protected void onActivityResult(int requestCode,int resultCode,Intent data){ switch(requestCode){ //判断数据来源 case 1: if(resultCode == RESULT_OK){//判断处理结果是否成功 String returnData = data.getStringExtra("data_return"); //从data取值 } break; default: } }
点击back键返回
通过重写
onBackPressed()
方法来实现点击back键返回:
public void onBackPressed(){ Intent intent = new Intent(); intent.putExtra("data_return","Hello FirstActivity"); setResult(RESULT_OK,intent); finish(); }
活动的生命周期
返回栈
Android是使用任务(Task)来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈,也被称作为返回栈(Back Stack)
活动状态
每个活动在其生命周期中最多可能会有4中状态。
- 运行状态:当一个活动位于返回栈的栈顶时,这是活动就处于运行状态。系统最不愿意回收这个状态的活动。
- 暂停状态:当一个活动不再处于栈顶位置,但是仍然可见,这时活动处于暂停状态。仅当内存极低的情况下系统才考虑回收这种活动。
- 停止状态:当一个活动不再处于栈顶位置,并且完全不可见的时候,就进入停止状态。当其他地方需要内存时,处于停止状态的活动有可能被系统回收。
- 销毁状态:当一个活动从返回栈中移除后就变成了销毁状态。系统最倾向于回收处于这种状态的活动,从而保证手机的内存不足。
活动的生存期
生命周期
- onCreate():在活动第一次被创建的时候调用。应该在这个方法中完成活动的初始化,如加载布局绑定事件等。
- onStart():这个方法在活动由不可见变为可见的时候调用。
- onResume():这个方法在活动准备好和用户交互的时候调用。此时活动一定位于返回栈的栈顶,并且处于运行状态。
- onPause():这个方法在系统准备去启动或者恢复另一个活动的时候调用。通常在这个方法中将一些消耗CPU内存的资源释放掉,以及保存一些关键数据,但这个方法的执行速度一定要快,不然会影响到新的栈顶活动的使用。
- onStop():这个方法在活动完全不可见的时候调用。它和onPause()方法的主要区别在于,如果启动一个对话框式的活动,onPause()会执行,而onStop()不会执行。
- onDestroy():这个方法在活动被销毁之前调用,之后活动的状态将变为销毁状态。
- onRestart():这个方法在活动由停止状态变为运行状态之前调用,也就是活动被重新启动了。
生存期
- 完整生存期:活动在onCreate()和onDestroy()方法之间所经历的,就是完整生存期。一般情况下,一个活动在onCreate()方法中完成各种初始化操作,而在onDestroy()方法中完成释放内存的操作。
- 可见生存期:活动在onStart()方法和onStop()方法之间所经历的,就是可见生存期。在可见生存期内,活动对于用户总是可见的,即便有可能无法和用户进行交互。我们可以通过这两个方法,合理地管理那些对用户可见的资源。比如在onStart()方法中对资源进行加载,而在onStop()方法中对资源进行释放,从而保证处于停止状态的活动不会占用过多内存。
- 前台生存期:活动在onResume()方法和onPause()方法之间所经历的就是前台生存期。在前台生存期内,活动总是处于运行状态,此时活动是可以和用户进行交互的,我们平时看到和接触最多的也就是这个状态下的活动。
碎片知识点
- 将活动设置成对话框式:
在Manifest.xml文件的<activity>
标签中使用android:theme="@style/Theme.AppCompat.Dialog"
活动被回收了怎么办
Activity中还提供了一个
onSaveInstanceState()
回调方法,这个方法可以保证在活动被收回之前一定会被调用,因此可以通过这个方法来解决活动被回收时临时数据得不到保存的问题。保存数据
protected void onSaveInstanceState(Bundle outState){ super.onSaveInstanceState(outState); String tempData = "Something you just typed"; outState.putString("data_key",tempData); //键 - 值 }
恢复数据
在哪里进行恢复?
onCreate()
方法其实也有一个Bundle类型的参数。这个参数一般情况下都是null,但是如果在活动被系统回收之前有通过onSaveInstanceState()
方法来保存数据的话,这个参数就会带有之前所保存的全部数据,我们只需要再通过响应的取值方法将数据取出即可。
protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.activtiy.main); if(savedInstanceState!=null){ String tempData = savedInstanceState.getString("data_key"); //对取到的数据做逻辑处理,如重新复制到文本输入框上 } }
活动的启动模式
在实际的项目中我们应该根据特定的需求为每个活动指定恰当的启动模式。通过在Manifest.xml中设置
<activity>
标签的android:launchMode
属性来选择活动的启动模式
- standard:standard是活动默认的启动模式,在这种模式下每当启动一个新的活动,他就会在返回栈中入栈,并处于栈顶的位置。对于使用standard模式的活动,系统不会再回这个活动是否已经在返回栈中存在,每次启动都会创建该活动的一个新的实例。
- singleTop:在启动活动时如果发现返回栈的栈顶已经是该活动,则认为可以直接使用它,不会创建新的活动实例。不过当返回栈的栈顶不是要启动的活动时,还是会创建新的活动实例。
- singleTask:每次启动活动时系统首先会在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用该实例,并把在这个活动之上的所有活动统统出栈,如果没有发现就会创建新的活动实例。
- singleInstance:指定为singleInstance模式的活动会启动一个新的返回栈来管理这个活动(其实如果singleTask模式制定了不同的taskAffinity,也会启动一个新的返回栈)。使用场景:假设我们的程序中有一个活动时允许其他程序调用的,如果我们想实现其他程序和我们的程序可以共享这个活动的实例时,使用前面三种模式是做不到的,因为每个应用程序都会有自己的返回栈,同一个活动在不同的返回栈中入栈时必然是创建了新的实例。而使用singleInstance模式,会有一个单独的返回栈管理这个活动,不管是哪个应用程序来访问这个活动,都共用同一个返回栈。注:如果使用此模式的活动(B)被使用其他模式的活动(A)所启动,又在活动(B)启动另一个其他模式的活动(C)时,被启动的活动C还会在活动A的返回栈中入栈,当按下Back键进行返回时,退出的顺序应该是C->A->B。
活动的最佳实践
知晓当前是在哪一个活动
适合场景:当接手一个正在进行中的项目,阅读别人的代码时有一个很头疼的问题,当需要在某个界面修改一个非常简单的东西,却找不到这个界面对应的活动是哪个。
注:这里BaseActivity继承AppCompatActivity
//让BaseActivity成为所有活动的父类,不需要 public class BaseActivity extends AppCompatActivity{ protected void onCreate(Bundle saveInstanceState){ super.onCreate(Bundle saveInstanceState); Log.d(TAG,getClass.getSimpleName()); //获取当前实例的类名 } }
随时随地退出程序
如果目前返回栈中仍然存在很多的活动,你会发现当前想退出程序时非常 不方便的,需要连按N次Back键才行。按Home键只是把程序挂起,并没有退出程序。使用此方法即可解决:需要用一个专门的集合类对所有的活动进行管理
创建一个ActivityCollector作为活动管理器
public class ActivityCollector{ public static list<Activity> activities = new ArrayList<>(); //向List中添加一个活动 public static void addActivtiy(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()){ //检查活动是否在销毁的过程中,如果活动销毁了返回true,否则返回false activity.finish(); } } activities.clear();//清空List } }
BaseActivity代码如下:
public class BaseActivity extends AppCompatActivity{ protected void onCreate(Bundle saveInstanceState){ super.onCreate(Bundle saveInstanceState); Log.d(TAG,getClass.getSimpleName()); //获取当前实例的类名 ActivityCollector.addActivity(this);//表明将当前正在创建的活动添加到活动管理器里 } protected void onDestroy(){ super.onDestroy(); ActivityCollector.removeActivity(this);//将一个马上要销毁的活动从活动管理器中移除。(避免调用finishAll()方法时,再遍历到已经销毁的活动) } }
销毁所有活动代码(通过Button销毁):
button.setOnClickListner(new View.onClickListner(){ public void onClick(View v){ ActivityCollector.finishAll(); //销毁所有活动 android.os.Process.killProcess(android.os.Process.myPid());//杀掉当前进程,其中KillProcess()方法用于杀掉一个进程,它接受一个进程id参数,我们通过myPid()方法来获的当前程序的进程id } });
启动活动的最佳方法
当我们负责开发的部分需要启动AnotherActivity,而AnotherActivity需要两个非常重要的字符串参数,并且是由别人开发的,我们不清楚启动的这个的话需要传递哪些数据。为了减少反复阅读代码和反复询问同事的麻烦,使用以下代码进行解决:
在AnotherActivity中填写以下代码:
public class AnotherActivity extends BaseActivity{ ... public static void actionStart(Context context,String data1,String data2){ Intent intent = new Intent(context,AnotherActivity.class); intent.putExtra("param1",data1); intent.putExtra("param2",data2); context.startActivity(intent); } ... }
好处:一目了然,AnotherActivity所需要的数据在参数中全部体现出来了;简化了启动活动的代码
AnotherActivity.actionStart(FirstActivity.this,"data1","data2");
在每个活动都添加类似的启动方法,不仅可以让启动活动变得非常简单,而且节省了不少你同事过来询问你的时间。
小结:
做笔记的时间真的比看书的时间要长得多,不过效果还是很棒的,反复的巩固学过的知识,而且还能在写笔记的同事去思考每一句话。但是还是需要在实际应用中反复的实践,加深印象和理解。通过本章的学习,对活动(Activity)的认识更加深刻了,学习到了很多有用的实践技巧,这是在以前的学习中没有学到的,虽然有些累,但是感觉很充实,后面的章节随着学习还会持续更新。。。