天天看点

Android四大组件之Activity(intent、ActivityThread)

Activity

在Android的程序当中,Activity 一般代表手机屏幕的一屏。如果把手机比作一个浏览器,那么Activity就相当于一个网页。在Activity 当中可以添加一些Button、Check box 等控件。可以看到Activity 概念和网页的概念相当类似。Activity 之间的跳转可以有返回值

基本使用步骤: new activity文件 自动生成相应.xml文件 然后在Manifest.xml文件中 声明Activity 若要作为首启动Activity,则加 再在相应XML中配置组件 在JAVA中设置方法和属性

1.Activity生命周期:(以栈的形式存储不同Activity)

Android四大组件之Activity(intent、ActivityThread)

onCreate()

Activity创建时调用,做初始化设置

onStart()

Activity即将可见时调用

onRestoreInstanceState()

只有在Activity onDestroy之后,再次初始化Activity后才会回调该方法

onResume()

Activity(重新)恢复调用

onWindowFocusChanged()

Activity(重新)真正获取焦点或者失去焦点会调用 这里view能获取真正的width和height

强调的是Activity 并非 View

因此 无论View是可见非可见 只要view所在的Activity显示了 都是会回调这个接口的 并且参数为true

只是当View为GONE时 width和height为0 INVISIBLE和VISIBLE时 都能拿到正确的width和height

并且若Activity没有变,View从GONE变成了VISIBLE或者横竖屏是不会回调这个接口 因此这个接口并不是view的focusChanged 是window的!

可以在View的onMesure中 获取measureWidth和measureHeight 只有在view可见时,真正计算了长宽,才会回调,并且在super.OnMeasure之后去获取长宽是正确的

onpause()

界面被覆盖或界面不可见时调用

(理论上是可见但是不可操作时调用)

(实际:弹框Dialog这种不会调用onPause 但是系统的权限申请会,锁屏会,将Activity设置为Dialog的Theme的会回调)

onrestart()

界面不可见到再次恢复

onStop()

界面对用户不可见时

(理论上是不可操作并且不可见时调用)

(实际 锁屏和跳转界面都会调用,将Activity设置为Dialog的Theme的不会回调)

onSaveInstanceState()

在onStop后就会调用 用于保存界面的数据

onDestroy()

界面销毁:

onSaveInstanceState() && onRestoreInstanceState()

onSaveInstanceState() 会保存Activity和Fragment的状态

1、当用户按下Home键 app处于后台,此时会调用onSaveInstanceState 方法

2、当用户按下电源键时,会调用onSaveInstanceState 方法

3、当Activity进行横竖屏切换的时候也会调用onSaveInstanceState 方法

4、从BActivity跳转到CActivity的时候 BActivity也会调用onSaveInstanceState 方法

虽然以上四种情况会执行onSaveInstanceState 方法 但是并不是都会执行onRestoreInstanceState方法,只有第三种情况会调用onRestoreInstanceState,因为当Activity横竖屏切换的时候会重新走一遍其生命周期,所以Activity会被销毁创建。由此会执行onRestoreInstanceState方法。

正常退出,如: finish()或用户按下back,不会回调onSaveInstanceState

设置屏幕旋转:

屏幕改变为横屏:

android:screenOrientation="landscape"

configChanges属性

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

设置

android:configChanges=

,Activity生命周期在某些场景下不一定会被回调

场景:

// 屏幕旋转时,还是会重新调用各个生命周期,切横、竖屏时只会执行一次
"orientation"
// 屏幕大小改变时
"screenSize"
// 键盘显示或隐藏时 
"keyboardHidden"
// 键盘类型变更,例如手机从12键盘切换到全键盘
"keyboard"
// 用户变更了首选的字体大小
"fontScale"
// 用户选择了不同的语言设定
"locale"

// 只会回调onConfigurationChanged方法 而不会重新执行Activity生命周期
android:configChanges="orientation|keyboardHidden|screenSize"
// 会重新调用各个生命周期的 要screenSize
android:configChanges="orientation|keyboardHidden"
           

2.四种启动模式:

Standard模式:(闹钟程序)

每启动一个Activity就会在栈顶创建一个新实例(不管是不是之前有的)

singleTop模式:(浏览器书签)

每启动一个Activity就会在栈顶创建一个新实例(若该Activity已在栈顶中 则不用创建)

SingleTask模式:(浏览器主界面)

每启动一个Activity前会先在栈中查找有无该Activity,若有 则将其之上的所有实例出栈 ,(而自己会调用onNewIntent())没有才创新栈

singleInsatence模式:(来电显示)

启动一个新栈来管理该Activity,无论哪个栈启动该Activity,都会将该Activity所在栈转移到前台

3 Task

可以在AndroidManifest的Activity标签里面显式指定一个taskAffinity的属性,也就是说该Activity归属于对应taskAffinity的栈

若不指定 则该Activity默认使用的是包名对应的Task。

若对于同一个 Intent(action 起跳转界面和终跳转界面 各种值完全一致) 则会将整个Task移至栈顶

如在桌面点击你的应用并再次返回桌面(此时栈结构:YourActivity1->YourActivity2->桌面Activity),在桌面时,再次点击应用,启动的是带有LAUNCHER标签的界面,但是若该Activity的Task已存在,则会直接将整个Task移至栈顶(此时栈结构:桌面Activity->YourActivity1->YourActivity2)

而若通过第三方应用安装并打开你的应用(此时栈结构:桌面Activity->ThirdActivity1->ThirdActivity2->YourActivity1->YourActivity2),此时你应用的Task和桌面创建的Task是不一致的 因此 ,当再返回桌面(此时栈结构:ThirdActivity1->ThirdActivity2->YourActivity1->YourActivity2->桌面Activity) 再点击你的应用(此时栈结构:ThirdActivity1->ThirdActivity2->YourActivity1->YourActivity2->桌面Activity->YourActivity1->YourActivity2)

通过上层传标志位进行判断是否直接跳转界面还是重新加载

Activity之间的转换

当Activity A 运行时,另一个Activity B 运行,则A会调用 onSaveInstanceState()方法

若回调回去A,则当A没有销毁,则A调用 onResume() 若已经销毁,则onCreate并且使用SaveInstanceState的参数

4.Intent(实现界面的跳转和数据交互 以及与service等的交互)

Intent

记得在Manifest.xml文件中加入 新增的Activity名 如果是新建的Activity的方式,则Manifest.xml中已经自动配置了

可以跨进程通信,Intent->AIDL->Binder->Ashmen

Activity之间传递大图片

1 压缩一下

2 文件

3 数据库

4 静态变量

5 EventBus

6 还是通过Intent,只是通过

putBinder

的方式传递Bitmap的,此时系统是会将

allowFds

设置为true,运行带fd描述字符的,当传递数据的时候,首先会判断当前数据是否小于16K,小于16KB的时候会直接使用Binder的缓存空间,而当大于16KB的时候,则开辟一个ashmem,映射出一块内存,该数据会保存到ashmem中,在Intent中之写入一个fd的文件描述符,这样即使传输的数据再大,Intent中传输的也只是该资源的文件描述符。而ashmem就是利用了共享内存,在发送端和接收端之间映射同一块内存,无需多次拷贝。

Android Intent底层原理以及大小限制原因

Intent用的就是AIDL,底层就是用的Binder,可以通过Bundle传输序列化的数据,但是传输的数据因为受binder影响,有大小限制(1M 在 native/libs/binder/processState.cpp),超出了会报TransactionTooLargeException,并且也不是说1M以下就安全,这个1M是针对这个进程的Binder的transaction buffer大小

解决方案: 1 分段传输 2 使用别的IPC方案(文件 数据库 静态变量 EventBus)

显式意图:

(不在按钮事件中直接使用 新增一个函数调用)

直接显式调用另一个界面:

Intent intent = new Intent(当前类.this,另一个Activity.class(另一个Activity的类名.class))
           

数据传递

Intent.putExtra(“标签名 用来get的时候识别后面的message的”,message);
startActivity(intent);
           

intent传递的时候 尽量避免使用Int值 而是建议用String值或者传递一个对象(bean类对象) 通过 getSerializableExtra获取

这样避免int值是空的导致npe 也减少了trycatch的使用

隐式意图:(通过系统action或category的动作来调用)

Intent intent = new Intent();
Intent.setAction(可以是内部动作也可以是自定义的一个动作(”abcdefg”) );
(在目标activity的清单文件中配置<intent filter> <action android name=”abcdefg”)/>
startActivity(intent);
           

数据接收:

Intent intent =getIntent();
String name=intent.getStringExtra(“标签名”);
int a =intent.getIntExtra("flag",2);    // //后一个数值为 前一个不存在时 指定的一个默认值
           

数据回传:

在父Activity中,开启Activity2

Intent intent = new Intent();
StartActivityForResult(intent,1);(1为请求码 用于连接activity2)
           

在子Activity中,添加返回数据

Intent intent = new Intent()
Intent.putExtra(“标签名 用来get的时候识别后面的message的”,message);
setResult(1,intent)
           

再在Activity1中

重写onActivityResult方法 获得返回的数据

@Override
    public void onActivityResult(int requestCode,int resultCode,Intent data){
        super.onActivityResult(requestCode,resultCode,data);
        if(requestCode==1){
            if(resultCode==1){
                customerID = data.getStringExtra("customerID");
                xiangGuankeHu.setText(data.getStringExtra("customerName"));
            }
        }
    }
           

传递序列化对象

val intent = Intent()
val bundle = Bundle()
bundle.putSerializable("doc_id_list", mDocIdList)
intent.putExtras(bundle)


接收:
val bundle = intent?.extras
val docIdList = bundle?.getSerializable("doc_id_list") as ArrayList<String>
           

接收URI数据

data:Intent?
 data.getData() return Uri
 data.getDataString return Uri转的String/null
 dataString中也可能存在多个URI  通过 URI.parseUri() 即可分隔 再放到array中
data.getClipData return ClipData 
ClipData 剪切板格式 如可以用来传URI
若有多个 得到的是一个Uri[]的数组
clipData.getItemCount()
           

Activity用另一个xml文件的组件:

final View dialogView = LayoutInflater.from(当前类.this)
        .inflate(R.layout.你想要的XML文件,null);
           

然后

**Private 组件 **

组件=dialogView.findViewById(R.id.progress_bar);

dialogView 一定不能漏了!!!!!!!!!!!!!!!!!!!!!!!!

非Activity使用Intent

Private Context mContext;
intent.setClass(mContext, 跳转到的Activity.class); 
intent.setFlags( Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
           

但是系统的activity好像不行

5.Activity窗口模式

在Theme中 使用Theme.dialog

6 Activity不显示界面:

可以在ManiFest文件中作了如下设定:

android:theme="@android:style/Theme.NoDisplay"
           

或者设置子 activity 不显示界面:

<activity
android:name="com.learns.LocationManager"
android:theme="@android:style/Theme.NoDisplay">
</activity>
           

界面A跳转界面B:

若没有调用onDestroy,界面A的操作是执行不了的,但是若开启了线程之类的仍可以

若调用了onDestroy则所有都不会执行了 相当于这个对象被回收了

setContentView

setContentView可以设置activity 的layout id(R.id.xx) 也可以设置root view(xxView)

setContentView实际上会添加一层FrameLayout(即带标题栏的那层)因此叫setContentView 而不是 setView

setContentView后才进行view的初始化 不然是找不到这个view的

若是传的是resId 其实是会调用Inflate方法的

LayoutInflater

获取LayoutInflater:

1 LayoutInflater.from(context);
2 (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)
           

通过 resId去获取相应的view:

layoutInflater.inflate(resourceId, root);
           

第二个是指给该布局的外部再嵌套一层父布局

inflate相当于是创建了一个view

如:

通过 (activity)findViewById()得到的是 带 AppCompatButton

而通过layoutinflater的 得到的是Button 这两个虽然对应的id是一样的 但是是不同的对象和类型

findViewById()也是要产生一个对象的 会将context和 attributes(即xml内容) 传入

findViewById 真正准确的是通过它的parent.findViewById得到的对象

而通过rootView.findViewById 或(activity) findViewById()得到的结果是首次加载parent.findViewById的对象

通过getId()得到的ViewId则都是一样的

关闭另一个界面:

1 直接EventBus(跨进程用Binder 如AIDL) 收到消息后自己关闭自己

2 若是A唤起的B 要关闭B

通过startActivityResult设置reqCode

然后通过在A中 使用finishActivity(reqCode)来关闭界面B

仅适用于该场景

原理是:通过Binder调用AMS的finishActivity方法(finish也是)

关另一个app 还是通过广播每个界面都监听 然后正常相关逻辑进行退出 然后关闭finish掉界面

而不是

android.os.Process.killProcess(android.os.Process.myPid()

这个可能会有bug

安全退出多个Activity

1 递归方式:在onActivityResult中,用一个标志递归退出

2 广播机制:每个Activity接收广播,进行退出

ActivityCompat.finishAffinity(this)
           

判断界面是否存在:

val intent = Intent()
intent.setClassName("应用包名", "界面名全路径包括包名")
val resolveInfo = context.packageManager.resolveActivity(intent, 0)
resolveInfo.activityInfo.name}
           

一个应用的Context个数是Activity个数+Service个数+1(applicationContext)

ActivityThread

一个依附于主线程的对象(很多文章说他就是主线程是错的)

只是他有一个main函数,main函数中会创建MainLooper