安卓基础之Activity

最近在整理笔记,因为我很多东西都是放在印象笔记里面,写的也不是特别规范,有时候时间长了,自己回头看都有点懵圈,现在一点点的挪到博客里来,也当温故知新,我会尽量写的规范一点,如果能帮到有需要的朋友,也是件好事。

一. Activity的定义

1.构建 类 继承安卓系统的Activity类,一般为AppCompatActivity
2.清单文件中配置标签

	    <!-- Activity必须要在清单文件中进行配置 -->
        <activity
            android:name="com.haha.activity.SecondActivity"
            android:icon="@drawable/photo2"
            android:label="呵呵" >
        </activity>

如果Activity在清单文件中配置意图过滤器intent-filter,可以指定应用程序的入口:

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

注意点:4.0之后的新特性,不写intent-filter,代表应用程序中没有程序入口,但如果第一次是有入口进行过部署,后期删除了入口,这种情况后台会运行。

二.Activity 之间的跳转

显示跳转

  1. 常用方法:本项目中的字节码跳转方法

    首先构建意图对象,直接指定要跳转的Activity的字节码和所在包名
    Intent intent = new Intent(this,类名.class,);
    开启跳转服务
    startActivity(intent);

  2. 根据包名跳转

    可跳到不同的项目中,一般不适用,因为包名会更改,更多的时候适用隐式跳转来跳不同的项目

     Intent intent = new Intent();   
     intent.setClassName("指定Activity目标所在应用的包名"/或者写this,"要跳转的Activity的完整类名");
     startActivity(intent);

隐式跳转

  1. 隐式启动时会遍历项目中所有的清单文件,对意图筛选器进行匹配(注意是对筛选器中的所有属性进行匹配),匹配到了就跳转,匹配不到就报错。
    特点:
    a.一个Activity可以定义多个intent-filter,只要匹配一个就可以
    b.一个intent-filter中的可以定义多个相同的标签,任意匹配一个就可以,Intent-filter的标签有action、category、data。
    c.三个标签都存在时,每个标签至少匹配一个。
    d.如果一个项目中有多个筛选器与之相匹配,会弹出对话框让用户进行选择
	<activity
    android:name=".module.ui.VideoSplashActivity"
    android:screenOrientation="landscape"
    android:theme="@style/fullWindow">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <action android:name="a.b.c" />
        <action android:name="hahh.hehe.duang" />
        <category android:name="android.intent.category.LAUNCHER" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:mimeType="text/plain"/>
    </intent-filter>
	</activity>

intent-filter中可以添加的标签:

  • action
    action是一个字符串,系统预定义了一些action,同时我们也可以在应用中定义自己的action,action的匹配规则是Intent中的action必须能够和过滤规则中的action匹配,无论有几个action,只要任意一个可以匹配上即可成功。也就是隐式启动必须要设置一个action。另外action区分大小写。
     <action android:name ="a.b.c"/>
  • category
    同样系统为我们预定义了一些category,我们也可以自定义。他的匹配规则为要求Intent如果含有该标签,那么这个标签一定要存在于过滤规则中,如果不存在就会报错,但是可以不写,与action不同,action是必须写一个。
    Activity只有配置了action和category才能隐式启动,actiond的name值可以由自己定义,格式是同上,而category是种类的意思。我们是为了演示所以选择默认,当选择默认的时候,在逻辑代码中可以省略不写,系统会自动添加,注意点,如果我们没有写category,系统依旧会帮我们进行添加,这样就匹配不上了,会报错
  • data
    data的匹配规则和action类似,如果过滤规则中定义了data,那么Intent中必须也要定义与之匹配的data。data由两部分构成,mimeType和URI;mimeType指的是媒体类型,比如image/jpeg,audio/mpeg4-generic等,而URI包含的数据就多了,URI的结构如下:
	<scheme>:<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]
	实例:
	http://www.google.com:8080/search/info

scheme:指的是URI的模式,比如http,file,comtent等等,如果没有指定scheme,整个URI的其他参数都是无效的。
Host:主机名,没有指定的话,其他的参数也是无效的
Port:端口号,仅当指定了模式和主机名后该参数才是有效的
Path,pathPattern,pathPrenfix是同级的,path和pathPattern表示完整的路径信息,但是它里面可以包含通配符“”,表示0或者多个任意字符,注意的时如果表示真实的字符串,需要转义,在字符前面添加“\”,比如写成”\*“,\写出”\\“;pathPrenfix表示路径的前缀信息

   <data android:scheme ="name"/>

data标签用于传递数据,在使用时,为data数据中的scheme赋值:

     Intent intent = new Intent();
     intent.setAction( "a.b.c");
     intent.setData(Uri. parse("name:小宏"));
     startActivity(intent);

在被启动的activity中可以获取请求启动的activity发送来的数据:

     Intent intent = getIntent();
     Uri data = intent.getData();
     System. out.println(data);

mimeType属性注意事项:
data标签的第二个属性值mimeType,用于指定我发送的mime类型(大类型/小类型),此处用于表示我发送的text文本类型,具体类型是name属性,这一个bug级别的属性,太tmd不要脸了,只要你写了这个属性,在匹配的时候,无论你的activity中配置了几个筛选器,只要有一个筛选器中写了这个属性,你即使不使用这个筛选器进行筛选(前面说只要有一个能匹配到就可以),他都会崩掉,而且你只能对他所在的筛选器进行匹配
所以只要写了这个mimeType类型就一定得对他进行匹配(setType)。这个属性还有一点不要脸的情况是他和setData()方法属性水火不容,无论谁写到前面他都会清空对方,所以在使用时一定得写到一起:setDateAndType
例如某个过滤规则如下:

	<data android:mimeType="text/name"
    android:scheme="http"
    android:host="a.b.c"/>

则我们在匹配这个规则的时候,可以使用如下的Intent:

     public void myClick4(View v){
         Intent intent = new Intent();
         intent.setDataAndType(Uri. parse("http://a.b.c"), "text/name");
         startActivity(intent);
     }

如果我们使用隐式启动一个Activity的时候,可以做一下判断,检查一下是否有Activity能够匹配我们的隐式Intent,如果不做判断那就很可能报错了,判断方法有两种,采用PackageManager的resolveActivity方法或者使用Intent的resolveActivity方法,如果检索不到适合的Activity就会返回null。PackageManager还提供了queryIntentActivitys方法,这个方法和resolveActivity不同的是他返回的是所有可以成功匹配的Activity信息。
这些方法都有两个参数,第一个是Intent,第二个为int类型的标志,这个标志一般我们使用MATCH_DEFAULT_ONLY这个标记位,用于仅仅匹配哪些在intent-filter中声明了category为DEFAUL的Activity,使用这个标记代表着如果该方法不返回null,你就一定可以startActivity成功

三.页面跳转时传递数据

前面我们已经了解到了,页面跳转可以传递数据,是通过Intent对象的setData方法来传递,也就是只有隐式跳转时才能使用setData传递信息,除此之外,还有两种方式可以传递消息,注意点是:传递的大多是简单信息,否则会是跳转变卡,影响用户体验。

  • 简单的封装信息,当隐式跳转时,通过setData,首先在清单文件中进行配置,指定data筛选
    intent.setData(Uri. parse(“name:小宏”));
    对应:intent.getData();

  • 如果要发送的信息稍微复杂,需要分条,可以选择直接封装到Intent中,或者使用Bundle

    • 直接封装型:
	 intent.putExtra("man", man);
     intent.putExtra( "woman", woman);
	 对应:
     Intent intent = getIntent();
     String man = intent.getStringExtra( "man");
     String woman = intent.getStringExtra("woman" );
  • 封装Bundle型
     Bundle bundle = new Bundle();   
     bundle.putString( "man", man);    
     bundle.putString( "woman", woman);    
     intent.putExtras(bundle);     
     startActivity(intent);
     对应:
     Intent intent = getIntent();
     Bundle bundle = intent.getExtras();
     String man = bundle.getString( "man");
     String woman = bundle.getString( "woman");

四.Activity的生命周期

七种生命方法

Acitivity共有7种控制生命周期的方法

  • onCreate 初始化Activity,可以调用setContentView去加载界面布局资源等
  • onStart 可见但是不可以交互,运行在后台
  • onRestart 调用onStart,当前acitivity正在重新启动
  • onResume 运行状态,可见,可交互,运行在前台
  • onPause 部分可见,当他上面有透明的页面覆盖时;一般紧跟着onStop被调用,但是有种极端情况Activity又被开启,这个时候会调用onResume,虽然可见,但是不可以交互
  • onStop 完全不可见,可做一些轻量级的回收工作,但是不能太耗时
  • onDestroy 销毁,我们可以做一些回收工作和最终的资源释放

一个Activity初始化后要经历三种方法:onCreate/onStart/onResume
点击退出按钮进行销毁时经历三个方法: onPause/onStop/onDestroy
当Activity在onStop状态时通过onRestart可以回到可见不可交互状态
注意点:当点击退出后,activity已经挂掉,但内存中依旧会保留该软件的进程,以便下次开启时不用开启进程,只加载activity,这样可以快速加载,当内存中已经不够用时,才会去随机kill掉不用的进程
Activity生命周期

  • 注意点
    (1)某天在项目中设置onActivityResult的时候,本来的意愿是从A跳转到B,从B退回的时候在onDestory里面设置setResult,把返回码给设置上,但是却发现,A的onActivityResult先执行了,B的onDestory后执行的,原因就是
    因为在 B 退回 A过程中,首先是B处于Pause 状态,然后等待 A 执行 restart——〉 start ——〉resume,然后才是B 的stop——〉destroy,而A的 onActivityResult() 需要在 B pause之后,A restart 之前 这中间调用,所以 B中的setResult()函数应该放在B pause 之前调用。
    简单的内部代码说明:Activity的启动过程还是相当复杂的,涉及Instrumentation/ActivityThread/ActivityMannagerService(AMS),启动Activity的请求由Instrumentation处理,然后通过Binder向AMS发送请求,AMS内部维护着一个ActivitStack并负责栈内的Activity同步,AMS通过ActivityThread去同步Activity的状态从而完成生命周期方法的调用,

    (2)当用户打开新的Activity或者切换到桌面的时候执行onPause --> onStop,有种特殊情况,如果新的Activity采取了透明的主题,那么当前Activity不会回调onStop。
    (3)当Activity被系统回收后再次打开,生命周期的回调和第一次启动一样,但是过程稍有不同,后面会详细说明。

异常状态下的生命周期

Activity的正常生命周期如上所示,但是还有一些异常的情况,比如资源相关系统配置发生改变或者内存不足时,Activity就有可能被杀死,这个时候的生命周期和正常是不一样的。

  1. 资源相关的系统配置发生改变导致Activity被杀死并重新创建
    这种情况举例说明就是一张图片,我们有不同的样式,xdpi,xxdpi,land(横屏)等,当Activity处于竖屏的状态,如果旋转屏幕,由于系统配置发生改变,默认的情况下Activity被销毁并且重新创建,我们也可以阻止系统重新创建,但是默认情况下系统会重建Activity的。

    默认的情况下,当系统配置发生改变后,Activity会被销毁并重新创建,其生命周期为:
    在这里插入图片描述
    当异常发生后,旧的Activity依旧会被销毁,其正常的onPause,onStop,onDestory依旧会走,除此之外,系统还会调用onSaveInstanceState来保存当前的Actvity的状态,该方法调用于onStop之前,与onPause没有太多 的关联。当Activity重建后系统会调用onRestoreInstanceState,并把销毁时在onSaveInstanceState里面保存的Bundle对象作为参数传递给该方法和onCreate方法,该方法是在onStart调用之后进行调用。这两个方法都是异常情况 下才进行调用的,如果Activity正常流程,是不会调用这两个方法的。
    这两个方法是系统默认给我们做了一些恢复工作,系统会默认给我们保存当前Activity的视图结构,ListView的滚动位置等等,每一个View都会实现这两个方法,具体恢复什么可以去查源码。对应保存和恢复View的层次结 构,流程是这样的,首先Activity被意外终止时,Activity会调用onSaveInstanceState来保存数据,然后Activity会委托Window去保存数据,接着Window会委托他上层的顶级容器来保存数据。顶层的容器是一个ViewGroup,一般来 说是DecorView,最后顶层再一一的通知子元素来保存数据,和事件分发机制类似,是一种委托思想。

  2. 资源内存不足的情况导致低优先级的Activity被杀死
    这种情况下的数据存储和回复过程和(1)中完全一致。Activity的优先级大概可以分为3种:
    a.正在和用户交互的Activity,优先级最高
    b.可见但不是前台Activity,比如一个Activity上面弹出了对话框,导致可见但无法与用户交互
    c.后台的Activity,例如已经执行了onStop的Activity。
    所以一些后台工作最高关联一下四大组件,这样进程很容易被干掉。

  3. 横竖屏切换
    当横竖屏切换时,如果不进行设置他会kill掉Activity然后重建,所以很多应用软件不提供横竖屏转换的功能
    1). 当由竖屏转为横屏时,开启layout_land(此文件夹由我们自己创建)中的同名布局文件加载横屏布局。
    2).清单文件写死方向:在清单文件声明 Activity时配置 节点的几个属性

    android:screenOrientation=“portrait”//竖屏
    android:screenOrientation=“landsoape”//横屏

    3).清单文件配置当切换横竖屏时,系统依旧显示竖屏,不理会,这样不会销毁重建(注意不要再在代码中修改了,否则会覆盖掉他)

    android:configChanges =“orientation|screenSize”

    4).代码中写死文件方向,在初始化oncreate时

    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);//横屏
    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);//竖屏

    当代码设置文件方向后会覆盖掉原先清单文件中的设置
    很多时候会用到屏幕旋转时需要对一些数据进行保存,例如当横竖屏区切换时要保存先前屏幕的一些数据和状态,有两个方法提供使用:

    a.当前的Activity不销毁:
    那么就需要在AndroidManifest.xml配置文件中的Activity标签下面添加:Android:configChanges=“orientation|keyboardHidden”

    然后在activity中重写onConfigurationChanged()方法,每次旋转时会调用该方法,可以再该方法中处理数据

    b.销毁当前的Activity:

    如果销毁当前的Activity,那么就要重写Activity的两个方法onSaveInstanceState()和 onRestoreInstanceState(),上面已经讲到了。

五.Activity启动模式

  • LaunchMode

    在清单文件中配置activity时,有一个属性可以设置启动模式,如果不设置,就默认正常启动模式.
    (1)standard 正常模式
    系统会创建多个实例并把他们一一放到任务栈中,遵循后进先出的结构,当任务栈中清空时,系统就会回收这个任务栈。一个任务栈中可以有多个实例,每个实例也可以属于不同的任务栈,默认模式下,谁启动了这个Activity这个Activity就运行在启动他的Activity所在的栈中,比如A启动了B,B是标准模式,那么B就会进入A所在的栈中。
    注意点:当使用ApplicationContext去启动默认模式的Activity时会报错,因为默认模式下被启动的Activity会进入启动他的Activity所属的栈当中,非Activity类型的Context没有任务栈,所以就报错了,解决该问题可以为被启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记,也就是新开一个任务栈,但这种情况就相当于singleTask模式。
    (2)singleTop 栈顶复用
    如果栈顶就是这个Activity那么就不会重新创建了,同时它的onNewIntent方法会被回调,通过此方法参数去获取当前的请求信息,但是这个Activity的onCreate、onStart方法不会被重新调用。
    (3)singleTask 栈内复用
    如果栈中有该实例了就不再重新创建,如果要再次跳转到该页面,他会kill掉他上面的Activity,返回该Activity。使用该模式,系统先去寻找Activity所需要的任务栈,如果任务栈不存在,首先创建任务栈,比如A要以singleTask的模式启动,他所需的任务栈为s2,由于s2和A都不存在,系统会先创建s2,再创建A压栈。
    (4)singleInstance 单实例模式
    该模式会在独立的任务栈中创建一个实例,并且只会创建一次,任何跳转到该实例的操作都是把独立的栈提到前台显示,一个栈中只装一个实例.

  • Activity的flags
    1.FLAG_ACTIVITY_NEW_TASK
    指定Activity为“singleTask”启动模式
    2.FLAG_ACTIVITY_SINGLE_TOP
    指定Activity启动模式为singleTop
    3.FLAG_ACTIVITY_CLEAR_TOP
    使用该标记位启动的Activity同一个任务栈中位于该Activity上面的所有Activity都会被清空,这个标记位一般和singleTask启动模式一块出现,这种情况下,被启动的Activity如果已经存在,那么系统就会调用他的onNewIntent。如果被启动的Activity采用默认模式启动,那么该Activity和他上面的所有Activity都会被清空,然后系统创建新的Activity放入栈顶。
    4.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
    具有这个标记的Activity不会出现在历史Activity列表中,当某些情况下我们不希望用户通过历史列表回到我们的Activity的时候就需要用到他,和我们在清单文件中配置Activity的属性android:excludeFromRecents = “true”

  • 任务栈
    每个Activity都是运行在栈当中的,从一个参数来说明,该参数为TaskAffinity,称之为任务相关性,这个参数标识了一个Activity所需任务栈的名字,默认的情况下,所有的Activity所需的任务栈的名字为应用的包名,我们可以为每个Activity单独制定TsdkAffinity属性,这个属性值不能和包名相同,否则就相当于没有制定,该属性值为字符串,并且中间必须包含包名分隔符“.”。TaskAffinity主要和singleTask启动模式或者allowTaskReparenting属性配合使用,其他的情况下没有意义。
    (1)与singleTask启动模式配对使用,它具有该模式Activity目前任务栈的名字,待启动的Activity将运行在名字和TaskAffinity相同的人物栈当中。
    (2)与AllowTaskReparenting结合
    这种情况较为复杂,举例有两个应用A和B,A中的一个Activity称之为a1,他启动了应用B中的一个activity,称之为b1,如果b1的allowTaskReparenting为true的话,b1就从a1的任务栈转移到应用B的任务栈中;如果这个时候你点击了home键回到桌面,再点击应用B,弹显示的界面为b1,而不是B应用应该显示的主页面。由于A启动了B应用的b1页面,原本b1应该运行在A应用的任务栈中,但是b1属于B应用,他的TaskAffinity值不会和A应用的任务栈同名,因为包名不同,所以当B启动后,B会创建自己的任务栈,当任务栈创建完成之后,b1页面就从A的任务栈中转移过来了。

  • 设置启动模式

  • 通过清单文件指定

	<activity
    android:name=".module.ui.MainActivity"
    android:launchMode="singleTask"
    android:theme="@style/MainTheme" />
  • 通过Intent设置标志位
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);

第二种方式的优先级要高于第一种,当两种模式同时存在,以第二种方式为主;第一种方式无法直接为Activity设定FLAG_ACTIVITY_CLEAR_TOP,第二种方式无法为Activit指定singleInstance模式。

六.Activity数据回传

  1. 数据回传时,开启Acitivity的方法不是startActivity,而是statActivityForResult,这个方法可以使用onActivityReslut方法来获取回传的数据
    Intent intent = new Intent( this, ContactActivity.class );
    //启动Activity,并告知系统,当这个Activity销毁会回传数据,第二个参数是请求码,用于判断是哪个方法请求的以便接收对应Activity回传的数据
    startActivityForResult(intent, 1)
  1. 二级Activity销毁时发送信息给上一级
    //当Activity销毁时,data就会传递给上一级Activity,第一个参数为响应码,用于区分不同的数据给上级
     setResult(10, intent);
  1. 销毁当前Activity使用finish()方法
   //销毁当前Activity
     finish();
  1. 在初始Acitivity中复写onActivityResult方法接收销毁后的Activity返回的数据
  //系统回调,用于接收回传的数据,requestCode指请求码,resultCode指响应码,data是封装数据的Intent,在super之后我们可以对其进行操作
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         super.onActivityResult(requestCode, resultCode, data);
     }
  • Acitivity接收来自多个页面的请求时如何区分
    当我们有这样的需求,一个Activity页面由多个页面请求过来的,而又对这多个页面需要进行区分以便进行数据的回传和界面的不同效果展示,而具体怎样区分多个页面,可以通过在意图对象Intent中配置信息来区分,当前Activity拿到Intent对象之后可以解析Intent对象中的同一条信息。
    例如A和B界面都访问C界面,在C界面分别对这两个界面有不同的显示效果和数据回传,那么这时候A和B界面可以把信息封装到Intent对象中:
    A:
Intent intent = new Intent(this,C.class);
intent.setAction("A");
startActivityForResult(intent,1);

B:

Intent intent = new Intent(this,C.class);
intent.setAction("B");
startActivityForResult(intent,1);

C:

String action = getAction();
if("A".equals(action)){
......
}else if("B".equls(action)){
......
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值