Android学习羁绊之Activity
Activity是Android系统的四大组件之一,Activity是用户可操作的可视化界面,为用户提供一个完成操作指令的窗口,一个Activity通常是一个单独的屏幕。一个应用程序可以包含零个或者多个Activity(零个Activity的应用程序并不常见),接下来学习一下Activity。
Activity的基本用法
创建Activity
在Android项目中添加一个Activity,需要重写Activity的onCreate()方法,一般使用Android Studio创建的Activity都会帮我们重写这个方法,代码如下:
public class FirstActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } }
默认的onCreate()方法只是调用父类的onCreate()方法,需要自己去添加自己的逻辑
创建和加载布局
Android程序设计讲究逻辑和视图分离,一个Activity一般对应一个布局,布局就是用来显示界面内容的。使用Android Studio创建Activity时可以同时为我们创建布局,当然也可以手动来创建布局。在res文件夹下创建Layout resource file,布局文件是个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/button" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="button" /> </LinearLayout>
在这个布局文件中添加了一个按钮,Button元素中有以下这些属性(当然其他布局元素中也有这些属性):
- android:id: 给当前的元素定义一个唯一标识符,之后可以在代码中对这个元素进行操作。如果要在XML中应用一个id,则使用“@id/button”这种语法;如果要在XML中定义一个id,则使用“@+id/button”这种语法。
- android:layout_width: 指定了当前元素的宽度。match_parent表示让当前元素和父元素一样宽。
- android:layout_height: 指定了当前元素的高度。wrap_content 表示当前元素的高度只要能刚好包含里面的内容就行.
- android:text: 指定了元素中显示的文字内容
创建完布局,需要布局和Activity关联,在Activity中onCreate()方法中添加如下代码
public class FirstActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.first_layout); } }
- setContentView()方法给当前活动加载一个布局,一般这个方法都需要传入一个布局文件的id(这个id是唯一的)。
- 项目中添加的任何资源都会在R文件中生成一个相应的资源id,所以在setContentView()方法中可以直接传入布局文件id:R.layout.first_layout(R是res文件夹,也就是R文件;layout是指res文件夹中的子文件夹,也就是你存放布局文件的路径文件夹;first_layout就是所要引用的布局文件的文件名;布局文件id是文件路径+文件名构成的,所以可以存在多个同名布局文件,但需要放置到不同文件夹下)
注册Activity
创建任何Activity,都需要在AndroidManifest.xml中进行注册才能生效。AndroidManifest.xml文件内容如下:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="cn.chenjianlink.android.activitytest"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" 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>
Activity注册声明需要放在 <application>标签中,通过<activity>标签进行注册。
<activity>标签中有这些属性:</activity></activity></application>
- android:name: 用来指定具体注册哪一个Activity,代码中的.FirstActivity是cn.chenjianlink.android.activitytest.FirstActivity的缩写,因为在在最外层的
<manifest>标签中已经通过package属性指定了程序的包名是cn.chenjianlink.android.activitytest,因此在注册活动时这一部分就可以省略了,直接使用.FirstActivity 就足够了。</manifest> - android:label: 指定Activity中标题栏的内容,标题栏是显示在界面最顶部的。
注册完Activity后还需指定哪个Activity是主Activity,不然程序无法启动。指定哪个Activity为主Activity需要在<activity>标签内添加<intent-filter>标签,在这个标签中添加以下声明:</intent-filter></activity>
<intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter>
这样,程序就能运行了。程序运行时会首先打开First要调用Menu的Activity中重写onCreateOptionsMenu() 方法。有些应用程序并没有指定任何一个Activity为主Activity,这种程序也能运行,只不过在启动器中无法看到或者打开这个程序,一般这种程序都是作为第三方服务供其他内部进行调用。
Toast
Toast是Android系统提供的一种非常好的提醒方式,在程序中可以使用它将一些短小的信息通知给用户,这些信息会在一段时间后自动消失,并且不会占用任何屏幕空间。
Toast的用法非常简单,通过静态方法makeText() 创建出一个Toast 对象,然后调用show()将Toast显示出来就可以了。
makeText() 方法需要传入3个参数:
- 第一个参数是Context ,也就是Toast要求的上下文,由于活动本身就是一个Context 对象,因此这里直接传入FirstActivity.this 即可。
- 第二个参数是Toast显示的文本内容。
- 第三个参数是Toast显示的时长,有两个内置常量可以选择Toast.LENGTH_SHORT 和Toast.LENGTH_LONG。
Menu
开发Android程序时,往往会用到菜单,Android给开发者提供了一种菜单,既可以让菜单内容充分显示,又不占任何屏幕空间。
Menu是使用xml文件来表示的,代码如下:
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:app="http://schemas.android.com/apk/res-auto" 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>标签用来创建具体的某一个菜单项,<item>中有以下两个属性:</item></item>
- android:id:给这个菜单项指定一个唯一的标识符
- android:title:给这个菜单项指定一个名称
Menu布局文件创建好了,需要在要调用Menu的Activity中重写onCreateOptionsMenu()方法,代码如下:
@Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; }
- getMenuInflater()方法能够得到MenuInflater 对象,再调用它的inflate() 方法就可以给当前活动创建菜单了
- inflate() 方法接收两个参数,第一个参数用于指定通过哪一个资源文件来创建菜单。第二个参数用于指定菜单项将添加到哪一个Menu对象当中,这里直接使用onCreateOptionsMenu()方法中传入的menu参数
- 这个方法返回true,表示允许创建的菜单显示出来,如果返回了false,创建的菜单将无法显示。
现在菜单可以显示出来了,但菜单还需要添加响应事件才能使菜单真正可用。在调用Menu的Activity中重写onOptionsItemSelected() 方法:
@Override public boolean onOptionsItemSelected(@NonNull 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 clicked Remove", Toast.LENGTH_SHORT).show(); break; default: } return true; }
通过item.getItemId()来判断点击的是哪一个菜单项,在对应的菜单项中添加对应的逻辑
销毁Activity
有创建Activity的方法,那必有销毁Activity的方法。销毁Activity有两种方法:
- 点击Back键便可销毁当前的Activity
- 在Activity中使用finish()方法,就可销毁当前的Activity,这个效果与Back键是一样的。
Intent
Intent是Android程序中各组件之间进行交互的一种重要方式,它不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据。Intent一般可被用于启动活动、启动服务以及发送广播等场景。
Intent一般分为显式Intent和隐式Intent。
显式Intent
显示Intent在启动时直接指定了启动哪一个Activity,同时指定了启动Activity的上下文。
Intent(Context packageContext, Class<?> cls)这个构造函数接收两个参数:第一个参数Context要求提供一个启动活动的上下文,第二个参数Class则是指定想要启动的目标活动,通过这个构造函数就可以构建出Intent。
构建好Intent之后需要使用Activity类中提供了一个startActivity() 方法,这个方法是专门用于启动活动的。传入Intent对象就可启动对应的Activity。
startActivity(Intent intent)
通过Intent启动Activity代码大致如下:
Intent intent = new Intent(FirstActivity.this, SecondActivity.class); startActivity(intent);
通过这种方式可以通过显示Intent启动Activity(显示启动)。
隐式Intent
启动程序内Activity
和显示Intent不同,隐式Intent并没有指定要启动的Activity,它指定了抽象的action和catagory等信息,然后交给系统来分析,找到要合适的Activity。(能响应这个隐式Intent的所有Activity)
要隐式启动一个Activity,需要在AndroidManifest.xml文件中添加相关代码。如下所示:
<activity android:name=".SecondActivity"> <intent-filter> <action android:name="cn.chenjianlink.android.ACTION_START" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="cn.chenjianlink.android.MY_CATEGORY" /> </intent-filter> </activity>
- <action>标签中指定当前Activity可以响应的action, </action>
- <category>标签包含了附加信息,指明了当前Activity能够响应的Intent可能带有的category </category>
- 只有<action>和<category>中的内容同时匹配Intent指定的action和category时,这个Activity才能响应Intent。</category></action>
对应的Intent代码如下:
Intent intent = new Intent("cn.chenjianlink.android.ACTION_START"); //intent.addCategory("android.intent.category.DEFAULT"); 默认category,可以不写 intent.addCategory("cn.chenjianlink.android.MY_CATEGORY"); startActivity(intent);
- Intent(String action)指定了启动能响应指定action的Activity(action是字符串)。每个Intent只能指定一个action。
- 每个Intent能指定多个category,当category指定的是android.intent.category.DEFAULT(一种默认的category),在调用startActivity() 方法的时候会自动将这个category添加到Intent中;如果是自定义的category,不仅需要在AndroidManifest.xml文件中指定category,还需要在构建intent后使用addCategory()将自定义的category添加进去。
- 主Activity中使用了隐式Intent,所以Android应用程序能够在启动时直接调用主Activity:
<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>
上述声明就是通过隐式Intent将FirstActivity声明为主Activity。
启动其他程序的Activity
隐式Intent不仅可以启动程序内的Activity还可以启动其他程序的Activity
Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse("http://www.baidu.com")); startActivity(intent);
上述代码可以调用系统的浏览器来打开网页(这里当然是百度一下了)。
- Intent的action是Intent.ACTION_VIEW,这是一个Android系统内置的动作,其常量值为android.intent.action.VIEW。
- Uri.parse()方法将网址字符串解析成uri对象
- Intent的setData()方法将这个Uri对象传递进去
与上面对应,可以在AndroidManifest.xml中配置Activity能够响应哪些类型的数据。
<activity android:name=".ThirdActivity"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <data android:scheme="http" /> </intent-filter> </activity>
上述配置指定了ThirdActivity响应的action是android.intent.action.VIEW(一般使用这个action响应其他程序的调用),响应数据的协议是http。
在<intent-filter>标签中配置<data>标签用于更精确地指定当前活动能够响应什么类型的数据。
<data>标签可配置的内容如下:</data></data></intent-filter>
- android:scheme:用于指定数据的协议部分。(http,geo,tel等)
- android:host:用于指定数据的主机名部分。
- android:port:用于指定数据的端口部分,一般紧随在主机名之后。
- android:path:用于指定主机名和端口之后的部分,如一段网址中跟在域名之后的内容。
- android:mimeType:用于指定可以处理的数据类型,允许使用通配符的方式进行指定。
传递数据
Intent还可以在启动Activity时传递数据。使用如下代码搭配就可以实现:
//发送端 String data = "Hello SA"; Intent intent = new Intent(FirstActivity.this, SecondActivity.class); intent.putExtra("extra_data",data); startActivity(intent); //接收端 Intent intent = getIntent(); String data = intent.getStringExtra("extra_data");
- putExtra()方法接收两个参数,第一个参数是键,用于后面从Intent中取值,第二个参数才是真正要传递的数据(putExtra()方法有多种重载,可以传递不同类型的数据)
- getIntent()方法可获取到用于启动当前Activity的Intent,调用getStringExtra() 方法,传入相应的键值,就可以得到传递的数据了(同理,传递的是整型数据,则使用getIntExtra() 方法)
Intent可以传递数据给下一个Activity,也可以返回数据给上一个活动。见如下代码:
/* * 调用方 */ //关键代码 Intent intent = new Intent(FirstActivity.this, SecondActivity.class); startActivityForResult(intent, 1); //必须重写该方法 @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case 1: if (requestCode == RESULT_OK) { String returnData = data.getStringExtra("data_return"); Log.d("FA:", returnData); } break; default: } } /********************************************************************************/ /* *被调用方 */ //核心代码 intent.putExtra("data_return", "Hello FirstActivity"); setResult(RESULT_OK, intent); finish(); //最好要重写这个方法、 @Override public void onBackPressed() { Intent intent = new Intent(); intent.putExtra("data_return", "Hello FirstActivity"); setResult(RESULT_OK, intent); finish(); }
Activity中有一个startActivityForResult()方法也是用于启动Activity的,但这个方法期望在Activity销毁的时候能够返回一个结果给上一个Activity
startActivityForResult(Intent intent, int requestCode);
startActivityForResult()方法传递两个参数:Intent对象和请求码,请求码用于回调中判断数据来源。setResult()方法用于向上一个Activity返回数据。
setResult()方法接收两个参数,第一个参数用于向上一个活动返回处理结果,一般只使用RESULT_OK 或RESULT_CANCELED 这两个值,第二个参数则把带有数据的Intent传递回去。在当前Activity被销毁之后会回调上一个Activity的onActivityResult()方法,故需要重写上一个Activity的onActivityResult()方法
onActivityResult()方法带有三个参数,第一个参数requestCode,即在启动Activity时传入的请求码。第二个参数resultCode ,即在返回数据时传入的处理结果。第三个参数data,即携带着返回数据的Intent有时用户通过Back键销毁Activity,这样要返回数据就还需重写onBackPressed()方法,将相关逻辑添加到onBackPressed()方法中
Activity的生命周期
返回栈
Android中的Activity是可以层叠的,每启动一个新的Activity,就会覆盖在原Activity之上,然后点击Back键会销毁最上面的Activity,下面的一个Activity就会重新显示出来。
Android是使用任务(Task)来管理Activity的,一个任务就是一组存放在栈里的Activity的集合,这个栈也被称作返回栈(Back Stack)。在默认情况下,每当启动了一个新的Activity,它会在返回栈中入栈,并处于栈顶的位置。而每当按下Back键或调用finish() 方法去销毁一个Activity时,处于栈顶的Activity会出栈,这时前一个入栈的Activity就会重新处于栈顶的位置,系统总是会显示处于栈顶的Activity给用户。
下图展示了返回栈的工作原理:
Activity的状态
运行状态
当一个Activity位于返回栈的栈顶时,这时这个Activity就处于运行状态。Android系统不会回收处于运行状态的Activity。
暂停状态
当一个Activity不再处于栈顶位置,但仍然可见时,这时Activity就进入了暂停状态。处于暂停状态的Activity仍然是完全存活着的,Android系统一般不会回收这种Activity,只有在内存极低的情况下,Android系统才会去回收这种Activity。
停止状态
当一个Activity不再处于栈顶位置,并且完全不可见的时候,就进入了停止状态。Android系统仍然会为这种Activity保存相应的状态和成员变量,当其他地方需要内存时,处于停止状态的Activity有可能会被系统回收。
销毁状态
当一个Activity从返回栈中移除后就变成了销毁状态。系统会优先回收销毁状态的Activity,从而保证手机的内存充足。
Activity的生存期
Activity类中定义了7个回调方法,覆盖了活动生命周期的每一个环节:
- onCreate() :在Activity第一次被创建的时候调用,通过这个方法完成对Activity的初始化。
- onStart() :这个方法在Activity由不可见变为可见的时候调用。
- onResume() :这个方法在Activity准备好和用户进行交互的时候调用。此时的Activity一定位于返回栈的栈顶,并且处于运行状态。
- onPause() :这个方法在系统准备去启动或者恢复另一个Activity的时候调用。这个方法一般用于释放部分资源保留重要数据,执行速度一定要快,不然会影响到新的栈顶Activity的使用
- onStop() :这个方法在Activity完全不可见的时候调用。与onPause() 方法的主要区别在于,如果启动的新Activity是一个对话框式的Activity,那么onPause() 方***得到执行,而onStop() 方法并不会执行
- onDestroy() :这个方法在Activity被销毁之前调用,之后Activity的状态将变为销毁状态。
- onRestart() :这个方法在Activity由停止状态变为运行状态之前调用,也就是Activity被重新启动了。
除了onRestart()方法,其他方法都是两两相对的。
可以将Activity分为3中生存期:
- 完整生存期:Activity在onCreate() 方法和onDestroy() 方法之间所经历的,就是完整生存期。
- 可见生存期:Activity在onStart() 方法和onStop() 方法之间所经历的,就是可见生存期。
- 前台生存期:Activity在onResume() 方法和onPause() 方法之间所经历的就是前台生存期。
Android官方提供了一张Activity的生命周期示意图如下所示:
保存被回收Activity的数据
Activity处于停止状态时,可能被系统回收,Activity中提供了一个onSaveInstanceState()回调方法,这个方法可以保证在Activity被回收之前一定会被调用,可以通过这个方法来保存Activity被回收时保存临时数据。
需要重写Activity的onSaveInstanceState()方法,同时在onCreate()获取保存的数据,相关代码如下:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.first_layout); if (savedInstanceState != null) { //相关处理逻辑 String tempData = savedInstanceState.getString("data_key"); } } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); String tempData = "Something you just typed"; outState.putString("data_key", tempData); }
- onSaveInstanceState()方法携带一个Bundle类型的参数,Bundle类提供了一系列的方法用于保存数据,保存字符串使用putString()方法,保存整型数据使用putInt()方法,以此类推。每个保存方法需要传入两个参数,第一个参数是键,用于后面从Bundle中取值,第二个参数是真正要保存的内容。
- 当重新创建Activity时,可以判断onCreate()传入的Bundle对象是否为空(通常情况下为空),如果在Activity被系统回收之前有过onSaveInstanceState()方法来保存数据,这个参数就会带有之前所保存的全部数据,依次读取这些数据即可。
Activity的启动模式
Activity启动模式一共有4种,分别是standard、singleTop、singleTask和singleInstance,可以在AndroidManifest.xml中通过给<activity>标签指定android:launchMode属性来选择启动模式</activity>
standard
standard是Activity默认的启动模式,在不进行显式指定的情况下,所有Activity都会自动使用这种启动模式。在standard模式下,每当启动一个新的Activity,它就会在返回栈中入栈,并处于栈顶的位置。对于使用standard模式的Activity,系统不会在乎这个Activity是否已经在返回栈中存在,每次启动都会创建该Activity的一个新的实例。
standard模式的原理示意图如下:
singleTop
Activity的启动模式指定为singleTop,在启动Activity时如果发现返回栈的栈顶已经是该Activity,则认为可以直接使用它,不会再创建新的Activity实例。当Activity不在返回栈栈顶时仍然会在创建新的Activity实例。
singleTop模式的原理示意图如下:
singleTask
Activity的启动模式指定为singleTask,每次启动该Activity时系统首先会在返回栈中检查是否存在该Activity的实例,如果发现已经存在则直接使用该实例,并把在这个Activity之上的所有Activity统统出栈,如果没有发现就会创建一个新的Activity实例。
singleTask模式的原理示意图如下:
singleInstance
指定为singleInstance模式的Activity会启用一个新的返回栈来管理这个Activity,这种模式下会有一个单独的返回栈来管理这个Activity,不管是哪个应用程序来访问这个Activity,都共用的同一个返回栈。这种模式解决了共享Activity实例的问题。
singleInstance模式的原理示意图如下:
开发Tips
知晓当前活动
- 新建一个class 继承AppCompatActivity
- 并在OnCreate()中获得当前实例的类名 getClass().getSimpleName
- 其他Activity继承这个class
随时随地退出程序:用专门的集合类对活动管理
- ActivityCollector类中用List暂存活动
- addActivity() 向List中添加一个活动
- removeActivity() 从List中移除活动
- finishAll() 全销毁
- 在想要退出的地方调用ActivityCollector.finishAll()
- 杀掉进程:killProcess(),这个方法有1个参数:进程id,通过myPid()方法获得当前程序的进程id
android.os.Process.killProcess(android.os.Process.myPid());
启动活动的最佳写法:actionStart()
在该方法中构建Intent,通过actionStart()传递需要的参数 最后调用startActivity启动活动