Activity


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


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

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

activity获取scheme_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