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)

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值