最近在整理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 |