Java
equals和==、hashCode的区别
- == 比较栈中存储的值是否相同
- equals 如果不重写equals方法时,其和==作用相同,Object类默认实现就是通过==来实现equals的,重写后按照会根据equals返回结果比较.
- 重写equals方法时需要重写hashCode方法,主要是针对Map、Set等集合类型的使用;
a: Map、Set等集合类型存放的对象必须是唯一的;
b: 集合类判断两个对象是否相等,是先判断equals是否相等,如果equals返回TRUE,还要再判断HashCode返回值是否ture,只有两者都返回ture,才认为该两个对象是相等的。 - 由于Object的hashCode返回的是对象的hash值,所以即使equals返回TRUE,集合也可能判定两个对象不等,所以必须重写hashCode方法,以保证当equals返回TRUE时,hashCode也返回Ture,这样才能使得集合中存放的对象唯一。
JVM内存模型
JVM 的内存区域可以分为两类:线程私有和区域和线程共有的区域。 线程私有的区域:程序计数器、JVM 虚拟机栈、本地方法栈;线程共有的区域:堆、方法区
- 程序计数器(Program Counter Register)也叫做PC寄存器,是一块较小的内存空间。在虚拟机概念模型中,字节码解释器工作时就是通过改变程序计数器来选取下一条需要执行的字节码指令,Java虚拟机的多线程是通过轮流切换并分配处理器执行时间的方式来实现的,在一个确定的时刻只有一个处理器执行一条线程中的指令,为了在线程切换后能恢复到正确的执行位置,每个线程都会有一个独立的程序计数器,因此,程序计数器是线程私有的。如果线程执行的方法不是Native方法,则程序计数器保存正在执行的字节码指令地址,如果是Native方法则程序计数器的值则为空(Undefined)。程序计数器是Java虚拟机规范中唯一没有规定任何OutOfMemoryError情况的数据区域。
-
Java虚拟机栈(Java Virtual Machine Stacks)
它的生命周期与线程相同,与线程是同时创建的。Java虚拟机栈存储线程中Java方法调用的状态,包括局部变量、参数、返回值以及运算的中间结果等。一个Java虚拟机栈包含了多个栈帧,一个栈帧用来存储局部变量表、操作数栈、动态链接、方法出口等信息。当线程调用一个Java方法时,虚拟机压入一个新的栈帧到该线程的Java栈中,当该方法执行完成,这个栈帧就从Java栈中弹出。我们平常所说的栈内存(Stack)指的就是Java虚拟机栈。
1.如果线程请求分配的栈容量超过Java虚拟机所允许的的最大容量,Java虚拟机会抛出StackOverflowError
2.Java虚拟机栈动态扩展内存时,无法申请到足够内存,则会报OutOfMemoryError -
本地方法栈(Native Method Stack)
与Java虚拟机栈类似,只不过其执行的是native方法,该区域Java规范没有强制要求支持,HotSpot VM将本地方法栈和Java虚拟机栈合二为一.本地方法栈也会抛出StackOverflowError或OutOfMemoryError - Java堆(Java Heap) 是被所有线程共享的运行时内存区域。Java堆用来存放对象实例,几乎所有的对象实例都在这里分配内存。Java堆存储的对象被垃圾收集器管理,这些受管理的对象无需也无法显示的销毁。从内存回收的角度,Java堆可以粗略的分为新生代和老年代。Java堆的容量可以是固定的,也可以动态的扩展。Java堆的所使用的内存在物理上不需要连续,逻辑上连续即可。堆中没有足够的内存来完成实例分配,并且堆也无法进行扩展时,则会抛出OutOfMemoryError异常。
-
方法区(Method Area),其是被所有线程共享的运行时内存区域。用来存储已经被Java虚拟机加载的类的结构信息
每一个Class文件中,都维护着一个常量池(这个保存在类文件里面,不要与方法区的运行时常量池搞混),里面存放着编译时期生成的各种字面值和符号引用;这个常量池的内容,在类加载的时候,被复制到方法区的运行时常量池 ;
如果方法区的内存空间不满足内存分配需求时,Java虚拟机会抛出OutOfMemoryError异常
类的加载过程
类的生命周期
其中前五步为加载阶段,而验证、准备、解析又可概括为链接,所以类加载大致的一个流程为加载、链接、初始化,下面我将对这三部分逐个进行详细讲解
- 加载
将类class文件内容加载到内存中,然后把静态数据转换为方法区需要的数据结构 (是转换,并不是将静态数据加载到方法区) ,最后在堆中生成一个类的Class对象用来访问方法区数据。其中class文件的表现形式就是字节数组,所以class文件的来源可以是本地文件、网络、jar包等等。另外加载过程中需要有类加载器参与,在java中类ClassLoader就是类加载器。 - 链接
1.验证: 验证加载进来的class文件各种格式是否符合JVM的要求
2.准备:为静态变量分配内存,并赋予初始值。这个阶段开发者定义的值不会赋予静态变量并且也不会执行静态代码块。但如果为final修饰的变量会直接赋予开发者定义的值。
3.解析:将常量池中的符号引用转换为直接引用
符号引用:符号引用是以一组符号来描述所引用的目标,符号可以是任何的字面形式的字面量。
直接引用:是指向目标的指针,偏移量或者能够直接定位的句柄。该引用是和内存中的而布局有关的,并且一定加载进来的。
- 初始化
初始化为类加载过程的最后一个阶段,这个阶段会为静态变量进行赋值,并且顺序执行静态代码块
Java类执行过程
类初始化的4种情况
- 遇到new,getstatic,putstatic,invokestatic这失调字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译器把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
- 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
- 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
- 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
Java引用及GC垃圾回收机制
Java Exception和Error 异常
Java I/O
Java泛型
Java反射
Jni介绍
Java多线程
Android
Android 系统架构
Android系统架构分为五层
- 应用层
- 应用框架层(Java API Framework),向应用层提供api,例如:ActivityManager,LocationManager,PackageManager,ContentProvider等
- 系统运行库层(Native)
1.C++程序库 SQLite轻型关系型数据库引擎
2.Android运行时库 包括核心库和ART虚拟机(Android5.0后,Dalvik虚拟机被ART取代)。核心库提供了Java语言核心库大多数功能,这样开发者才可以使用Java语言编写Android应用。Dalvik虚拟机(DVM)中应用每次运行,字节码都需要通过即时编译器(Just In Time,JIT)转化为机器码,这会使应用运行效率降低。而ART,系统在安装应用时会进行一次预编译(Ahead Of Time,AOT),将字节码预先编译为机器码并存储在本地,这样应用每次运行时就无需编译了,运行效率大大提高。 - 硬件抽象层(HAL) 隐藏了特定平台的硬件接口细节,为操作系统提供虚拟硬件平台。
- Linux 内核层(Linux Kernel) 系统的内存管理,驱动模型都依赖于该内核。
JVM&DVM&ART区别
Android系统启动过程
- 启动电源以及系统启动
当电源按下时引导芯片代码开始从预定义的地方(固化在ROM)开始执行。加载引导程序Bootloader到RAM,然后执行。 - 引导程序Bootloader
引导程序是在Android操作系统开始运行前的一个小程序,它的主要作用是把系统OS拉起来并运行。 - linux内核启动 (Linux 内核层)
内核启动时,设置缓存、被保护存储器、计划列表,加载驱动。当内核完成系统设置,它首先在系统文件中寻找”init”文件,然后启动root进程或者系统的第一个进程。 - init进程启动(Native层)
init进程解析init.rc文件孵化出Zygote进程,Zygote进程是Android系统第一个Java进程(虚拟机进程),zygote进程是所有Java进程的父进程。 - Zygote进程启动(Framework层)
创建Java虚拟机并为Java虚拟机注册JNI方法,创建服务端Socket,启动SystemServer进程 - SystemServer进程启动
启动各种系统服务,如ActivityManagerService,PackageManagerService,WindowManagerService等。 - Launcher启动
AMS会启动Launcher。
Apk打包流程
myapp.apk
|------assets/ // 原封不动打包
---lib/ // 原封不动打包
--armeabi-v7a
- libconversation.so
---META-INF/
--CERT.RSA // 这个文件保存了签名和公钥证书。
--CERT.SF
--MANIFEST.MF
---res/ // 所有XML文件都是二进制
--anim/
--color/
--drawable/
--layout/
--menu/
--raw/
--xml/
...
---AndroidManifest.xml // 二进制
---classes.dex // *.java(通过javac)--->*.class(通过dx工具)--->classes.dex
---resources.arsc // 记录了所有的应用程序资源目录的信息,将其想象成是一个资源索引表,这个资源索引表在给定资源ID和设备配置信息(例如设备分辨率)的情况下,能够在应用程序的资源目录中快速地找到最匹配的资源
- 通过aapt工具编译所有资源文件(res目录但不包括res/raw)生成R.java(但res/raw资源也会在R.java)供代码引用资源。
assets和res/raw不参与编译,会原封不动打包到apk,res目录下的其他xml资源和AndroidManifest.xml都会编译为二进制。
打包工具aapt负责编译和打包资源,编译完成之后,会生成一个resources.arsc文件和一个R.java,前者保存的是一个资源索引表,后者定义了各个资源ID常量。res/values会被直接保存在resource.arsc中。
那为什么要将文本格式的xml转为二进制格式的xml呢?
(1) 二进制格式的XML文件占用空间更小。
(2) 二进制格式的XML文件解析速度更快。 - 通过aidl工具将aidl代码转化为java代码
- 所有的java文件通过javac编译为class字节码文件(包括R.java和aidl生成的java类)
- 将生成的class文件和第三方jar包通过dx工具生成classes.dex
- 通过apkbuilder(最新的sdk没有该脚本,是通过sdklib.jar完成)将so文件,classes.dex、resources.arsc、res文件夹(res/raw资源被原装不动地打包进APK之外,其它的资源都会被编译或者处理)、Other Resources(assets文件夹)、AndroidManifest.xml打包成apk文件
1.res/raw和assets的相同点:
两者目录下的文件在打包后会原封不动的保存在apk包中,不会被编译成二进制。
2.res/raw和assets的不同点:
res/raw中的文件会被映射到R.java文件中,访问的时候直接使用资源ID即R.id.filename;assets文件夹下的文件不会被映射到R.java中,访问的时候需要AssetManager类。
res/raw不可以有目录结构,而assets则可以有目录结构,也就是assets目录下可以再建立文件夹 - jarsigner对apk进行签名,可以进行Debug和Release 签名。
- 通过zipalign工具对apk中的未压缩资源(图片、视频)进行“对齐操作”,让资源按4字节的边界进行对齐,使得资源访问速度更快。(优化思想类似结构体变量分配时采用内存对齐)
Activity相关
1. 谈谈onSaveInstanceState(Bundle outState)
onSaveInstanceState一般在onStop之间调用,与onpause没有调用时序关系
- 用户行为要Activity关闭,不会执行
onSaveInstanceState
方法.当用户按下HOME键时或启动其他程序时都会调用onSaveInstanceState -
onSaveInstanceState
和onRestoreInstanceState
不是成对执行,onRestoreInstanceState转屏时会执行
2. onRestoreInstanceState(Bundle savedInstanceState)
onRestoreInstanceState在onResume之前调用,onCreate(Bundle)可能没有数据,但onRestoreInstanceState调用时一定会有.
- 其并不是和onSaveInstanceState成对回调,如果Activity并没有销毁也不需要恢复,这种情况下不会调用。
- 回调onRestoreInstanceState方法也会将保存的bundle传给onCreate()方法中的bundle
3. onPause和onSaveInstanceState区别
作用不同,onPause适用做数据持久化存储,onSaveInstanceState适用于临时状态数据
4. 4种启动模式介绍
- standard:标准模式 每次启动一个Activity,都会创建一个实例,放入启动他的Activity(除了启动它的Activity是SingleInstance情况)栈顶,即使已经存在
- singleTop:栈顶复用模式 如果新Activity已经位于栈顶,那么activity不会重新创建,同时onNewIntent方法会被回调.
- singleTask:栈内复用模式 只要该Activity在一个任务栈中存在,都不会重新创建,并回调onNewIntent方法.比如启动Activity A,先判断是否存在其想要的任务栈,如果不存在,系统会先重新创建一个任务栈将A放入该任务栈;如果存在,这时会判断该栈是否有实例存在,如果实例存在则将A调到栈顶并调用onNewIntent方法,如果实例不存在,就创建A实例并将其压栈.
- singleInstance:单实例模式 具有此模式的Activity只能单独位于一个任务栈中,且此任务栈中只有唯一一个实例.
TaskAffinity可以指定任务栈名称,默认为包名,其用来和SingleTask配对适用,其他模式没有意义.
各自适用场景
SingleTop 栈顶复用,可能有多个Activity,但SingleTask栈中只有一个.
FLAG
FLAG_ACTIVITY_NEW_TASK,FLAG_ACTIVITY_SINGLE_TOP,作用和singleTask,singleTop作用相同,但指定FLAG的优先级高.
IntentFilter匹配规则
- 一个Intent只有同时匹配某个Activity的intent-filter中的action,category,data才算完全匹配.
- 一个Activity可以有多个intent-filter,一个intent只要成功匹配任意一组就可以启动Activity.
1.action匹配规则: intent-filter可以有多个action,但Intent只能指定一个action,只要与intent-filter中一条action匹配,则action匹配
2.category匹配规则:intent可以不指定category,则系统会为其添加default的category,intent可以指定多条category,但多条category必须在intent-filter包含,如果包含则category匹配
3.data匹配规则:intent-filter中有data,则intent中必须指明data,并且该data需要和intent-filter中一个data圈圈匹配即可.
Fragment相关
1. Fragment生命周期
onAttach()->onCreate()->onCreateView()->onActivityCreated()->onStart()->onResume()->onPause()->onStop()->onDestroyView()->onDestroy()->onDetach()
- onAttach():当Fragment和Activity建立关联是调用
- onCreateView(): 当Fragment创建视图时调用
- onActivityCreated():当与Fragment关联的Activity执行完onCreate方法后调用
- onDestroyView(): Fragment中布局被移除的时候
- onDetach():与Activity解除关联时调用
2. Fragment和Activity异同
- 相同点:他们都可包含布局,有自己的生命周期
- 不同点:Fragment依附在Activity上,声明周期有宿主Activity而不是操作系统调用
3. 合适采用Fragment
- Fragment性能高,可以解决Activity切换卡顿
- Fragment对于大小屏适配非常合适
4. DialogFragment与AlertDialog的优点
- 转屏DialogFragment可保持显示,并可数据恢复
- 有着和Activity几乎一致的生命周期,更容易管理
- 大小屏幕可以定制显示状态,例如大屏幕将内容直接显示在Activity中,小屏幕显示对话框
5. 同一个Activity不同Fragment的传值
- Activity向Fragment传值
通过Activity中在添加Fragment之前调用setArgument传入Bundle值,Fragment通过getArgument即可得到Bundle - Fragment向Activity传值
采用接口回调,Fragment中定义接口,Activity实现该接口,需要传值时Fragment调用接口方法即可。Fragment中该接口的具体实现可以通过getActivity直接获得,也可通过set方法传入实现了该接口的对象。 - Fragment向Fragment传值
- 先由Fragment传值到Activity,再Activity传至Fragment
- 得到另一个Fragment的对象,调用其方法即可,eg:左右两个Fragment,左Fragment向右Fragment传值
LeftFragment
// 或者调用findFramgentByTag方法 RightFragment rightFragment =(RightFragment) getFragmentManager().findFragmentById(R.id.right_fragment);
- 同属一个Activity,可以直接得到目标Fragment的控件进行操作
Service相关
1. Service生命周期
2. startService和bindService
-
启动Service
context.startService()->onCreate()->onStartCommand()->Service运行->(如果调用context.stopService)->onDestroy
- 多次调用startService()只会走一次onCreate(),每次都会执行onStartCommand()方法
- 多次调用stopService之后的stopService不会有任何反应
-
绑定Service
context.bindService()->onCreate()->onBind()->Service绑定->(如果调用context.unbindService())->onUnbind()->onDestory();
- 多次调用bindService(),只有第一次会走onCreate()和onBind(),之后的不会走任何方法
- 多次调用unbindService(),之后的unbind会报错
Caused by: java.lang.IllegalArgumentException: Service not registered: com.breeze.b.MainActivity$1@bdbf7e8
- onBind返回null,onServiceConnected不会被回调
- Service已经被启动,这时绑定Service解绑后又重新绑定,在onRebind方法返回值为true的情况,这时会执行onRebind方法而不是调用bind
Service可以同时处于绑定状态和运行状态,只要Service存在则再start或bind就不会回调onCreate方法,当Service被start并且bind,需要同时stop和unbind才会回调onDestory()
-
启动方式和绑定方式区别
- 启动方式一旦启动Service,Service就一直运行在后台,除非调用自身
stopSelf()
方法,或者context.stopService()
;绑定方式绑定Service后,如果客户端调用unbindservice或者客户端退出后Service都会销毁,其与客户端声明周期有关 - 启动方式,调用方式无法与Service进行交互,只能启动或关闭Service;绑定方式可以通过返回的IBinder接口调用服务端的代码,从而实现交互。
- 启动方式一旦启动Service,Service就一直运行在后台,除非调用自身
3. Service如何和Activity进行通信
通过bindService可以实现Activity调用Service的方法.通过广播实现Service向Activity发送消息
4. IntentService
用于后台执行耗时任务,执行完成并可自动停止.比线程优先级高. 实现原理是通过HandlerThread消息机制.
5. Android8.0不允许启动后台Service
切换为前台Service,但必须有通知.或者通过JobIntentService实现.
6. 如何保证Service不被杀死
- Service的onStartCommand()中设置flag为START_STICKY,在运行onStartCommand后service进程被kill后,那将保留在开始状态,但是不保留那些传入的intent。不久后service就会再次尝试重新创建,因为保留在开始状态,在创建 service后将保证调用onstartCommand。
- 提升Service优先级为前台Service
- Service执行onDestory()方法时发送广播,接收到广播时重启Service
- 系统级应用
Broadcast相关
广播的类型主要分为5类:
- 普通广播(Normal Broadcast)
- 系统广播(System Broadcast)
- 有序广播(Ordered Broadcast)
- 粘性广播(Sticky Broadcast)
- App应用内广播(Local Broadcast)
1. 普通广播(Normal Broadcast)
普通广播接收没有先后顺序.最常用,分为动态注册和静态注册。但静态注册在Android8.0上做了限制,只能接收部分系统广播.
2. 系统广播(System Broadcast)
- Android中内置了多个系统广播:只要涉及到手机的基本操作(如开机、网络状态变化、拍照等等),都会发出相应的广播
- 每个广播都有特定的Intent - Filter(包括具体的action),Android常用系统广播action如下:
系统操作 | action |
---|---|
监听网络变化 | android.net.conn.CONNECTIVITY_CHANGE |
关闭或打开飞行模式 | Intent.ACTION_AIRPLANE_MODE_CHANGED |
充电时或电量发生变化 | Intent.ACTION_BATTERY_CHANGED |
系统启动完成后(仅广播一次) | Intent.ACTION_BOOT_COMPLETED |
屏幕被关闭 | Intent.ACTION_SCREEN_OFF |
屏幕被打开 | Intent.ACTION_SCREEN_ON |
3. 有序广播(Ordered Broadcast)
- 发送出去的广播被广播接收者按照先后顺序接收,
广播接受者接收广播的顺序规则(同时面向静态和动态注册的广播接受者)
按照Priority属性值从大-小排序;Priority 取值范围-1000~+1000,默认为0
Priority属性相同者,动态注册的广播优先; - 先接收的广播接收者可以对广播进行截断通过调用
abortBroadcast();
,即后接收的广播接收者不再接收到此广播;
先接收的广播接收者可以对广播进行修改通过setResultxxx(),getResultxxx()
,那么后接收的广播接收者将接收到被修改后的广播 - 有序广播的使用过程与普通广播非常类似,差异仅在于广播的发送方式:
sendOrderedBroadcast(intent);
4. 粘性广播(Sticky Broadcast)
粘性消息在发送后就一直存在于系统的消息容器里面,等待对应的处理器去处理,如果暂时没有处理器处理这个消息则一直在消息容器里面处于等待状态,粘性广播的Receiver如果被销毁,那么下次重建时会自动接收到消息数据。(在 android 5.0/api 21中deprecated,不再推荐使用)
耳机插拔广播Intent.ACTION_HEADSET_PLUG就是粘性广播
5. App应用内广播(Local Broadcast)
Android中的广播可以跨App直接通信(exported对于有intent-filter情况下默认值为true).
存在的问题
- 其他App针对性发出与当前App intent-filter相匹配的广播,由此导致当前App不断接收广播并处理;
- 其他App注册与当前App一致的intent-filter用于接收广播,获取广播具体信息;
即会出现安全性 & 效率性的问题。
App应用内广播可理解为一种局部广播,广播的发送者和接收者都同属于一个App。相比于全局广播(普通广播),App应用内广播优势体现在:安全性高 & 效率高。通常采用如下两种方式实现本地广播
- 增设相应权限permission或者通过intent.setPackage(packageName)指定包名接收
- v4包下LocalBroadcastManager实现
localBroadcastManager = LocalBroadcastManager.getInstance(this);
获取其实例,通过其发送广播和接收广播。
ContentProvider相关
ContentProvider四大组件之一,严格意义不属于数据存储,它实现存储方式是通过其他存储方式实现(文件存储,SharedPrefernces,SQLite数据).ContentProvider主要是用于不同应用之间数据共享.
数据存储方式有哪些?
- File文件存储: 写入和读取文件的方法和Java中实现I/O的程序一样
- SharedPreferences: 轻型数据存储方式,常用与存储一些简单配置信息,本质是通过XML文件存储键值对数据
- SQLite数据存储:轻型关系型数据库,速度快,占用资源少,适合存储大量复杂关系型数据
- ContentProvider: 实现数据存储与共享
- 网络存储 使用较少,暂不记录
Android数据存储的5种方式
1. SharedPreferences适用情形?适用中需要注意什么?
适用情形上面已说明.SharedPreferences底层通过读/写XML文件来实现,并发容易出问题.从而可靠性下降
Android进程和线程区别
- 进程就是一个应用程序的执行过程,它是一个动态的概念
- 线程是进程中的一部分,起始CPU调度的最小单元
进程至少包含一个线程,及主线程,Android中称为UI线程。线程之间可以共享进程资源,但进程与进程彼此独立。 - 一般一个应用对应一个进程,但也可以通过process属性开启多进程模式
- 线程是有限个:不可无限制的产生,且线程的产生和销毁都是要一定开销。
IPC
1. 为什么要进行IPC,多进程可能出现什么问题
由于每个应用都运行在独立的虚拟机,不同虚拟机内存地址空间不同,从而不同虚拟机访问同一个对象会产生多个副本,故涉及到内存共享数据,都会共享失败,一般会造成如下几个问题。
- 静态成员和单例模式完全失效
- 线程同步完全失效
不同进程的锁永远不会是同一个对象 - SharedPreference可靠性下降
SharedPreference不支持两个进程同时执行写操作,否则会引起一定概率的数据丢失 - Application多次创建
2. Serializable接口和Parcelable接口的区别
- Parcelable和Serializable都是实现序列化并且都可以用于Intent间传递数据
- Serializable是Java的实现方式,会频繁的IO操作,所以消耗比较大,但是实现方式简单。适用本地存储和网络传输。
- Parcelable是Android提供的方式,其是将一个对象进行效率比较高,但是实现起来复杂一些 。
二者的选取规则是:内存序列化上选择Parcelable, 存储到设备或者网络传输上选择Serializable(当然Parcelable也可以但是稍显复杂)
3. Android中为何新增Binder来作为主要的IPC方式?
- 传输效率高、可操作性强:传输效率主要影响因素是内存拷贝的次数,拷贝次数越少,传输速率越高。从Android进程架构角度分析:对于消息队列、Socket和管道来说,数据先从发送方的缓存区拷贝到内核开辟的缓存区中,再从内核缓存区拷贝到接收方的缓存区,一共两次拷贝,共享内存虽然无需拷贝,但控制复杂,难以使用.
- 安全性 传统Linux IPC的接收方无法获得对方进程可靠的UID/PID,从而无法鉴别对方身份;而Binder机制为每个进程分配了UID/PID且在Binder通信时会根据UID/PID进行有效性检测。
4. Binder机制?
以AIDL运行机制介绍
- 服务端中的Service给与其绑定的客户端提供Binder对象
-
客户端通过AIDL接口中的asInterface()将这个Binder对象转换为代理Proxy,通过它发起RPC请求。客户端发起请求时会挂起当前线程,并将参数写入data然后调用transact()方法,RPC请求会通过系统底层封装后由服务端的onTransact()处理,并将结果写入reply,这个过程运行在服务端中的Binder线程池,最后返回调用结果并唤醒客户端线程。
5. 是否了解AIDL?原理是什么?如何优化多模块都使用AIDL的情况
当有多个业务模块都需要AIDL来进行IPC,此时需要为每个模块创建特定的aidl文件,那么相应的Service就会很多。必然会出现系统资源耗费严重、应用过度重量级的问题。解决办法是建立Binder连接池,即将每个业务模块的Binder请求统一转发到一个远程Service中去执行,从而避免重复创建Service
每个业务模块创建自己的AIDL接口并实现此接口,然后向服务端提供自己的唯一标识和其对应的Binder对象。服务端只需要一个Service,服务器提供一个queryBinder接口,它会根据业务模块的特征来返回相应的Binder对像,不同的业务模块拿到所需的Binder对象后就可进行远程方法的调用了
6. IPC有哪些,各自优缺点适用场景
-
Intent数据传递
使用Bundle进行传递数据在Activity,Service,BroadcastReceiver中,数据支持类型有
1.8种基本类型及数组
2.String(实现了Serializable),CharSequence及对应数组 - 是实现了Serializable或者Parcelable的对象或数组
Bundle支持传输的最大数据为1M,甚至更小,因为Binder会话缓冲区大小限制为1M,它是被所有处于Binder会话的进程锁共享的。 -
文件共享 (适合对并发要求不高的进程之间使用)
- ObjectOutputStream/ObjectInputStrem实现读写对象到文件 文件可以是各种格式,只要双方约定好即可
- 并发读写容易引起问题
-
SharedPreference (不建议使用)
- 本质上也是文件共享的一种
- 系统对其有缓存策略,即内存中存在其缓存,多进程对其读写不可靠
- 高并发容易引起数据丢失
- 基于Binder的Messager和AIDL
- Socket
-
LocalSocket
LocalSocket解决的是同一台主机上不同进程间互相通信的问题。其相对于网络通信使用的socket不需要经过网络协议栈,不需要打包拆包、计算校验,自然的执行效率也高。与大名鼎鼎的binder机制作用一样,都在Android系统中作为IPC通信手段被广泛使用
android下使用localsocket可以实现C与C,C与JAVA,JAVA与JAVA进程间通信
1. MotionEvent是什么?包含几种事件?什么条件下会产生?
MotionEvent是手指触摸屏幕锁产生的一系列事件。包含的事件有:
ACTION_DOWN:手指刚接触屏幕
ACTION_MOVE:手指在屏幕上滑动
ACTION_UP:手指在屏幕上松开的一瞬间
ACTION_CANCEL:手指保持按下操作,并从当前控件转移到外层控件时会触发
2. View的事件分发机制?
触摸事件传递顺序是Activity,ViewGroup,View
而在事件分发过程中,涉及到三个最重要的方法:
- dispatchTouchEvent()
事件能够传递到当前View,则该方法一定调用,返回结果受当前View的onTouchEvent和子View的dispatchTouchEvent方法影响,表示是否消耗事件. - onInterceptTouchEvent()
判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列当中,此方法不会被再次调用,返回值表示是否拦截当前事件序列. - onTouchEvent
在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗则统一事件序列中,当前View无法再次接收到事件.
整体分发过程如下
public boolean dispatchTouchEvent(MotionEvent ev) {
//consume 表示事件是否在该View中消耗
boolean consume = false;
//是否当前View拦截事件,如果拦截,则调用本View的onTouchEvent方法,不会再下发事件序列中的其他事件
if (onInterceptTouchEvent(ev)) {
consume = onTouchEvent(ev);
} else {
//如果没有拦截事件,则会遍历手指按下时接触的所有子View(不是容器的所有子View,而是该容器中down事件所接触的子View)进行遍历派发事件,事件如果有消耗则退出循环,如果所有字View均没有消耗,则此时consume为false
for(int i=0; i<childs.length && !consume;i++) {
consume = childs[i].dispatchTouchEvent(ev);
}
}
如果派发给所有字view事件依然没有消耗,则有本View的onTouchEvent来进行处理,如果依然返回false,则当前View没有消耗事件,进一步上发事件给父View,如果顶层View依然没有处理,则Activity的onTouchEvent会被调用.
if(!consume) {
consume = onTouchEvent(ev);
}
return consume;
}
- ViewGroup在ACTION_DOWN事件时,onInterceptTouchEvent方法返回true,则所有子View都无法接收到事件,只在ACTION_MOVE或ACTION_UP时,onInterceptTouchEvent方法返回true,上次接收到事件的子View会接收了ACTION_CANCEL事件
- ACTION_DOWN时,所有View都不消耗事件时,则Activity不会再继续下事件给View.
- View的onTouchListener的onTouch方法>onTouchEvent>onClickListener的onClick方法
- 子View可通过requestDisallowInterceptTouchEvent干预父View的事件拦截,除了ACTION_DOWN事件.
具体意思就是如果父View在ACTION_DOWN时已经拦截了事件,那么子View无论如何都不会得到任何事件. 还有一种情况是,子View虽然禁止拦截事件,但自己不消耗事件,那么事件序列依然不会给其下发.
Android 子线程访问主线线程更新UI && Handler原理
1. 为什么Android无法在子线程更新UI
实质上是为了避免多线程并发问题,容易引起界面更新错乱。如果通过加锁的机制实现同步,则非常消耗性能。
2. 其他线程访问UI线程的5种方式:
- Activity.runOnUiThread(Runnable);
- View.post(Runnable);
- View.postDelayed(Runnable,long);
- Handler;
- AsyncTask。
上述5种方式包括AsyncTask本质都是通过Handler来实现的
3. Handler原理
Handler原理如下:
一个线程通过Looper.prepare会创建唯一的Looper对象,创建Looper中就会创建一个消息队列MessageQueue(数据结构是单链表队列)。创建Handler时会与当前线程的Looper及MessageQueue绑定,调用sendMessage方法发送消息Message,该消息Message会入队到MessageQueue。线程调用Looper.loop方法就会循环从MessageQueue中取消息并将该消息通过Handler的dispatchMessage方法将Message交由Handler处理。Looper负责一直从消息队列拿消息交由Handler来处理消息。
一个线程只能有一个Looper对象,并且有一个消息队列,但可以有多个Handler对象。
4. 主线程中的Looper.loop()一直无限循环为什么不会造成ANR?
造成ANR的原因一般有两种:
- 当前的事件没有机会得到处理(即主线程正在处理前一个事件,没有及时的完成或者looper被某种原因阻塞住了)
- 当前的事件正在处理,但没有及时完成
当只在UI线程延时很长时间,是不会引起ANR,如果按钮点击后延时,按钮则弹起后无法恢复,直到延时结束,如果按钮弹起,这时还要处理其他事件则会引起ANR
因为Android 的是由事件驱动的,looper.loop() 不断地接收事件、处理事件,每一个点击触摸或者说Activity的生命周期都是运行在 Looper.loop() 的控制之下,如果它停止了,应用也就停止了。只能是某一个消息或者说对消息的处理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞它。也就说我们的代码其实就是在这个循环里面去执行的,当然不会阻塞了。
5. HandlerThread
HandlerThread继承自Thread,它就是一个线程,不过做了对looper的封装。
HandlerThread的run方法中开始执行了Looper.prepare方法,在run方法结尾调用了Looper.loop方法,Looper.loop之前回调onLooperPrepared方法供子类可以进行一些设置。当在其他线程创建Handler时,需要传入HandlerThread的getLooper方法,这样该Handler才与该线程的Looper绑定了,从而Handler发送的消息才会进入该线程的MessageQueue中,Looper.loop取得消息才可以交由该Handler处理。
当线程无需处理消息时,可通过HandlerThread的quit或者quitSafely方法退出消息循环,从而run方法执行完成,该线程也就终止了。quit和quitSafely区别在于quit消息队列中的全部消息,而quitSafely只清除消息队列中的延迟消息。
6. AsyncTask相比Handler有什么优点?不足呢?
- Handler
使用的优点:结构清晰,功能定义明确
对于多个后台任务时,简单,清晰
使用的缺点:在单个后台异步处理时,显得代码过多,结构过于复杂(相对性) - AsyncTask
使用的优点:简单,快捷,过程可控
使用的缺点:在使用多个异步操作和并需要进行Ui变更时,就变得复杂起来.
7. 使用AsyncTask需要注意什么?
一个异步对象只能调用一次execute()方法