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个参数:

  1. 第一个参数是Context ,也就是Toast要求的上下文,由于活动本身就是一个Context 对象,因此这里直接传入FirstActivity.this 即可。
  2. 第二个参数是Toast显示的文本内容。
  3. 第三个参数是Toast显示的时长,有两个内置常量可以选择Toast.LENGTH_SHORT 和Toast.LENGTH_LONG。

开发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>

  1. android:id:给这个菜单项指定一个唯一的标识符
  2. 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有两种方法:

  1. 点击Back键便可销毁当前的Activity
  2. 在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中生存期:

  1. 完整生存期:Activity在onCreate() 方法和onDestroy() 方法之间所经历的,就是完整生存期。
  2. 可见生存期:Activity在onStart() 方法和onStop() 方法之间所经历的,就是可见生存期。
  3. 前台生存期:Activity在onResume() 方法和onPause() 方法之间所经历的就是前台生存期。

Android官方提供了一张Activity的生命周期示意图如下所示:
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模式的原理示意图如下:
standard模式

singleTop

Activity的启动模式指定为singleTop,在启动Activity时如果发现返回栈的栈顶已经是该Activity,则认为可以直接使用它,不会再创建新的Activity实例。当Activity不在返回栈栈顶时仍然会在创建新的Activity实例。
singleTop模式的原理示意图如下:
singleTop模式

singleTask

Activity的启动模式指定为singleTask,每次启动该Activity时系统首先会在返回栈中检查是否存在该Activity的实例,如果发现已经存在则直接使用该实例,并把在这个Activity之上的所有Activity统统出栈,如果没有发现就会创建一个新的Activity实例。
singleTask模式的原理示意图如下:
singleTask模式

singleInstance

指定为singleInstance模式的Activity会启用一个新的返回栈来管理这个Activity,这种模式下会有一个单独的返回栈来管理这个Activity,不管是哪个应用程序来访问这个Activity,都共用的同一个返回栈。这种模式解决了共享Activity实例的问题。
singleInstance模式的原理示意图如下:
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启动活动

全部评论

相关推荐

1 收藏 评论
分享
牛客网
牛客企业服务