Android四大组件Activity系列之一——概述

前言: 人总是理所当然的忘记,是谁风里雨里,一直默默的守护在原地。

一、概述

  Activity是Android应用的四大组件之一,它负责管理Android应用的用户界面。大多数应用有多个界面,这就意味着包括若干个Activity,每一个Activity组件负责一个用户界面的展示。通常应用中的某一个Activity被指定为主界面,这是应用用户启动时出现的第一个屏幕,然后每个Activity可以启动另一个Activity,以执行不同的操作。要在应用中使用,必须在清单文件中注册关于Activity的信息,并且适当地管理生命周期。

二、Activity的使用步骤

在Android应用中,创建Activity的步骤如下:

(1) 定义一个类继承AppCompatActivity或者是AppCompatActivity的子类;
(2)在res/layout目录中创建一个xml布局文件,用于创建Activity的布局;
(3)重写Activity的onCreate()方法,并在方法内setContentView()加载指定的布局文件;
(4)在清单文件AndroidManifest.xml中注册Activity。

(1) 要创建Activity必须是Activity的子类或者继承AppCompatActivity,命名方式通常是以xxxActivity方式命名:

//自定义Activity
public class MainActivity extends AppCompatActivity {

}

(2)在res/layout资源目录中创建一个xml布局文件,用于创建Activity的布局:
在这里插入图片描述
布局文件的命名方式通常是以activity开头,以下划线_分隔,注意名字不能有中文和大写字母,否则会报错,下面是activity_main.xml布局文件:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
    	android:id="@+id/tv_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="我是一个新的Activity!"
        android:textSize="24sp"
        android:textColor="@color/colorPrimary"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

布局文件里面只定义了一个TextView,当然你可以在里面定义你需要的布局界面。

(3)重写Activity中的onCreate()方法,并在方法setContentView()内加载指定的布局文件;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView tv_name = findViewById(R.id.tv_name);
    }
}

onCreate()方法是必须实现的生命周期方法(后续会讲解到),是Activity的初始化活动,系统在Activity创建的时候先调用这个方法,必须在此方法内调用setContentView()加载布局文件,用来定义Activity用户界面布局,也是在onCreate()方法内findViewById()查找Activity的必须要组件,但是还不能直接启动,需要在清单文件中注册Activity,否则会报错。

(4)在清单文件AndroidManifest.xml中注册你已经创建的Activity。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.sum.activitydemo">

    <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=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

<application>节点内添加Activity节点并声明name属性<activity android:name=".MainActivity"></activity>完成Activity的注册。

那么一个完整的Activity的创建流程,效果如上图,一个简单的用户界面创建完成。

三、Activity注册清单文件

AndroidManifest.xml是每个安卓程序必须的文件,向安卓系统提供应用的必要信息,系统必须具有这些信息方可运行引用的任何代码。它位于整个项目的根目录,描述了package中暴露的组件(Activity、Service、BroadcastReceiver等等),声明他们各自的实现类,各种能被处理的数据和启动位置,还自定权限和instrumentation(安全控制和测试)。
在这里插入图片描述
AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.sum.activitydemo">

    <application
    	android:name=".App"//指定应用类
        android:allowBackup="true"//关闭应用程序数据的备份和恢复功能
        android:icon="@mipmap/ic_launcher"//桌面应用启动图标
        android:label="@string/app_name"//应用名称
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"//是否支持从右到左布局,RTL就是right-to-left
        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>

3.1 元素

只有<manifest><application>元素是必须的,他们都必须存在并且只能出现一次。其他大部分元素可以出现多次或者可以不出现,但清单文件必须至少存在其中某些元素才有用。如果一个元素包含某些内容也就包含其他元素,所有值均通过属性设置,而不是通过元素内的字符数据设置。

同一级别的元素不分先后顺序,比如<activity><service>可以按照任意顺序混合在一起,这条规则有两个主要例外:

  • <application>必须是<manifest>元素内的最后一个元素,即 </application>结束标签必须紧跟在</manifest>结束标签后;
  • <activity-alias>元素必须跟在别名所指定的<activity>之后。

3.2 属性

从某种意义上来说,所有属性都是可选的,但是必须指定某属性,元素才能实现其目的。对于真正可选的属性,它将指定默认值或声明指定规范时将执行何种操作。除了</manifest>的一些属性外,所有属性名称均以android:前缀开头,例如android:name=".MainActivity"。由于前缀是通用的,因此在按名称引用属性时,本文通常会将其忽略。

3.3 声明类名

许多元素对应java对象,包括应用本身的元素<application>及其主要组件:Activity (<activity>)、服务 (<service>)、广播接收者 (<receiver>) 、内容提供者 (<provider>)。如果按照你针对组件类几乎一致采用的方式来定义子类,则该子类需要通过name属性来声明,该名称必须包含完整的应用包名称。例如:Activity的子类声明如下:

<manifest ······>
    <application ······>
        <!--com.sum.activitydemo 是应用包名-->
        <activity android:name="com.sum.activitydemo.MainActivity">
        </activity>
    </application>
</manifest>

但是如果声明类名字符串第一个字符是句点,则应用包名的名称(<manifest>元素中指定的package属性的值)将附加到该字符串,如下赋值和上述方法相同:

<manifest ······>
    <application ······>
        <!--.MainActivity前会被应用包名com.sum.activitydemo替代-->
        <activity android:name=".MainActivity">
        </activity>
    </application>
</manifest>

3.4 标签解析

下面分别来讲解下<manifest>标签、<application>标签、<activity>标签的相关属性:

(1)<manifest>标签是整个清单文件的最上层,用来做一下最基本的声明,比如包名、权限、命名空间等:

  • package=“com.sum.activitydemo”     整个应用程序的包名,package表示属性名称,com.sum.activitydemo表示属性值。
  • xmlns:android=“http://schemas.android.com/apk/res/android”     声明命名空间,使各种Android系统级的属性能让我们使用。

(2)<application>标签一个清单文件必须含有一个application标签,这个标签声明每一个应用程序的组件以及属性:

  • android:name=“App”               <application>标签下的android:name属性表示该应用中的指定应用类,App类是继承Application,用于初始化化应用相关操作;
  • android:icon="@mipmap/ic_launcher"   表示应用的启动图标;
  • android:label="@string/app_name"     表示应用程序名称;
  • android:theme="@style/AppTheme"    表示当前应用主题,每个Activity都默认为该主题;
  • android:allowBackup=“true”       表示开启应用程序数据的备份和恢复功能,默认为true。

(3)<activity>标签表示指定单个Activity组件。通过android:name属性来指定具体的Activity。还可以通过<Intent-filter>匹配规则。

注意:如果一个Activity被指定为主Activity,那么需要在Activity节点内设置<intent-filter>子节点,声明动作action和动作属性的类型category。(下面会讲解到)

<intent-filter>
 	   <!--主界面,程序入口-->
       <action android:name="android.intent.action.MAIN" />
       <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

android.intent.action.MAIN表示主要入口,android.intent.category.LAUNCHER表示动作在最顶层,共同表示应用入口,将应用注册到系统列表中,缺一不可。

四、Activity中使用Intent

  在Android应用中组件间完成通信功能,此时就需要使用到Intent,Intent叫意图,是执行一个操作的抽象描述。一个意图提供了一个工具,用于在不同应用程序的代码之间执行后期运行时绑定。它最重要的用途是启动Activity,可视为Activity之间的粘合剂。它基本上是一个被动的数据结构,包含要执行的操作的抽象描述。通常是绑定应用组件,并在应用程序间进行通信,Intent一般用于启动Activity,启动服务,发送广播,负责安卓应用程序三大核心组件相互间的通信功能。

4.1显式和隐式意图

Intent寻找目标组件的方式有两种,一种是显式意图,一种是隐式意图。启动Activity的API如下:

  • startActivity(Intent intent)    表示开启一个新的Activity,Intent 表示开始的意图,意图是执行一个操作的抽象描述。

创建Intent的方式有很多种,我们可以根据实际情况来选择使用:

//根据给出的动作创建一个意图
Intent(String action)
//给定的动作和给定数据的url创建一个意图
Intent(String action, Uri uri)
//为一个特定的组件创建一个意图
Intent(Context packageContext, Class<?> cls)
//为一个特定的组件和数据创建一个使用指定的操作意图
Intent(String action, Uri uri, Context packageContext, Class<?> cls)

(1)显式意图

  显式意图就是通过Intent寻找目标组件时,需要明确指定目标组件的名称。显示意图适用于同一应用程序或者不同应用程序且知道包名类名的组件间跳转。

方式一:跳转同一项目下的Activity,直接指定该Activity的字节码即可。按照上面Activity的创建步骤在app Module中创建一个新的Activity(FirstActivity),并在清单文件中注册:

public class FirstActivity extends AppCompatActivity{
    private static final String TAG = "FirstActivity";

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_first);
    }
}

在MainActivity中点击按钮跳转到FirstActivity中,代码如下(源码在文章最后给出):

public class MainActivity extends AppCompatActivity{
	······
 	//创建意图,直接指定该Activity的字节码
 	Intent intent = new Intent(MainActivity.this, FirstActivity.class);
 	//跳转Activity
 	startActivity(intent);
}

从MainActivity界面跳转到FirstActivity界面就是明确指定了目标组件FirstActivity.class,效果如下:

方式二:跳转到已安装的其他应用的Activity,需要指定应用包名和该类的全局类名,这种适用于已安装第三方应用程序且知道界面类名的情况。通过setClassName()来指定应用程序包名和类名从而指定了Intent:

  • setClassName(String packageName, String className)    指定显式应用程序包名和类名。packageName表示所需包的名称,即包名;className表示应用程序包内将用作此Intent的组件的类的名称,即类名。

我们首先要创建新的应用程序(应用2),直接在项目中创建了一个新的Module(OtherModule,相当于一个新的应用程序),然后创建新的Activity(OtherExplicitActivity),该应用要先启动并且存在手机中。
在这里插入图片描述然后在应用1中MainActivity,点击按钮后,跳转到应用2中的OtherExplicitActivity,代码如下:

public class MainActivity extends AppCompatActivity{
  	······
 	Intent intent2 = new Intent();
	//指定显式应用程序包名和类名
	intent2.setClassName("com.sum.othermodule", "com.sum.othermodule.OtherExplicitActivity");
	startActivity(intent2);
}

效果如下:
在这里插入图片描述
可以看到,从ActivityDemo应用成功跳转到OtherModule应用中的OtherExplicitActivity。
注意:被跳转的界面OtherExplicitActivity需要在清单文件中添加android:exported="true"属性,表示该Activity可以被其他应用访问。

<activity
    android:name="com.sum.othermodule.OtherExplicitActivity"
    android:exported="true" />

如果不添加android:exported="true"属性会报错,报错信息如下:在这里插入图片描述
(2)隐式意图

  隐式意图没有明确指定Intent指定的目标组件,Android系统会根据隐式意图提供的参数(动作action ,类别category ,数据data),通过匹配机制Android系统能根据Intent中的数据信息找到需要启动的组件,如果找到多个适合的组件,系统会显示一个对话框,提示用户选择哪一个适合的组件。这种匹配机制是通过隐式Intent中的数据信息与启动的系统组件的Intent过滤器(Intent Filter)中的信息相匹配来实现的。

  • action      表示可执行的动作,意图的任意名称,String类型,任意值。隐式调用时Intent必须setAction(),一个过滤器中可以有多个action属性,Intent和其中任意一项equal匹配一致就算成功;
  • category     表示动作所属的类别,提供了有关意图执行的action的附加详细信息,也是String类型,添加匹配时必须和过滤器中定义的值相同,当我们不为Intent主动addCategory()时,系统会帮我们添加一个默认值android.intent.category.DEFAULT,如果需要我们自己写的Activity需要接受隐式Intent启动,必须在它的过滤器中添加android.intent.category.DEFAULT,否则无法启动成功。
  • data        表示给Intent的数据信息,不同的动作有着不同的数据。data可以分为URI和mimeType两部分,URI由android:scheme,android:host,android:port等属性组成,scheme代表模式,常用的有http,content,file,package等,host是主机地址,port是端口号。mimeType指定媒体格式类型,音频,文件,图片都有特定的属性值。

方式一:隐式意图跳转系统的界面,设置action ,category ,data来找到具体的组件。通过匹配机制Intent中的数据信息需要与启动的系统组件的Intent过滤器(Intent Filter)中的信息相匹配。

public class MainActivity extends AppCompatActivity{
	······
	//隐式意图跳转到系统的界面,发送信息
	Intent i = new Intent();
	i.setAction(Intent.ACTION_SENDTO);//发送信息action
	i.addCategory("android.intent.category.DEFAULT");//默认Category
	i.setData(Uri.parse("smsto:10086"));//设置动作附加的数据,Uri.parse()将字符串转成URI对象
	startActivity(i);
}

Intent.ACTION_SENDTO表示系统中定义向数据提供的联系人发送信息,这里演示了一个调起系统发送信息界面发送信息,效果如下:
在这里插入图片描述
提供部分系统的action说明:

动作 说明
ACTION_SEARCH 启动一个Activity,执行搜索动作
ACTION_CALL 打开拨号盘界面并拨打电话,使用Uri中的数字部分作为电话号码
ACTION_SENDTO 启动一个Activity,向数据提供的联系人发送信息
ACTION_WEB_SEARCH 打开一个Activity,对提供的数据进行Web搜索
ACTION_VIEW 最常用的动作,对以Uri方式传送的数据,根据Uri协议部分以最佳方式启动相应的Activity进行处理。对于http:address将打开浏览器查看;对于tel:address将打开拨号界面并呼叫指定的电话号码

方式二:实现了隐式调用系统的Activity,也可以实现隐式调用自定义的Activity。要让一个Activity可以被隐式启动,则(该应用存在手机中)需要在被调用的Activity的清单文件中Activity节点中添加<intent-filter>子节点action和category等相关信息:
应用OtherModule中的OtherHideActivity:
在这里插入图片描述清单文件AndroidManifest.xml的配置:

<activity android:name="com.sum.othermodule.OtherHideActivity">
	<intent-filter>
    	<action android:name="com.sum.othermodule.hide" />
    	<category android:name="android.intent.category.DEFAULT" />
	</intent-filter>
</activity>

在应用ActivityDemo中点击按钮,跳转到应用OtherModule中的OtherHideActivity:

public class MainActivity extends AppCompatActivity{
	······
    //隐式意图跳转到自定义的界面,OtherModule中的OtherHideActivity
    Intent i2 = new Intent();
    i2.setAction("com.sum.othermodule.hide");//必须与清单文件中的action值相匹配
    i2.addCategory("android.intent.category.DEFAULT");//必须与清单文件中的category值相匹配
    startActivity(i2);
}

隐式启动Activity需要为Intent设置action ,category ,data属性,且值必须与被启动的Activity的清单文件中<intent-filter>定义的属性完全匹配 ,即MainActivity中Intent的action值与OtherHideActivity清单文件<intent-filter>节点下的action值一致,category也需要一致,效果如下:
在这里插入图片描述

4.2 Activity的数据传递

(1)数据传递

  实际应用中,要在Activity之间传递数据,Intent不仅可以启动Activity,也是Activity之间的数据传递的介质, 但是传递的数据是已经序列化的,未序列化的数据Intent不能传递。通过putExtra()添加拓展数据到Intent中,它其实是以key-value的形式保存数据到Bundle中,getXXXExtra()的形式在Intent中从Bundle里面将数据获取出来。下面列出比较常用的部分:

//添加数据到Intent中,name表示存值的key,value表示存储的数据值
putExtra(String name, String value);//传递字符串类型的数据
putExtra(String name, int value);//传递int类型的数据
putExtra(String name, boolean value);//传递boolean类型的数据
putExtra(String name, Parcelable value);//传递Parcelable序列化的数据
putExtra(String name, Serializable value);//传递Serializable序列化数据
······

//获取Intent中的数据,name表示存值的key,defaultValue表示默认值
getStringExtra(String name);//获取字符串类型的数据
getIntExtra(String name, boolean defaultValue);//获取int类型的数据
getBooleanExtra(String name, boolean defaultValue);//获取boolean类型的数据
getParcelableExtra(String name);//获取Parcelable序列化的数据
getSerializableExtra(String name);//获取Serializable序列化数据
······

我们在跳转到FirstActivity的Intent中添加传递的数据:

public class MainActivity extends AppCompatActivity{
  	······
	Intent intent = new Intent(MainActivity.this, FirstActivity.class);
    intent.putExtra("name", "陈奕迅");//传递String类型
    intent.putExtra("type", 1001);//传递int类型
    intent.putExtra("person", new Person("歌神"));//传递对象类型,Person已序列化
    startActivity(intent);
}

在FirstActivity中获取Intent传递的数据:

public class FirstActivity extends AppCompatActivity {
    private static final String TAG = "FirstActivity";
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_first);

        String name = getIntent().getStringExtra("name");
        int type = getIntent().getIntExtra("type", 0);
        Person person = (Person) getIntent().getSerializableExtra("person");

        Log.e(TAG, "name == " + name);
        Log.e(TAG, "type == " + type);
        Log.e(TAG, "person == " + person);
    }
}

onCreate()方法中,通过getIntent()获取传递的意图Intent,getXXXExtra()获取对应类型key的数据,打印数据如下:
在这里插入图片描述
(2)数据回传

  某些情况下,一个Activity需要得到第二个Activity的数据,这种情况叫做数据回传。Android系统提供了一种startActivityForResult()方法来实现数据回传,Activity01需要实现onActivityResult()来监听Activity02的数据回传,当Activity02关闭前通过setResult()设置回传数据,调用finish()方法后,Activity01的onActivityResult()会被调用,即Activity01一直监听Activity02是否关闭。

  • startActivityForResult(Intent intent, int requestCode)  有结果返回的启动Activity请求。intent表示意图,requestCode表示请求码,用于判断同一个Activity跳转不同Activity的数据回传;
  • onActivityResult(int requestCode, int resultCode, Intent data) 监听您启动的Activity退出时调用,并提供启动该Activity时使用的requestCode、返回的resultCode和其中的任何附加数据。requestCode表示请求码,与上面的请求码一样,resultCode表示结果码,与下面的结果码一样,data表示返回的Intent,即携带返回的数据;
  • setResult(int resultCode, Intent data)   给Activity设置返回给其调用者的结果。resultCode表示返回码,data是一个意图,包含返回给调用者的数据结果。

在调用者Activity01中通过startActivityForResult()启动被调用者Activity02,在Activity01中实现onActivityResult()方法,监听Activity02是否关闭,关闭时回调:

public class MainActivity extends AppCompatActivity{
  ······
  //跳转FirstActivity,请求码为100
  Intent intent = new Intent(MainActivity.this, FirstActivity.class);
  startActivityForResult(intent, 100);//带回传结果的启动Activity

   //监听被调用者数据回传方法,即FirstActivity关闭时回调
   @Override
   protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == 100 && resultCode == 101 && data != null) {
            Log.e(TAG, "requestCode == " + requestCode + " | resultCode == " + resultCode + " | data == " + data.getStringExtra("return"));
        }
    }
}

被调用者Activity02在点击按钮,将回传数据保存到Intent中,通过setResult()设置回传Intent和resultCode,调用finish()方法销毁该Activity:

public class FirstActivity extends AppCompatActivity {
	······
    @Override
    public void onClick(View v) {
        Intent intent = getIntent().putExtra("return", "FirstActivity销毁后回传数据");
        setResult(101, intent);//给Activity设置返回给其调用者的结果
        finish();
    }
}

打印数据如下:
在这里插入图片描述
可以看到FirstActivity销毁后,MainActivity的onActivityResult()接收到了FirstActivity回传过来的数据,其中请求码与MainActivity中的请求码一致,结果码与FirstActivity中的结果码一致,这样可以在多个Activity回传数据时方便区分。

至此,本文结束!有关Activity的生命周期等将在下一篇讲解。


源码地址:https://github.com/FollowExcellence/ActivityDemo

请尊重原创者版权,转载请标明出处:https://blog.csdn.net/m0_37796683/article/details/105243356 谢谢!


相关文章:

Activity系列(一)

 ● Activity:创建步骤、清单文件注册、显式隐式意图、数据传递

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章