您的位置:首页 > 移动开发 > Android开发

关于Activity你应该知道的一切

2016-11-27 21:22 507 查看

Activity

Context上下文

可以理解为语文中的上下文,也就是语境

从Android系统的角度来理解,Context就是当前应用所处的环境,

从程序的角度上来理解,Context是一个抽象类,Activity、Service、Application都是Context的子类

在Android中,可以通过上下文访问跟当前应用相关的资源

获取系统资源(getsystemservice 获取系统的服务 getwallpaper 获取壁纸)

获取应用的私有的资源(getAssets 获取assets目录下的内容)

获取res目录的资源(getResource())

startActivity+startService

获取内部文件(夹)路径

创建View

XXXActivity.this和getApplicationContext的区别?

一个是当前Activity的实例,一个是当前应用的实例,两个的生命周期不同,使用时要注意使用的场景,如果使用不当就会造成一些问题。

Context的应用场景



凡是跟UI相关的,都应该使用Activity做为Context来处理;其他的一些操作,Service,Activity,Application等实例都可以,当然了,注意Context引用的持有(生命周期),防止内存泄漏

1.什么是Activity

Activity是Android四大组件之一,用于展示界面并和用户交互。

Activity之间通过Intent进行通信。Intent即意图,用于描述一个页面的信息,同时也是一个数据的载体。

2. Activity之间的跳转

Activity之间的跳转分为2种:

显式跳转:直接通过字节码 .class打开对应activity,在打开自己应用activity的时候 一般使用显示意图

如果是打开其他应用的activity,intent创建之后要通过intent.setClassName(“包名”,”全类名”); 这个方法打开其它应用的activity(一般不用)

Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent);


隐式跳转:通过在清单文件中activity节点下配置一个intent-filter,可以让自己的应用或其他应用通过匹配意图过滤器来打开当前的activity

需要注意:想通过隐式意图打开activity,意图过滤器必须要写,category节点配置为DEFAULT 虽然代码中不用匹配category

如果 data节点中既有scheme节点也有mimetype 需要使用setDataAndType来匹配这两个信息

//创建一个Intent对象
Intent intent = new Intent();
//设置Action
intent.setAction("android.intent.action.VIEW");
//设置category
intent.addCategory("android.intent.category.BROWSABLE");
//设置参数
intent.setData(Uri.parse("http://www.gxs.com"));
//启动Activity
startActivity(intent);

清单文件中
* action 指定动作(自定义)
* data   指定数据
* category 类别 (机顶盒,车载电脑)
* 通常我们需要去开发一个和系统应用一样功能的软件时,就可以拿取他的action和data和category


一般打开自己应用的activity使用显示意图,打开别人应用的activity使用隐式意图

当打开系统应用Activity时,需要寻找系统应用的action/Category/Data

打开相应的系统应用,观察logcat中activitymanager打印的信息

I/ActivityManager: START u0 {cmp=com.android.mms/.ui.ComposeMessageActivity} from pid 1081
I/ActivityManager: Displayed com.android.mms/.ui.ComposeMessageActiy


根据ComposeMessageActivity在系统上层源码里对应的清单文件中找到对应的结点(Activity-intentfilter)

如果需要传递数据的话,可以去代码中找到传输数据的格式

通过隐式意图开启一个Activity,可以先进行一下判断

如果不判断,当没有Activity匹配的时候,会出现错误

PackageManager.resolveActivity

Intent.resolveActivity

当返回值为空时,就是没有。另外注意第二个参数为MATCH_ DEFAULT_ ONLY

intent-filter节点及其子节点都可以同时定义多个,隐式启动时只需与任意一个匹配即可

IntentFilter中的过滤信息有:action,category,data

分析各属性的匹配规则:

action的匹配规则

隐式启动必须要有action

action的匹配规则要求intent中的action和IntentFilter中的action完全相同

category的匹配规则

intent中可以没有category,但若是有,不管有几个,只要能够和过滤规则中任何一个category相同即可

data的匹配规则

这个和action类似,如果过滤规则中定义了data,那么intent必须也要定义能够匹配的data

data的结构:(data由mimeType和URI两部分组成

MIME是描述消息内容类型的因特网标准。
MIME 消息能包含文本、图像、音频、视频以及其他应用程序专用的数据。
类型/子类型
* mimeType指媒体类型
* image/jpg/video....可以表示图片,文本,视频等不同的媒体格式

* URI包含的数据就比较多了,下面是URI的结构
<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]
eg: http://www.baidu.com:80/search/info content://com.jike.project:200/folder/subfolder/etc

下面介绍一下每个数据的含义:
scheme:URI的模式,比如http,file,content等
Host:URI的主机名,比如www.baidu.com
port:URI中的端口号,比如80
path,pathPrefix,pathPattern:这三个参数表示路径信息

//data的语法
<data
android:host="string"
android:mimeType="string"
android:path="string"
android:pathPattern="string"
android:pathPrefix="string"
android:port="string"
android:scheme="string" />


若清单文件中的Activity声明为:

<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>


则此Activity将作为程序的入口,有几个作为入口的Activity,apk文件安装的时候就会生成几个图标。

3. 使用Intent传递数据

Intent可传递的数据类型有: 基本数据类型及其数组 String String数组 String集合 Integer集合 序列化(serilizeble parcelable) 数组和集合,也可以通过Bundler来传递数据 把数据封装到一个对象中

注意:Intent传递的数据过多可能会造成跳转速度极慢甚至黑屏一会,不要用Intent传递过多的数据,会影响到应用程序的使用。

意图设计目的

高内敛,低耦合。

使得程序组件之间彼此独立,又可以互相调用。

数据传递有两种情况:

1.从A界面打开B界面,把A界面的数据传递给B界面

示例如下:

在清单文件中设置SecondActivity的intent-filter参数

<activity android:name="com.gxs.activitySkip.SecondActivity">
<!-- 配置意图过滤器 -->
<intent-filter >
<!-- 在意图过滤器中设置action和category,当有匹配的action和category的时候启动该Activity -->
<action android:name="com.gxs.activitySkip.SecondActivity"/>
<category android:name="android.intent.category.DEFAULT"/>
<!-- 设置数据协议 -->
<data android:scheme="money"/>
<!-- 配置数据的mimeType:必须为xxx/xxx的格式,否则会报异常  -->
<data android:mimeType="data/mymime"/>
</intent-filter>
</activity>


在FirstActivity布局中添加一个按钮,给该按钮绑定事件,点击该按钮实现跳转到SecondActivity界面

// 发送数据给SecondActivity
public void sendData2Second(View view) {
// 创建一个Intent对象
Intent intent = new Intent();
// 设置Action
intent.setAction("com.gxs.activitySkip.SecondActivity");
// android.intent.category.DEFAULT为Android系统默认,省略也可以
intent.addCategory("android.intent.category.DEFAULT");
**  //当有setData和setType需要使用这个,这两个会互相清除对方的值**
intent.setDataAndType(Uri.parse("money:转账100元。"), "data/mymime");

//给Intent设置数据
intent.putExtra("name", "张三");

ArrayList<String> list = new ArrayList<String>();
e62d

for(int i=0;i<10;i++){
list.add("list"+(i+1));
}
//给Intent设置字符串类型的集合
intent.putStringArrayListExtra("list", list);

//声明一个Bundle对象
Bundle bundle = new Bundle();
//在Bundle对象中绑定数据
bundle.putString("pwd", "123456");
//给Intent设置Bundle对象
intent.putExtras(bundle);

// 启动Activity
startActivity(intent);

}


编写SecondActivity类,使其接收数据

public class SecondActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
//从当前Context对象中获取Intent
Intent intent = getIntent();
//获取数据
String data = intent.getData().toString();
System.out.println(data);

//从Intent中获取Bundle对象
Bundle extras = intent.getExtras();
//从Bundle中获取key为name的数据
String name = (String) extras.get("name");
System.out.println("name="+name);

//从Bundle对象中获取key为pwd的数据
String pwd = (String) extras.get("pwd");
System.out.println(pwd);

//从Bundle中获取ley为list的数据
List<String> list = (List<String>) extras.get("list");
System.out.println(list);
}


2.从A界面打开B界面,B界面关闭的时候,返回一个数据给A界面

跳转时使用startActivityForResult方法开启新页面

startActivityForResult(intent, 请求码);


在被开启的新的页面里, 调用setResult方法设置回传的数据

//获取当前Intent
Intent intent = getIntent();
//获取Intent中绑定的数据
Bundle extras = intent.getExtras();
int data = (Integer) extras.get("rp");

//创建一个新的Intent
Intent data = new Intent();
data.putExtra("phone", phone);
//设置一个结果数据,数据会返回给调用者
setResult(结果码, data);
//关闭掉当前的activity,才会返回数据
finish();


当页面被关闭后,回传数据给前一个页面的onActivityResult方法,在此方法中对数据进行处理即可,如果有多个页面会返回数据 可以通过requestcode 或者 resultcode区分 不同数据

//请求码  结果码  传递的数据
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
//从当前Intent中获取数据(需要进行非空判断)
Bundle extras = data.getExtras();
String result  = (String) extras.get("result");
}


注意:如果请求码设为-1,则使用startActivityForResult(intent, 请求码),没有结果返回。

4. 生命周期

生命周期方法

* oncreate()  activity被创建的时候调用的方法
> 作用:ui界面的初始化 setContentView()

* onStart() 当界面变成用户可见的时候调用的方法(可见不可交互)
> 作用:更新ui的操作,eg:播放视频

* onResume()//resume继续 ,当界面获取到焦点的时候(可见可交互)
> 作用:界面获取到了焦点,按钮可以相应点击事件

* onPause()//pause暂停,当界面失去焦点的时候调用的方法(部分可见不可交互)
> 作用:界面失去了焦点,按钮不可以被点击了
> 不能在onPause中做重量级操作

* onStop() activity的界面用户不可见(完全不可见)
> 界面不可见,eg:暂停视频播放

* onDestroy() activity被销毁的时候调用的方法
> 作用:界面退出之前的扫尾操作,eg:线程的销毁,退出前数据的保存。

* onRestart()//activity重新启动
>作用:activity被最小化了,并没有销毁,如果下次再去打开这个activity就会调用此方法


完整生命周期

onCreate-->onStart-->onResume-->onPause-->onStop-->onDestory


可视生命周期

onStart-->onResume-->onPause-->onStop


前台生命周期

onResume-->onPause


见图——activity生命周期

正常情况下的生命周期分析:

onStart和onStop是针对Activity**是否可见来说的,OnResume和OnPause是针对Activity是否位于前台**来说的。

当前是ActivityA,这时打开ActivityB,A的OnPause先于B的OnResume执行。

不能在onPause中做重量级操作,因为必须onPause执行完成之后新Activity才能Resume

异常情况下的生命周期分析:

相关的系统配置发生改变(屏幕旋转)或者内存不足而导致Activity被杀死并重新创建

由于Activity是在异常情况下终止,系统会调用onSaveInstanceState(Bundle outState)来保存当前Activity的状态(这个方法在Onstop之前调用,并且这个方法只会在Activity异常终止的情况下调用,正常不会调用),

当Activity被重新创建,系统会调用onRestoreInstanceState(Bundle savedInstanceState)方法,并把onSaveInstanceState保存的Bundle对象传递给onRestoreInstanceState和onCreate方法。

因此我们可以通过onRestoreInstanceState判断Activity是否重建,如果重建了可通过onRestoreInstanceState中的Bundle来恢复数据(在OnStart之后调用)

在activity重建过程中,系统会保存当前Activity的视图结构,并自动恢复,比如文本框中的数据,listview滚动的位置,并自动恢复

和Activity一样,每个View都有这两个方法,通过查看源码可知系统为每个View保存恢复哪些数据。

保存恢复View层次结构,系统工作流程:
activity被异常终止--activity调用onSaveInstanceState保存数据--然后Activity会委托Window去保存数据--接着window委托它里面的顶级容器DectorView去保存数据
--DectorView通知子View保存数据。
这是一种委托思想,上层委托下层。类似于View的绘制,事件的分发机制。


内存不足导致低优先级的Activity被杀死

其数据存储和恢复过程与1过程一致。

activity优先级

前台activity——正在和用户交互,优先级最高

可见但非前台activity——比如activity中弹出一个对话框,优先级其次

后台activity——执行了OnStop

横竖屏切换的生命周期

默认情况下 ,横竖屏切换, 会销毁当前的activity,重新创建一个新的activity

不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行两次

设置Activity的android:configChanges=”orientation”时,切屏还是会重新调用各个生命周期,切横、竖屏时只会执行一次

设置Activity的android:configChanges=”orientation|keyboardHidden”时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法

需求:让屏幕旋转不再重新调用生命周期

横竖屏写死

android:screenOrientation="landscape"
android:screenOrientation="portrait"
//screen 屏幕 orientation 方向 landscape 风景(躺) portrait 肖像


让系统的环境 不在去敏感横竖屏的切换。

android:configChanges="orientation|screenSize|keyboardHidden"


在Activity结点声明

5. 任务栈

任务栈创建的目的:管理activity

程序打开时就创建了一个任务栈, 用于存储当前程序的activity,所有的activity属于一个任务栈。

只有在任务栈栈顶的activity才可以跟用户进行交互。

任务栈可以移动到后台, 并保留了每一个activity的状态. 并有序的给用户列出它们的任务, 而且还不丢失它们状态信息。

当把任务栈中所有的activity清除出栈时,任务栈会被销毁,程序退出。

一般一个应用程序对应一个任务栈,手机操作系统里面有多个任务栈

任务栈的id是应用程序第一次开启的时候分配出来的,是一个自增长的int类型的数据。如果应用程序退出,id被回收。

一个Task中的activity可以来自不同的APP,一个APP的activity也可以在不同的栈中。

任务栈的缺点:

每开启一次页面都会在任务栈中添加一个Activity,而只有任务栈中的Activity全部清除出栈时,任务栈被销毁,程序才会退出,这样就造成了用户体验差, 需要点击多次返回才可以把程序退出了。

每开启一次页面都会在任务栈中添加一个Activity还会造成数据冗余, 重复数据太多, 会导致内存溢出的问题(OOM)。

为了解决任务栈产生的问题,Android为Activity设计了启动模式,那么下面的内容将介绍Android中Activity的启动模式

activity启动模式,修改任务栈

启动模式(launchMode)在多个Activity跳转的过程中扮演着重要的角色,它可以决定是否生成新的Activity实例,是否重用已存在的Activity实例,是否和其他Activity实例共用一个task。

Activity一共有以下四种launchMode:

standard 标准启动模式

默认的启动模式,每次启动activity都会创建新的实例。

singletop 栈顶复用模式

当栈顶有将要开启的Activity时,会复用这个Activity,同时这个activity的onNewIntent方法会被回调。这个activity的onCreate,OnStart方法不会被调用,因为它没有发生改变。若是栈顶没有,那么就会重新创建

应用场景:适用于接收到消息后显示的界面。例如:QQ接收到消息后会弹出activity,但如果一次来10条消息,总不能一次弹出10个activity。

singetask 栈内复用模式,在当前任务栈里面只能有一个实例存在

当一个启动模式为singleTask的activityA请求启动后,系统会先寻找是否存在A想要的任务栈,如果不存在,就重新创建一个任务栈,然后创建A的实例并将A放到栈中。如果存在A所需的任务栈,这时要看栈中有是否有要开启的activityA,如果有则直接复用并删除A上面的activity,将A移动到栈顶,同singletop一样,也会回调这个activity的onNewIntent方法。如果没有实例,则创建A实例并压入栈中

现在有两个任务栈,前台任务栈中有BA,后台任务栈中有DC,假设DC启动启动模式都为singleTask。现在请求启动D,那么整个后台任务栈都会被切到前台,这时候前台任务栈为DCBA,当按back键时,前台栈中activity会一一出栈;如果请求启动C,那么情况就不一样了,会把D删除,C切换到前台。(具体看图——singleTask启动模式特例.pptx)

所以这种启动模式通常可以用来退出整个应用程序。。将主activity设为singleTask,然后在要退出的activity中转到主Activity,从而将主Activity上的其他activity全部清除,然后在主Activity中的onNewIntent()中加上finish(),将最后一个activity结束。

singleInstance 单实例模式,可以看作加强版singleTask模式

activity会开启一个新的任务栈,并且这个任务栈里面只有一个实例存在。

这种启动模式和浏览器的工作原理类似。当多个程序访问浏览器时,如果浏览器没有打开,则打开浏览器,否则会在当前打开的浏览器中访问。举个例子来说,当应用A的任务栈创建了ActivityA实例,并且其启动模式为sinleInstance,如果应用B也要激活ActivityA,则不需要创建,两个应用共享即可。

如果你要保证一个activity在整个手机操作系统里面只有一个实例存在,使用singleInstance

关于singleInstance这种启动模式还有一点需要特殊说明:

如果在一个singleInstance的activityA中通过startActivityForResult()去启动另一个activityB,那么在A中拿不到数据。因为android不允许task间互相传递数据。

onNewIntent()

protected void onNewIntent(Intent intent)通过这个方法可以取出当前请求的信息


* 第一次创建Activity A时,执行的逻辑顺序是:

* onCreate() ­­­>onStart()­­­>onResume()

* 而如果使用singleTask模式第二次启动Activity A,且A处于任务栈的顶端,则执行的逻辑顺序是:

* onNewIntent() ­­­>onRestart> onStart>onResume()。

注意,getIntent()仍返回原来的意图。你可以使用setIntent来设置新的意图。

如何给activity设置模式

在清单文件中设置

<activity
android:name=".B"
android:launchMode="singleTask"*******
--------------------------------------------------------------
android:taskAffinity="kk" //任务栈名称,默认为包名

//taskAffinity属性主要和singleTask启动模式配对使用或者allowTaskPeparenting属性配对使用
* 当一个应用A启动了应用B的某个activity,如果这个activity的allowTaskPeparenting设置为true的话,那么当应用B被启动后,此activity会直接从应用A的任务栈转移到应用B的任务栈中。
/>


在代码中通过Intent设置

Intent intent =new Intent(this,MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);*********
startActivity(intent);

Intent Flag设置启动模式
* Intent.FLAG_ ACTIVITY_ NEW_TASK
* 开启一个新的task来存放这个activity
* 通常用于在广播,服务中开启一个新的activity。由于他们中并不存在activity栈,所以需要这样。
* intent.FLAG_ ACTIVITY_ NO_HISTORY
* 使用这种模式启动的activity,当该activity启动其他activity的时候,这个activity就消失了。eg:A-B,B中以这种模式启动C,C在启动D,则当前栈为ABD
* FLAG_ ACTIVITY_ SINGLE_ TOP
* ==singleTop
* FLAG_ ACTIVITY_ clear_ Top
* 清除位于它上面的Activity


二者区别:

1. 第二种优先级大于第一种

2. 当两个同时设置时,以第二种为准,

任务栈_清单文件中相关参数

TaskAffinity
标识了一个Activity需要的任务栈。默认情况下,所有Activity的任务栈的名字是包名

这个属性主要和singleTask/allowTaskReparenting配合使用
1. 当TaskAffinity和singleTask配合使用时,TaskAffinity为当前任务栈的名字,这个Activity会运行在名字和TaskAffinity相同的任务栈中
2. 当TaskAffinity与allowTaskReparenting配合使用的时候。当应用A启动应用B的某个Activity后,如果这个Activity的allowTaskReparenting属性为true,那么当B被启动后,此Activity会直接从A的任务栈转移到B的任务栈中(栈顶)


Android中允许将四大组件放到单独的进程中运行,只有四大组件可以放到别的进程中

补充

android:allowBackup

它表示是否允许应用程序参与备份。如果将该属性设置为false,则即使备份整个系统,也不会执行这个应用程序的备份操作,而整个系统备份能导致所有应用程序数据通过ADB来保存。该属性必须是一个布尔值,或为true,或为false,其默认值为true。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息