Android 四大组件之Activity详解

最近在整理Android方面的知识,也算是对Android知识的一个复习总结。

文章目录

  • [一、 Activity的概念](#一、 Activity的概念)
  • 二、Activity的创建与注册
    • [2.1、 利用系统创建Activity](#2.1、 利用系统创建Activity)
    • [2.2 、自己创建Activity](#2.2 、自己创建Activity)
  • 三、Activity的启动方式
    • [3.1 、显式启动](#3.1 、显式启动)
    • [3.2 、隐式启动](#3.2 、隐式启动)
    • [3.3 、MAIN](#3.3 、MAIN)
    • [3.4、 显式启动和隐式启动对比](#3.4、 显式启动和隐式启动对比)
    • [3.5、 启动其他应用的Activity](#3.5、 启动其他应用的Activity)
  • 四、activity的生命周期
  • 五、activity的四种状态
  • 六、Activity的栈式管理
  • [七 、Activity的启动模式](#七 、Activity的启动模式)
  • 八、其他
    • [8.1、 Activity跳转方法](#8.1、 Activity跳转方法)
    • [8.2、Intent 可以传递的数据类型:](#8.2、Intent 可以传递的数据类型:)
    • [8.3、 多个Activity跳转时的生命周期](#8.3、 多个Activity跳转时的生命周期)

一、 Activity的概念

Activity是Android组件中最基本也是最为常见用的四大组件之一,它提供一个可视化的用户界面,放置各种UI组件,与用户进行交互。一般来说,你所能看到界面都属于Activity。

二、Activity的创建与注册

2.1、 利用系统创建Activity

右击包名------New------Activity,或者左上角选择File------New------Activity,你会看到下发的界面;

然后我们一般选择Empty Activity,你会会看到类似于下面的界面;

输入Activity和布局的名字,☑️Generate a Layout 后,Android系统会自动为我们创建布局,且在AndroidManifest.xml文件中自动注册当前Activity。

2.2 、自己创建Activity

  • 右击包名------New------Java Class,或者左上角选择File------New------Java Class,然后输入你要创建的Activity名字,如MainActivity3;
  • 在AndroidManifest文件中application 标签中注册Activity;
java 复制代码
<activity android:name=".MainActivity3"/>
  • 右击layout------New------ Layout Resource File------输入布局名称,创建一个新布局;

  • 让MainActivity3继承Activity,重写onCreate()方法,并在onCreate()方法中用setContentView引用你刚才创建的布局;

java 复制代码
public class MainActivity3 extends AppCompatActivity {

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

三、Activity的启动方式

Activity的启动也可以叫做Activity间的跳转,是从一个Activity 跳转到另一个Activity。它的启动是通过Intent来表达的,Intent同时也是Android组件之间的通信媒介,专门提供组件互相调用的相关信息,Activity的启动方式,大的方向上可以分为两种: 显式启动和隐式启动。

原则上启动一个activity不应该既是显示又是隐式,如果二者共存,那么以显示调用为主。

3.1 、显式启动

明确的指定要启动的Activity。

  • class 跳转
java 复制代码
Intent intent = new Intent(Activity.this,MainActivity3.class);
startActivity(intent);
  • 包名.类名跳转
java 复制代码
Intent intent = new Intent(); 
intent.setClassName(FirstActivity.this,"com.lixiang.application.MainActivity3");
startActivity(intent);
  • ComponentName跳转
java 复制代码
Intent intent = new Intent()
ComponentName componentName = new ComponentName(FirstActivity.this,MainActivity3.class);
intent.setComponent(componentName);
startActivity(intent);

3.2 、隐式启动

能被隐式启动的Activity必须在AndroidManifest.xml文件中配置Intent-filter (过滤器),而IntentFilter又有action、category和data,所以隐式启动需要Intent能够匹配intent-filter设置的过滤信息。而且可以有多组intent-filter,只要匹配一组即可。系统即可帮我们找出合适的Activity去启动。

java 复制代码
<activity android:name=".MainActivity3">
    <intent-filter>
        <action android:name="com.lixiang.application.x"/>
        <action android:name="com.lixiang.application.x1"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="com.lixiang.application.x1"/>
        <category android:name="com.lixiang.application.x2"/>
        <data android:mimeType="text/plain"/>
    </intent-filter>
    <intent-filter>
        <action android:name="com.lixiang.application.y"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</activity>
  • action的匹配规则

    action是一个字符串,系统预定了一些,也可以自己定义,区分大小写,必须在Intent-filter添加。

    注意的是,比如AA启动AB,那么AB的过滤信息中的一组Intent-filter中可以有一个action也可以有多个action,只要有一个action匹配成功就行。再者就是AA中Intent设定action的时候只能有一个。

    上图是设置action的方法,可以看到,设置多个action也是没有意义的,设置结果以最后一次为准。

  • category的匹配规则

    category内容也是一个字符串,系统预定了一些,也可以自己定义,区分大小写,必须在Intent-filter添加。

    从上图的方法中可以看出,category可以设定多个,那么每个都要和过滤规则中的其中一个相同。也就是说,比如过滤规则中的category有5个,那么intent可以从这个5个中选3个添加,也可以全部添加,也可以重复添加。

    系统在调用startActivity或者startActivityForResult的时候会默认为Intent加上"android.intent.

    category.DEFAULT"(无论你的过滤规则中是否设定category,系统都会为你加上),所以必须要在过滤规则intent-filter中加上"<category android:name="android.intent.category.DEFAULT"/>",否则会报错。

  • data的匹配规则

    data在intent或者manifest中都可以不定义,因为这不是必需的,当启动某些activity的时候才会需要,但如果intent-filter中定义了data,那么Intent中必须定义可匹配的data。

    项目中data很少用到,特别是自己的项目作为被启动方时,记住几个常用的系统启动项目即可。

javascript 复制代码
 <data android:scheme="string"
       android:host="string"
       android:port="string"
       android:path="string"
       android:pathPattern="string"
       android:pathPrefix="string"
       android:mimeType="string" >
 </data>

从上面的状态数据可以见到,总体来说, data有两部分组成,mimeType和Uri。mimeType指image/jpeg,video/ 等媒体类型,也可以自己自定义,比如:ccddy/test。当然还是用公共语法最好,方便大家阅读。再者就是mimeType可以单独设定,不设定Uri。还有一种情况是不设定mimeType时,可以只设定scheme。

  • 当只设定mimeType,不设定Uri时:
java 复制代码
 <intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:mimeType="audio/*" />
 </intent-filter>

这个可以注册进打开音乐播放器选择中。

但是这种情况虽然没有指定Uri,Uri部分的schema默认值为content或file。也就是说虽然没有指定Uri,但是intent中的Uri部分的schema必须为content或者file才能匹配成功,这点尤其需要注意。

  • 当只设定Uri,不设定mimeType时:
java 复制代码
 <intent-filter>
 <action android:name="android.intent.action.VIEW"></action>
 <category android:name="android.intent.category.DEFAULT"></category>
 <data android:scheme="http" android:pathPattern=".*\\.pdf"></data>
</intent-filter>

这个的意思是可以匹配http开头,.pdf结尾的路径。比如程序要打开pdf,配置这样的以后用户可以使用自己的app查看pdf。

URI的结构如下:

<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]
  • scheme:URI的模式,比如http、file、content等,如果Uri中没有指定,则整个Uri无效。可以自定义scheme,如:test、ddd等。在过滤器中只设定scheme是无效的,至少有scheme和host。

  • host:URI的主机名,比如ccddy.top,如果URI中没有指定,则整个URI无效。实际上只设定host会报错,port、path也是一样。而且设定host之后所有的子路径均可匹配。

  • port:URI的端口号,比如80、8080等。仅当scheme、host指定,port才有意义

  • path:完整路径信息,

  • pathPrefix:表示路径的前缀信息 ,

  • pathPattern:完整的路径信息,可以包含通配符"*",表示0个或多个任意字符,需要注意的是通配符前面需要添加"\\"。

比如这个链接http://ccddy.top/categories/android/,http和scheme、ccddy.top是host、port这个链接没有,那么对于:

  • path来讲是/categories/android/

  • 对于pathPrefix来讲/categories就可以进行匹配了。

  • pathPattern来讲比较麻烦。这里说下匹配符号和转义。

    ** 匹配符号:**

    1、" * " 用来匹配0次或更多,如:"a* " 可以匹配"a"、"aa"、"aaa"...

    2、" . " 用来匹配任意字符,如:"." 可以匹配"a"、"b","c"...

    3、因此 " .* " 就是用来匹配任意字符0次或更多,如:".*html" 可以匹配"abchtml"、"chtml","html","sdf.html"...
    转义:

    因为当读取 Xml 的时候,"\" 是被当作转义字符的(当它被用作 pathPattern 转义之前),因此这里需要两次转义,读取 Xml 是一次,在 pathPattern 中使用又是一次。所以通配符前面需要添加"\\"。可参考上方的pdf例子。

  • 需要注意的是,如果 Intent 要使用完整的数据,也就是 Uri 和 mimeType,可以调用 Intent 的 setDataAndType 方法,不能把 Intent 的 setType 和 setData 方法一起调用,它们两种方法都是把对方的值彼此置入空的,要两个方法 Uri 和 mime Type 一个必须是空的,下面看看这两个的源码。

3.3 、MAIN

这是任何一个APP必须有的程序切入点,算一个特殊的隐式启动,因为它其中没有必要加入android.intent.category.DEFAULT,当然加入也没有问题。

java 复制代码
<intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

那能不能在manifest中设定两个呢?答案是可以的,设定两个之后会发现在桌面有两个相同图标,比如说A和B。从A和B打开是在一个APP内的,后台运行也是一个。比如有些APP想在桌面有个快速的二维码扫描图标,那么就可以这么实现。

3.4、 显式启动和隐式启动对比

  • 显式启动:直接指定要跳转的Activity类名,目标明确,不用过滤,效率高,但是耦合性强,适用于同一个应用中的不同Activity跳转。
  • 隐式启动:需要过滤,相对耗时,需要找到与之匹配的应用,目标不明确,但是耦合性弱。适用于不同应用之间的Activity跳转。

3.5、 启动其他应用的Activity

  • 通过隐式启动(上方有详细讲解,这里就不介绍了);

  • 通过ComponentName启动。

    实例化一个ComponentName需要两个参数,第一个参数是要启动应用的包名称,这个包名称是指清单文件中列出的应用的包名称;第二个参数是你要启动的Activity或者Service的全称(包名+类名)。

启动目标APP的启动页

java 复制代码
 Intent intent = new Intent(Intent.ACTION_MAIN);
 intent.addCategory(Intent.CATEGORY_LAUNCHER);            
 ComponentName cn = new ComponentName("com.lixiang.application","com.lixiang.application.MainActivity");            
 intent.setComponent(cn);
 startActivity(intent);

有时候,当我们不知道应用程序的启动Activity的类名,而只知道包名,也通过ResolveInfo类来取得启动Acitivty的类名。

java 复制代码
    private void openApp(String packageName) {
        PackageInfo pi = getPackageManager().getPackageInfo(packageName, 0);
        Intent resolveIntent = new Intent(Intent.ACTION_MAIN, null);
        resolveIntent.addCategory(Intent.CATEGORY_LAUNCHER);
        resolveIntent.setPackage(pi.packageName);
        List<ResolveInfo> apps = pm.queryIntentActivities(resolveIntent, 0);
        ResolveInfo ri = apps.iterator().next();
        if (ri != null) {
            String packageName = ri.activityInfo.packageName;
            String className = ri.activityInfo.name;
            Intent intent = new Intent(Intent.ACTION_MAIN);
            intent.addCategory(Intent.CATEGORY_LAUNCHER);
            ComponentName cn = new ComponentName(packageName, className);
            intent.setComponent(cn);
            startActivity(intent);
        }
    }

启动目标APP的内部普通页面

java 复制代码
      ComponentName cn = new ComponentName("com.lixiang.application","com.lixiang.application.MainActivity3");
        try {
            Intent intent = new Intent();
            Bundle bundle = new Bundle();
            bundle.putString("type", "1");//传递值
            intent.putExtras(bundle);
            intent.setComponent(cn);
            startActivityForResult(intent, REQUEST_ID);//等待目标页面返回的结果
        } catch (Exception e) {
            e.printStackTrace();
        }

如果你要的启动的其他应用的Activity不是该应用的入口Activity,那么在清单文件中,该Activity节点一定要加上android:exported="true",表示允许其他应用打开,对于所有的Service,如果想从其他应用打开,也都要加上这个属性:

java 复制代码
        <service
            android:name="com.lixiang.application.MyService"
            android:exported="true" >
        </service>
 
        <activity
            android:name="com.lixiang.application.MainActivity3"
            android:exported="true" >
        </activity>
java 复制代码
			Intent it = new Intent();
			it.setComponent(new ComponentName("com.lixiang.application","com.lixiang.application.MyService"));
			startService(it);

对于除了入口Activity之外的其他组件,如果不加这个属性,都会抛出"java.lang.SecurityException: Permission Denial..."异常

入口Activity和普通Activity唯一不同的地方就是入口Activity多了一个过滤器,对于包含了过滤器的组件,意味着该组件可以提供给外部的其他应用来使用,它的exported属性默认为true,相反,如果一个组件不包含任何过滤器,那么意味着该组件只能通过指定明确的类名来调用,也就是说该组件只能在应用程序的内部使用,在这种情况下,exported属性的默认值是false。

四、activity的生命周期

当一个Activity正常启动,方法调用的顺序是onCreate -> onStart -> onResume;在Activity被kill掉的时候方法顺序是onPause -> onStop -> onDestroy,此为一个完整的Lifecycle。

那么对于中断处理(比如电话来了),则是onPause -> onStop,恢复时onRestart->onStart -> onResume;如果打开一个为Translucent(半透明) 或者小窗口的Activity那么中断就是onPause ,恢复的时候onResume。

那么对于"Other app need memory",就是我们手机在运行一个应用程序的时候,有可能打进来电话发进来短信,或者没有电了,这时候程序都会被中断,优先去服务电话的基本功能,另外系统也不允许你占用太多资源,至少要保证一些功能(比如电话),所以资源不足的时候也就有可能被kill掉。

方法在系统中的作用及我们应该做什么:

onCreate:在这里创建界面,做一些数据的初始化工作;

onStart: 到这一步变成"用户可见不可交互"的状态;

onResume:变成和用户可交互的,(在Activity栈系统通过栈的方式管理这些Activity,即当前Activity在栈的最上端,运行完弹出栈,则回到上一个Activity);

onPause:到这一步是可见但不可交互的,系统会停止动画等消耗CPU的事情。从上文的描述已经知道,应该在这里保存你的一些数据,因为这个时候你的程序的优先级降 低,有可能被系统收回。在这里保存的数据,应该在onResume里读出来。

onStop:变得不可见 ,被下一个activity覆盖了。

onDestroy:这是Activity被kill前最后一个被调用方法了,可能是其他类调用finish方法或者是系统为了节省空间将它暂时性的干掉,可以用isFinishing()来判断它,如果你有一个Progress Dialog在线程中运行,请在onDestroy里把他cancel掉,不然等线程结束的时候,调用Dialog的cancel方法会抛异常。

onPause,onstop, onDestroy,三种状态下 activity都有可能被系统kill 掉。

五、activity的四种状态

Android中Activity的四种状态:

1)Active/Runing: 可见,可交互,一个新 Activity 启动入栈后,它在屏幕最前端,处于栈的最顶端,此时它处于可见并可和用户交互的激活状态,onCreate()(一瞬间)-->onStrat()(一瞬间)-->onResume();。

2)Paused:当 Activity 被另一个透明或者 Dialog 样式的 Activity 覆盖时的状态。此时它依然与窗口管理器保持连接,系统继续维护其内部状态,所以它仍然可见,但它已经失去了焦点故不可与用户交互。onPause()-->onResume()(重回前台),onResume()-->onPause();

3)Stoped :不可见,不可交互,当 Activity 被另外一个 Activity 覆盖、失去焦点并不可见时处于 Stoped 状态,onPause()-->onStop()(结束),onStop()-->onRestart()-->onStart()-->onResume()(恢复);。

4)Killed :Activity 被系统杀死回收或者没有被启动时处于 Killed 状态。其中onDestory是在被系统finish期间调用的。(1)用户操作所致:onStop()-->onDestroy()(2)activity处于暂停或停止状态,若内存不足,系统自动强制执行,activity被强行结束。

onDestory():系统内存不足时,跳过不执行。

onPause(),onStop():不能被跳过,不能不执行

六、Activity的栈式管理

Android 是通过一种 Activity 栈的方式来管理 Activity 的,一个 Activity 的实例的状态决定它在栈中的位置。处于前台的 Activity 总是在栈的顶端,当前台的 Activity 因为异常或其它原因被销毁时,处于栈第二层的 Activity 将被激活,上浮到栈顶。当新的 Activity 启动入栈时,原 Activity 会被压入到栈的第二层。一个 Activity 在栈中的位置变化反映了它在不同状态间的转换。Activity 的状态与它在栈中的位置关系如下图所示:
失去焦点 重新激活 Activity A:Running Activity B:Paused/Stoped/Killed Activity C:Paused/Stoped/Killed Activity D:Paused/Stoped/Killed

如上所示,除了最顶层即处在 Active 状态的 Activity 外,其它的 Activity 都有可能在系统内存不足时被回收,一个 Activity 的实例越是处在栈的底层,它被系统回收的可能性越大。系统负责管理栈中 Activity 的实例,它根据 Activity 所处的状态来改变其在栈中的位置。

七 、Activity的启动模式

Activity的启动模式是决定生成新的activity实例是否重用已存在的Activity实例,是否和其他Activity实例公用一个栈。在Android12前提供了四种启动模式:(这里的都是没有添加其它参数配置的情况,比如Intent.FLAG_ACTIVITY_NEW_TASK等。)

1、standard:默认启动模式,每次激活activity时,都创建activity实例,并放入任务栈。

2、singleTop: 如果某个Activity自己激活自己,即任务栈栈顶是该Activity,则不需要创建,其余情况都要创建Activity。当从别的Task任务栈切换回当前任务栈时,如果栈顶实例就是同样的Activity,则直接进行复用,并且通过onNewIntent方法进行通知。

3、singleTask:如果要激活的activity在任务栈中,则不需要创建,只需要把这个Activity放入栈顶,并把该Activity以上的Activity实例都出栈。

4、singleInstance:如果不存在包含目标Activity的栈,则创建一个新的Task,这个Task中是目标Activity所独有的,不允许有别的Activity存在,并且只会创建一次,后续如果在启动其它的Activity,这些新的Acitivty属于其原来的task栈。如果存在包含目标Activity的栈,则直接把包含目标Activity的Task栈挪到前台进行显示。

如下图所示,在AndroidManifest文件中Activity节点里增加属性android:launchMode="xxx" 即可:

但是在Android 12 的时候新加入了singleInstancePerTask类型,所以就有五种了。

5、singleInstancePerTask:如果不存在包含目标Activity的栈,则创建一个新的Task,这个Task中是目标Activity所独有的,并且只会创建一次,后续如果在启动其它的Activity,这些新的Acitivty仍然当前的task栈。如果存在包含目标Activity的栈,则把包含目标Activity的Task栈挪到前台,并且把该栈中目标Activity上面的所有Activity进行出栈操作,从而实现目标Activity显示在前台的效果。(如官方文档所描述,如果启动singleInstancePerTask类型的Activity时,同时设置FLAG_ACTIVITY_MULTIPLE_TASK标记,可以启动多个包含目标Activity的Task栈,这里不过多描述)

如何区分是首次创建还是从栈中返回?

答:通过回调用onNewIntent方法。

如何区分是从栈顶复用还是栈中复用?

答:如果是栈中复用,则onNewIntent方法中,其flags参数中会包含Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT标记。

如果一个Task中的Activity全部出栈,会退到哪里?

答:假设Task1启动了Task2,那么Task2中的Activity如果全部出栈,就会把Task1推到前台,Task1中栈顶的Activity就会被显示。

八、其他

8.1、 Activity跳转方法

1、Activity跳转,无返回结果,直接startActivity(new Intent(当前Activity.this, 下一Activity.class))。

2、Activity跳转,返回数据/结果,使用startActivityForResult (Intent intent, int requestCode),requestCode的值是自定义的,用于识别跳转的目标Activity。

跳转的目标Activity所要做的就是返回数据/结果,setResult(int resultCode)只返回结果不带数据,或者setResult(int resultCode, Intent data)两者都返回!

在setResult后,要调用finish()销毁当前的Activity,否则无法返回到原来的Activity。

接收返回的数据/结果的处理函数是onActivityResult(int requestCode, int resultCode, Intent data),这里的requestCode就是startActivityForResult的requestCode,resultCode就是setResult里面的resultCode,返回的数据在data里面。

8.2、Intent 可以传递的数据类型:

根据上图可以看出,intent 对象可以传递的具体数据类型以及使用的方法。

(1) putExtra( key , value ) 方法

  • 可以传递 8 种基本数据类型以及这八种基本数据类型的数组,
  • 可以传递 CharSequence以及它的数组,
  • 可以传递 Serializable 实现类的对象,
  • 可以传递Parcelable 实现类的对象和数组,
  • 可以传递Bundle 对象。

(2) putExtras( value )方法

  • 可以传递Bundle 对象,
  • 可以传递Intent 对象。

(3) Intent 也可以直接传递集合

  • 传递 Integer类型 的集合可以直接使用:putIntegerArrayListExtra( key,value)
  • 传递 String 类型 的集合可以直接使用: putStringArrayListExtra( key , value)
  • 传递 CharSequence 类型 的集合可以直接使用: putCharSequenceArrayList( key , value)
  • 传递 ** Parcelable 类型** 的集合可以直接使用 :putParcelableArrayList( key,value)

8.3、 多个Activity跳转时的生命周期

在MainActivity启动MainActivity2,然后在MainActivity2点击返回键。

MainActivity MainActivity2
onCreate onStart onResume 启动MainActivity2 onPause ~
~ onCreate onStart onResume
onStop ~
~ MainActivity2点击返回键 onPause
onRestart onStart onResume ~
~ onStop onDestroy
相关推荐
前端郭德纲1 小时前
浅谈React的虚拟DOM
前端·javascript·react.js
2401_879103682 小时前
24.11.10 css
前端·css
ComPDFKit3 小时前
使用 PDF API 合并 PDF 文件
前端·javascript·macos
深海呐3 小时前
Android 最新的AndroidStudio引入依赖失败如何解决?如:Failed to resolve:xxxx
android·failed to res·failed to·failed to resol·failed to reso
解压专家6663 小时前
安卓解压软件推荐:高效处理压缩文件的实用工具
android·智能手机·winrar·7-zip
Rverdoser3 小时前
Android 老项目适配 Compose 混合开发
android
冻感糕人~3 小时前
大模型研究报告 | 2024年中国金融大模型产业发展洞察报告|附34页PDF文件下载
人工智能·程序人生·金融·llm·大语言模型·ai大模型·大模型研究报告
yqcoder3 小时前
react 中 memo 模块作用
前端·javascript·react.js
优雅永不过时·4 小时前
Three.js 原生 实现 react-three-fiber drei 的 磨砂反射的效果
前端·javascript·react.js·webgl·threejs·three
️ 邪神5 小时前
【Android、IOS、Flutter、鸿蒙、ReactNative 】标题栏
android·flutter·ios·鸿蒙·reatnative