面试-android

- 工具的使用

调查内存泄漏工具: LeakCanary

如果只关注activity的内存泄漏,那么在Application中onCreate加入RefWatcher ref = LeakCanary.install(this);就OK了,会通过notification通知
如果还关注fragment的泄漏情况,那么Application加上RefWatcher,然后在对应fragment页面中onDestroy中加入:RefWatcher refWatcher = MyApplication.getRefWatcher(this); refWatcher.watch(this);
1.无法检测出Service中的内存泄漏问题
2.如果最底层的MainActivity一直未走onDestroy生命周期(它在Activity栈的最底层),无法检测出它的调用栈的内存泄漏

Android studio的工具:

Memory Monitor工具主要是用来监测APP的内存分配情况,判断是否存在内存泄漏 Dump Java Heap定位内存泄漏,
生成.hprof Eclipse: ddms->update heap->cause gc生成总内存信息,然后会有data object,然后操作界面,
看内存占用情况
1.adb shell dumpsys meminfo <package_name>/pid内查看本地层程序
2.top -m 5 -s cpu
3. procrank命令的输出就会有VSS、RSS、PSS、USS
VSS是单个进程全部可访问的地址空间,其大小可能包括还尚未在内存中驻留的部分。对于确定单个进程实际内存使用大小,VSS用处不大。
RSS是单个进程实际占用的内存大小,RSS不太准确的地方在于它包括该进程所使用共享库全部内存大小。对于一个共享库,可能被多个进程使用,实际该共享库只会被装入内存一次。进而引出了PSS,PSS相对于RSS计算共享库内存大小是按比例的。N个进程共享,该库对PSS大小的贡献只有1/N。
USS:Unique Set Size 进程独自占用的物理内存,不包含与其他进程共享占用的内存。USS去掉的是所有跨进程共享的内存,不是只去掉了共享库
VSS = 进程独占已分配但未使用内存 + 进程独占已用内存 + 共享内存
RSS = 进程独占已用内存 + 共享内存
PSS = 进程独占已用内存 + 共享内存/n
USS = 进程独占已用内存
dumpsys meminfo 使用的都是PSS

运行时错误分析工具:

addr2line 工具和 objdump 工具来定位错误

反编译工具

dex2jar转class.dex为jar
jd-gui 查看jar代码
so反编译 readelf objdump
objdump -tT libdvm.so 或者 nm -D libdvm.so 查看so函数列表
Android killer 反编译工具,查看smail语言并打包

卡顿内容等性能分析:
traceview和systrace
Systrace的功能包括跟踪系统的I/O操作、内核工作队列、CPU负载以及Android各个子系统的运行状况等, TraceView可视化工具可以看出代码在运行时的一些具体信息,方法调用时长,次数,时间比率,了解代码运行过程的效率问题,从而针对性改善代码
先使用traceview寻找当前程序有否有重复调用或者执行时间较长的函数,其次是代码分析,寻找可疑点,如果前面两步都不能准确定位的话,这时候就可以考虑用systrace

TraceView用法:

  1. Ddms打开, Start Method Profiling->操作界面->stop, 操作最好不要超过5s
  2. android.os.Debug.startMethodTracing();和android.os.Debug.stopMethodTracing();然后在sdcard目录下生产trace文件,然后eclipse打开

Systrace的功能包括:
跟踪系统的I/O操作、内核工作队列、CPU负载以及Android各个子系统的运行状况等
Java层的通过android.os.Trace. beginSection(“Fragement_onCreateView”);类完成,native层通过ATrace宏完成ATRACE_CALL

readelf xxx.so 查看so文件头部信息
-h 头部信息 -S section头信息 -a 全部信息
jarsign 使用keystore签名
signapk 签名apk用pk8,x509

知识

  • ART、Dalvik和JVM的关系及区别是什么

ART 就是 Android Runtime ,是安卓4.4之后的系统的新的虚拟机模式,改模式提升了运行效率,启用该模式之后,系统在安装APP的时候,会进行一次预编译,把代码转成机器语言存储在本地,这样运行的时候效率就高了。
Dalvik 是一种安卓系统在上面运行的虚拟机,因为安卓系统是以Linux 为底层构建的,为了更加高效的适配到各种不同的硬件设备上面,就创建了这个Dalvik虚拟机,该虚拟机可以将程序的语言由java转成机器语言二进制运行,然而每次开启运用的时候都会执行一次编译,所以效率不是很高,所以我们需要ART,增加效率。Dalvik虚拟机使用的指令是基于寄存器的,由于需要指定源地址和目标地址,某些指令需要占用两个字节,需要更多指令空间意味着数据缓冲(d-cache)更易失效
JVM 是 java虚拟机,是实现java夸平台的主要方式,可以使得java这样的高级语言编译成机器可以识别的机器语言,这样使得java一次编译,到处运行。Java虚拟机使用的指令集是基于堆栈的,指令只占一个字节,因而称为字节码,需要更多指令意味着要多占用CPU时间
Dalvik虚拟机和ART虚拟机不同的地方就在于,Dalvik虚拟机执行的是dex字节码,ART虚拟机执行的是本地机器码。这意味着Dalvik虚拟机包含有一个解释器,用来执行dex字节码。当然,Android从2.2开始,也包含有JIT(Just-In-Time),用来在运行时动态地将执行频率很高的dex字节码翻译成本地机器码,然后再执行。但是,将dex字节码翻译成本地机器码是发生在应用程序的运行过程中的,并且应用程序每一次重新运行的时候,都要做重做这个翻译工作的。因此,即使用采用了JIT,Dalvik虚拟机的总体性能还是不能与直接执行本地机器码的ART虚拟机相比。Android的运行时从Dalvik虚拟机替换成ART虚拟机.
Android系统在启动的时候,会创建一个Zygote进程,充当应用程序进程孵化器。Zygote进程在启动的过程中,又会创建一个Dalvik虚拟机。Zygote进程是通过复制自己来创建新的应用程序进程的。这意味着Zygote进程会将自己的Dalvik虚拟机复制给应用程序进程。通过这种方式就可以大大地提高应用程序的启动速度,因为这种方式避免了每一个应用程序进程在启动的时候都要去创建一个Dalvik。事实上,Zygote进程通过自我复制的方式来创建应用程序进程,省去的不仅仅是应用程序进程创建Dalvik虚拟机的时间,还能省去应用程序进程加载各种系统库和系统资源的时间
每一个Android应用程序进程都是由Zygote进程fork出来的。Zygote进程是由init进程启动起来的。Zygote进程在启动的时候,会创建一个虚拟机实例,并且在这个虚拟机实例将所有的Java核心库都加载起来。每当Zygote进程需要创建一个Android应用程序进程的时候,它就通过复制自身来实现,也就是通过fork系统调用来实现。这些被fork出来的Android应用程序进程,一方面是复制了Zygote进程中的虚拟机实例,另一方面是与Zygote进程共享了同一套Java核心库节省了内存空间

  • APP启动流程

(1)启动的起点发生在Launcher活动中,启动一个app说简单点就是启动一个Activity,那么我们说过所有组件的启动,切换,调度都由AMS来负责的,所以第一步就是Launcher响应了用户的点击事件,然后通知AMS
(2)AMS得到Launcher的通知,就需要响应这个通知,主要就是新建一个Task去准备启动Activity,并且告诉Launcher你可以休息了(Paused);
(3)Launcher得到AMS让自己“休息”的消息,那么就直接挂起,并告诉AMS我已经Paused了;
(4)AMS知道了Launcher已经挂起之后,就可以放心的为新的Activity准备启动工作了,首先,APP肯定需要一个新的进程去进行运行,所以需要创建一个新进程,这个过程是需要Zygote参与的,AMS通过Socket去和Zygote协商,如果需要创建进程,那么就会fork自身,创建一个线程,新的进程会导入ActivityThread类,这就是每一个应用程序都有一个ActivityThread与之对应的原因;
(5)进程创建好了,通过调用上述的ActivityThread的main方法,这是应用程序的入口,在这里开启消息循环队列,这也是主线程默认绑定Looper的原因;
(6)四大组建的启动都需要AMS去启动,将上述的应用进程信息注册到AMS中,AMS再在堆栈顶部取得要启动的Activity,通过一系列链式调用去完成App启动;

  • 系统的启动流程
  1. 按下Power键,引导芯片代码从预定义的地方(固化在ROM(read only memory)的预定义位置) 开始加载引导程序 BootLoader到RAM(random access memory),然后执行引导程序(bootloader)
  2. Linux内核启动. kernel(内核)启动时会设置缓存、被保护存储器、计划列表、加载驱动.然后在系统文件中寻找init文件,并启动init进程
  3. init进程启动,init进程是Linux系统中 用户空间的第一个进程,进程号为1,是所有进程的父进程.system/core/init/inti.cpp的main函数首先初始化属性,创建文件和挂载文件,分析和运行所有的init.rc文件,创建并启动zygote孵化进程.
  4. Zygote孵化进程是由init进程通过解析init.zygote.rc文件而创建的,Zygote进程的建立是真正的Android运行空间,DVM,systemservice,app进程都是由它创建出来.
  5. Zygote孵化进程首先会创建虚拟机(代码位置:App_main.cpp),之后通过JNI加载 Android Framework 中的 class、res到内存,然后启动SystemServer.接着创建服务端socket,轮询监听socket,等待AMS的请求来创建APP进程.
  6. Zygote启动过程中会调用startSystemServer(),启动SystemServer进程.
  7. SystemService会启动Binder线程池并且启动各种java层系统服务,并在ServiceManager中注册,随后依次调用系统服务的systemReady()方法,接着在AMS中调用finishBooting()完成启动.SystemUI在各种系统服务启动完成后启动.
  8. 被SystemServer进程启动的ActivityManagerService会启动Launcher,Launcher启动后会将已安装应用的快捷图标显示到界面上
  • Binder机制

Binder机制 是一种进程间通信机制,提供了远程过程调用功能,就是RPC。他把系统的一系列组件连接在了一起,这些组件分别是上层的 Client 和server,中间层的 Service Manager,该组件 ;底层的 Binder驱动程序,系统的核心组件。开发者只要实现要client和server就可以了。
ServiceManager一个守护进程,它里面维护了一张表,表里面存储的是向他注册过的进程信息,用来管理Server,并向Client提供查询Server接口的能力
Binder驱动:驱动负责进程之间Binder通信的建立,Binder在进程之间的传递,Binder引用计数管理,数据包在进程之间的传递和交互等一系列底层支持。
相比传统的Socket更高效。只需要数据拷贝1次,而传统的socket之类的需要2次 安全性高,支持通信双方进行身份验证

  • Binder工作流程

1客户端首先获取服务器端的代理对象。所谓的代理对象实际上就是在客户端建立一个服务端的“引用”,该代理对象具有服务端的功能,使其在客户端访问服务端的方法就像访问本地方法一样。
2客户端通过调用服务器代理对象的方式向服务器端发送请求。
3代理对象将用户请求通过Binder驱动发送到服务器进程。
4服务器进程处理用户请求,并通过Binder驱动返回处理结果给客户端的服务器代理对象。
5客户端收到服务端的返回结果。
客户端的远程调用会挂起当前线程,因此UI线程发起RPC(远程调用)会导致阻塞。Binder机制中的线程池,是指服务端的onTransact()运行在Binder线程池中。服务端的方法都运行在线程池中,因此要注意同步问题

  • Application,Task和Process的区别与联系

Application: 是由四大组件组成的。在app安装时,系统会读取manifest的信息,将所有的组件解析出来
task:是在程序运行时,只针对activity的概念, 它是存在于framework层的一个概念,控制界面的跳转和返回。framework是以栈的形式管理用户开启的activity。任务(Task)不仅可以跨应用(Application),还可以跨进程(Process)。
Proces:可以认为一个运行中的dalvik虚拟机实例占有一个进程,所以,在默认情况下,一个应用程序的所有组件运行在同一个进程中。但是这种情况也有例外,即,应用程序中的不同组件可以运行在不同的进程中。只需要在manifest中用process属性指定组件所运行的进程的名字

  • Activity四种启动模式

Standard: 标准启动模式。同一个任务中可以存在多个activity的实例。A->B->A->A
singleTop: 如果activity已经存在于任务桟的桟顶,则不会创建实例;如果不在栈顶,则和Standard相同,会创建多个实例。
singleTask:B启动模式为singleTask,则有以下情况:
1.A和B有不同taskAffinity,如果任务中有B实例,则将任务调到前台,并清除B所在的任务中B上面的所有activity;如果不存在B实例,则会在当前任务中创建B实例
2.B只设置singleTask,A启动B时会设定启动标志FLAG_ACTIVITY_NEW_TASK,这时系统会检查A所在的任务堆栈中是否存在B,如果不存在则在当前任务堆栈中创建B,如果存在则将任务调到前台,并清除B所在的任务中B上面的所有activity。最后当前堆栈只有A,B。
A->B->C->D,C启动B,则最后只有A和B
singleInstance:此模式启动的activity整个系统中只会存在一个这样的实例,且它会独自占用一个任务,不允许其他Activity和它共存在一个任务中。

taskAffinity:具有相同的affinity的activity(即设置了相同taskAffinity属性的activity)属于同一个任务.
为一个activity的taskAffinity设置一个空字符串,表明这个activity不属于任何task。

  • 8.如何对 Android 应用进行性能分析

android 性能主要之响应速度 和UI刷新速度。
可以参考博客:Android系统性能调优工具介绍
首先从函数的耗时来说,有一个工具TraceView 这是androidsdk自带的工作,用于测量函数耗时的。
UI布局的分析,可以有2块,一块就是Hierarchy Viewer 可以看到View的布局层次,以及每个View刷新加载的时间。
这样可以很快定位到那块layout & View 耗时最长。 还有就是通过自定义View来减少view的层次。

  • 什么情况下会导致内存泄露

内存泄露的根本原因:长生命周期的对象持有短生命周期的对象。短周期对象就无法及时释放。
I. 静态集合类引起内存泄露
主要是hashmap,Vector等,如果是静态集合 这些集合没有及时setnull的话,就会一直持有这些对象。
II.remove 方法无法删除set集 Objects.hash(firstName, lastName);
经过测试,hashcode修改后,就没有办法remove了。
III. observer 我们在使用监听器的时候,往往是addxxxlistener,但是当我们不需要的时候,忘记removexxxlistener,就容易内存leak。
IV.广播没有unregisterrecevier
Ⅴ.各种数据链接没有关闭,数据库contentprovider,io,sokect等。cursor
Ⅵ.内部类:
java中的内部类(匿名内部类),会持有宿主类的强引用this。 所以如果是new Thread这种,后台线程的操作,当线程没有执行结束时,activity不会被回收。
Context的引用,当TextView 等等都会持有上下文的引用。
如果有static drawable,就会导致该内存无法释放。
VI.单例 单例
是一个全局的静态对象,当持有某个复制的类A是,A无法被释放,内存leak。

  • 如何避免 OOM 异常

当程序需要申请一段“大”内存,但是虚拟机没有办法及时的给到,即使做了GC操作以后就会抛出 OutOfMemoryException
也就是OOM,为了减少单个APP对整个系统的影响,android为每个app设置了一个内存上限,减少内存对象的占用:
I.ArrayMap/SparseArray代替hashmap II.避免在android里面使用Enum
III.减少bitmap的内存占用
• inSampleSize:缩放比例,在把图片载入内存之前,我们需要先计算出一个合适的缩放比例,避免不必要的大图载入。
• decode format:解码格式,选择ARGB_8888/RBG_565/ARGB_4444/ALPHA_8,存在很大差异。
IV.减少资源图片的大小,过大的图片可以考虑分段加载

内存对象的重复利用: 大多数对象的复用,都是利用对象池的技术。
I.listview/gridview/recycleview contentview的复用
II.inBitmap 属性对于内存对象的复用ARGB_8888/RBG_565/ARGB_4444/ALPHA_8
这个方法在某些条件下非常有用,比如要加载上千张图片的时候。
III.避免在ondraw方法里面 new对象

原理判断是否存在内存泄漏
首先尝试着从ReferenceQueue队列中获取待分析对象(软引用和弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用或弱引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用或弱引用加入到与之关联的引用队列中),如果不为空,那么说明正在被系统回收,如果直接就返回DONE,说明已经被系统回收了,如果没有被系统回收,可能存在内存泄漏,手动触发系统GC,然后再尝试移除待分析对象,如果还存在,说明存在内存泄漏。
<1>无法检测出Service中的内存泄漏问题
<2>如果最底层的MainActivity一直未走onDestroy生命周期(它在Activity栈的最底层),无法检测出它的调用栈的内存泄漏

  • ANR 是什么?怎样避免和解决 ANR(重要)

ANR->Application Not Responding也就是在规定的时间内,没有响应。
从发生的场景分类:
1). KeyDispatchTimeout(5 s) --主要类型按键或触摸事件在特定时间内无响应
2). BroadcastTimeout(前台10S,后台60s) --BroadcastReceiver在特定时间内无法处理完成
3). ServiceTimeout(前台20s,后台200s) --小概率类型 Service在特定的时间内无法处理完成
4) .ContentProvider执行超时,比较少见

从发生的原因分
主线程有耗时操作,如有复杂的layout布局,IO操作等。
被Binder对端block
被子线程同步锁block
Binder被占满导致主线程无法和SystemServer通信
得不到系统资源(CPU/RAM/IO)

为什么会超时:事件没有机会处理 & 事件处理超时
怎么避免ANR:
避免在UI线程,BroadcastReceiver 还有service主线程中,处理复杂的逻辑和计算 而交给work thread操作。
1)避免在activity里面做耗时操作,oncreate & onresume
2)避免在onReceiver里面做过多操作
3)避免在Intent Receiver里启动一个Activity,因为它会创建一个新的画面,并从当前用户正在运行的程序上抢夺焦点。
4)尽量使用handler来处理UI thread & workthread的交互

  • Android 线程间通信有哪几种方式

1)共享变量(内存)
2)管道
3)handle机制 runOnUiThread(Runnable) view.post(Runnable)

  • 多进程之间通信

进程间的通信:
bind机制(IPC->AIDL)
linux级共享内存
boradcast

Framework 工作方式及原理,Activity 是如何生成一个 view 的,机制是什么

Framework是android 系统对 linux kernel,lib库等封装,提供WMS,AMS,bind机制,handler-message机制等方式,供app使用。简单来说framework就是提供app生存的环境。
1)Activity在attch方法的时候,会创建一个phonewindow(window的子类)
2)onCreate中的setContentView方法,会创建DecorView
3)DecorView 的addview方法,会把layout中的布局加载进来。

  • Android 中的动画有哪几类,它们的特点和区别是什么

视图动画,或者说补间动画。只是视觉上的一个效果,实际view属性没有变化,性能好,但是支持方式少。xml文件的根标签是 子标签是 对应的java对象是Animation,一般在自定义View动画时,或者是进出场动画效果等
属性动画,通过变化属性来达到动画的效果,性能略差,支持点击等事件。xml文件的根标签可以是 三个标签之一,动画集合
帧动画,通过drawable一帧帧画出来。xml文件的根标签是 子标签是 对应的java对象AnimationDrawable,比如下拉刷新的刷新头动图效果
Gif动画,原理同上,canvas画出来。

  • 如何修改 Activity 进入和退出动画

overridePendingTransition

  • SurfaceView & View 的区别

view的更新必须在UI thread中进行
surfaceview会单独有一个线程做ui的更新。 surfaceview 支持open GL绘制。

  • android事件分发

view的更新必须在UI thread中进行
View的事件分发机制:dispatchTouchEvent->onTouchEvent->performClick->onClick
ViewGroup的事件分发机制:dispatchTouchEvent->onInterceptTouchEvent return true/false->onTouchEvent
onInterceptTouchEvent返回false,就说明不拦截,返回true说明拦截该事件

  • Service的重启机制

onStartCommand(Intent,int,int)方法,这个方法return一个int值,return 的值有四种:
START_STICKY:如果service进程被kill掉,保留service的状态为开始状态,但不保留递送的intent对象。随后系统会尝试重新创建service,由于服务状态为开始状态,所以创建服务后一定会调用onStartCommand(Intent,int,int)方法。如果在此期间没有任何启动命令被传递到service,那么参数Intent将为null。
START_NOT_STICKY:“非粘性的”。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统不会自动重启该服务。
START_REDELIVER_INTENT:重传Intent。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统会自动重启该服务,并将Intent的值传入。
START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,但不保证服务被kill后一定能重启

  • windows

Window:
1表示一个窗口的概念,是所有View的直接管理者,任何视图都通过Window呈现(点击事件由Window->DecorView->View; Activity的setContentView底层通过Window完成)
2 Window是一个抽象类,具体实现是PhoneWindow
3创建Window需要通过WindowManager创建
4 WindowManager是外界访问Window的入口: 添加、更新、删除View。addView中会创建ViewRootImpl,内部会通过WMS去获取WindowSession,每个进程一般都有一个Session对象
5 Window具体实现位于WindowManagerService中
6 WindowManager和WindowManagerService的交互是通过IPC完成

WindowManager:

  1. WindowManager是一个接口,真正实现类是WindowManagerImpl,并最终以代理模式交给WindowManagerGlobal实现。
    2 addView: 1-创建ViewRootImpl;2-将ViewRoor、DecorView、布局参数保存到WM的内部列表中;
    3-ViewRoot.setView()建立ViewRoot和DecorView的联系。
    4 setView:1-进行View绘制三大流程;2-会通过WindowSession完成Window的添加过程(一次IPC调用)
    5 requestLayout:内部调用scheduleTraversals(), 底层通过mChoreographer去监听下一帧的刷新信号。 mWindowSession.addToDisplay: 执行WindowManangerService的addWindow
    6 addWindow:检查参数等设置;检查Token;将Token、Window保存到WMS中;将WindowState保存到Session中。
  • dispatchTouchEvent,onInterceptTouchEvent和onTouchEvent的区别

dispatchTouchEvent:决定了事件是否继续分发下去和是否响应事件,false:继续分发,true:不继续分发,此次事件到此结束,也不会有任何控件执行onTouchEvent方法。
onInterceptTouchEvent:决定了是否拦截该事件,false:不拦截,true:拦截,此时当前控件执行onTouchEvent方法。
onTouchEvent:决定了是否消费该事件,false:不消费,true:消费。

和事件分发相关的方法共有三个:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent
dispatchTouchEvent和onTouchEvent在Activity、ViewGroup和View中均存在
只有ViewGroup中有onInterceptTouchEvent方法

OnTouchListener.onTouch->onTouchEvent->OnClickListener.onClick
Activity::dispatchTouchEvent->ViewGroup::dispatchTouchEvent->ViewGroup::onInterceptTouchEvent->View::onTouchEvent->ViewGroup::onTouchEvent

  • Activity的启动过程

最终会由ActivityThread中的performLauchActivity来完成整个启动过程,performLauchActivity内部会通过类加载器创建Activity的实例对象,并为Activity的实例对象调用attach方法,为其关联运行过程中所以来的上下文环境变量, attch方法中,系统会创建Activity所属的Window对象,并为其设置回调接口, Window对象的创建是通过PolicyManager的makeNewWindow方法实现。Activity实现了window的callback接口,因此外界状态改变时会回调Activity的方法(onAttachedToWindow、dispatchTouchEvent等等)

  • APK打包流程

1 APPT工具对资源文件进行打包(AndroidManifest.xml、布局等)生成R.java文件;通过AIDL工具处理AIDL文件,生成对应的Java文件。
2 Javac工具对R.java、项目源码、aidl对应的Java文件这三部分进行编译,生成class文件
3 Dex工具将所有class文件转换为DEX文件:该过程进行将Java字节码转换为Dalvik字节码、压缩常量池、清除冗余信息等工作。
4 ApkBuilder工具将资源文件、Dex文件打包成APK文件
5 KeyStore对APK文件进行签名。
6 正式版APK,需要用ZipAlign工具进行对齐处理:过程中是将APK中所有资源文件的起始地址都便宜4字节的整数倍,通过内存映射访问APK文件的速度会更快

  • APK的安装流程

1 复制APK到/data/app目录下,解压并扫描安装包。
2 资源管理器解析APK里的资源文件。
3 解析AndroidManifest文件,并在/data/data/目录下创建对应的应用数据目录
4 对dex文件进行优化,并且保存在dalvik-cache目录下。
5 将AndroidManifest文件解析出的四大组件信息注册到PackageManagerService中。
6 安装完成后,发送广播

  • Intent传递数据的大小限制

大小限制大约是1MB:超过该限制可能导致 OOM。
解决办法 进程内:EventBus、文件缓存
进程间:通过ContentProvider进行进程间数据共享和传递。
总结:
不要通过 Intent 在 Android 基础组件之间传递大数据(binder transaction 缓存为1MB)

  • 界面的刷新

系统每16.6ms会发出一个VSYNC信号,发出信号后,才会开始进行测量、布局和绘制。
发出VSYNC信号时,还会将此时显示器的buffer缓冲区的数据取出,并显示在屏幕上。

  • WindowManagerService

WMS(Window Manager Service)在整个显示流程中起到关键的作用。它是 Android 系统中的一个系统服务,负责管理和控制应用程序窗口的显示、布局和交互。
以下是WMS在整个显示流程中的主要作用:

  1. 窗口管理:WMS负责跟踪和管理所有应用程序窗口。它负责生成、添加、删除和管理窗口,并根据窗口的优先级和交互状态进行层次化布局。
  2. 窗口交互:WMS处理用户与窗口之间的交互,例如触摸事件、键盘事件和手势识别。它会将这些事件传递给正确的窗口,以便应用程序可以响应用户的输入。
  3. 窗口布局:WMS根据窗口的属性和优先级来处理窗口的布局。它确保窗口按照正确的顺序层叠、叠加和放置,以正确的覆盖和显示。
  4. 窗口切换和动画:当用户切换应用程序或执行其他与窗口交互的操作时,WMS负责处理窗口的切换效果和动画效果,以提供流畅的视觉过渡效果。
  5. 多窗口支持:WMS还负责多窗口模式的管理,即同时显示多个应用程序窗口。它可以控制窗口的大小、位置和交互方式,以便用户可以在一个屏幕上同时运行和操作多个应用程序。
    总体而言,WMS是Android系统中的一个核心组件,它在整个显示流
    ViewRoot记录最终请求的结果
  • ActivityManagerService

AMS是Android中最核心的服务,主要负责系统中四大组件的启动、切换、调度及应用进程的管理和调度等工作,其职责与操作系统中的进程管理和调度模块相类似
main中:
创建一个AThread线程对象: new一个AMS
创建ActivtyStack:用来管理Activity的启动和调度
通过systemReady启动systemui及其他服务

  • handler和looper

Java层的handler在默认构造函数中通过Looper.myLooper()获取looper对象
A. 在Java层Looper.java,创建了一个Looper对象,这个Looper对象是用来进入消息循环的,它的内部有一个消息队列MessageQueue对象mQueue;
B. 在JNI层android_os_MessageQueue,创建了一个NativeMessageQueue对象,这个NativeMessageQueue对象保存在Java层的消息队列对象mQueue的成员变量mPtr中,通过reinterpret_cast类型转换,当通过java层调用jni方法时,传递mPtr到jni中,找到对应的C++层消息队列;
C. 在C++层,创建了一个Looper对象,保存在JNI层的NativeMessageQueue对象的成员变量mLooper中,在looper中,
创建一个Linux匿名管道(pipe),采用epoll_ctl模式等待可读管道,Looper的作用是,当Java层的消息队列中没有消息时,就使Android应用程序主线程进入等待状态,而当Java层的消息队列中来了新的消息后,就唤醒Android应用程序的主线程来处理这个消息
读取消息:MessageQueue的Next方法会等待消息队列中有可执行的消息,否则就进入空闲等待,通过调用nativePollOnce查询队列中是否有消息,如果有则取出,nativePollOnce通过C++层NatvieMessageQueue:: pollOnce->Looper::pollInner->epoll_wait看看epoll专用文件描述符mEpollFd所监控的文件描述符是否有IO事件发生,如果有则调用Looper::awoken读取管道数据
消息的发送:handler的sendMessageXX中,把当前handler的this赋值给msg.target,并调用MessageQueue的enqueueMessage把当前消息根据执行的时间插入到队列中,如果消息队列为空,则调用nativeWake->Looper::wake通过打开文件描述符mWakeWritePipeFd往管道的写入一个"W"字符串来唤醒应用程序的主线程
消息的处理: 通过java层Looper一直while循环,MessageQueue的Next取出队列中message,并通过msg.target.dispatchMessage(msg)分到到对应的handler的dispatchMessage,然后调用handleMessage

  • 开机画面显示过程

调用函数proc_create在/proc目录下创建了一个fb文件,接着又调用函数register_chrdev来注册了一个名称为fb的字符设备,最后调用函数class_create在/sys/class目录下创建了一个graphics目录,用来描述内核的图形系统
系统帧缓冲区: 用户空间的通过操作/dev/graphics/fb*

第一阶段
是在kernel中drivers/video,直接操作图形驱动设备显示
第二阶段:
system/core/init/init.c启动的时候,会解析init.rc,间接通过init启动ueventd,进程会通过一个socket接口来和内核通信,以便可以监控系统设备事件; system/core/init/logo.c开机画面文件由/initlogo.rle,指定如果文件/initlogo.rle不存在则向编号为0的控制台(/dev/tty0)输出“ANDROID”这7个字符,通过打开/initlogo.rle文件并mmap映射到init进程的地址空间,并通过操作设备文件/dev/graphics/fb0来输出到帧缓冲区硬件设备中(也是通过fb0映射到init进程).每次关机启动之后,系统都会重新将ramdisk映像中的initlogo.rle文件安装到目标设备上的根目录来,启动完成之后,删除/initlogo.rle
第三阶段:
/system/bin/bootanimation当SurfaceFlinger服务启动的时候,它会通过修改系统属性ctl.start的值来通知init进程启动应用程序bootanimation,而当System进程将系统中的关键服务都启动起来之后,ActivityManagerService服务就会通知SurfaceFlinger服务来修改系统属性ctl.stop的值,以便可以通知init进程停止执行应用程序bootanimation,即停止显示第三个开机画面;bootanimation通过SurfaceFlinger的代理服务,通过opengl绘制。
系统默认的由bootanimation::android实现: frameworks/base/core/res/assets/images的android-logo-mask.png和android-logo-shine.png最终会被编译在framework-res模块, 图片android-logo-mask.png用作动画前景,它是一个镂空的“ANDROID”图像。图片android-logo-shine.png用作动画背景 用户自定义的由bootanimation::movie实现:
用户自定义动画文件位于/data/local/bootanimation.zip或者/system/media/bootanimation.zip;
压缩文件都必须包含有一个名称为“desc.txt”的文件,用来描述用户自定义的开机动画是如何显示的,第一行显示宽度、高度以及帧速,后面的是显示动画片段,包含次数和时间间隔

  • SurfaceFlinger

Surface理解为一个绘图表面,Android应用程序负责往这个绘图表面填内容,而SurfaceFlinger服务负责将这个绘图表面的内容取出来,并且渲染在显示屏上, Surface类看作OpenGL库与Android的UI系统之间的一个桥梁
在每一个Android应用程序与SurfaceFlinger服务之间的连接上加上一块用来传递UI元数据的匿名共享内存,通过代理端SharedClient和SurfaceFlinger通信, 在每一个SharedClient里面,有至多31个SharedBufferStack, 每一个SharedBufferStack都对应一个Surface,即一个窗口。一个Android应用程序至多可以包含31个Surface
SurfaceFlinger 工作内容如下:

  1. 响应客户端事件,创建 Layer 与客户端的 Surface 建立连接
  2. 接收客户端数据及属性,修改 Layer 属性,如尺寸、颜色、透明度等
  3. 将创建的 Layer 内容刷新到屏幕上
  4. 维持 Layer 的序列,并对 Layer 最终输出做出裁剪计算
  • Selinux

一个可执行文件一旦被设置了SUID位,那么当它被一个进程通过exec加载之后,该进程的UID就会变成该可执行文件的所有者的UID。也就是说,当上述的su被执行的时候,它所运行在的进程的UID是root,于是它就具有最高级别的权限,想干什么就干什么
Signature作用: 1. 控制哪些APK可以共享同一个UID;2. 控制哪些APK可以申请哪些Permission
UID是1000的用户是system,
NDK在非root手机上调试APP的原理了:gdbserver通过run-as获得与目标进程相同的UID,然后就可以ptrace到目标进程去调试了
MAC机制: 强制访问控制。用户、进程或者文件的权限是由管理策略决定的,而不是由它们自主决定的, 每一个进程和文件都会关联有一个安全上下文,通过ls –Z命令查看,格式为“user:role:type:sensitivity”

  • ThreadLocal

ThreadLocal,一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其它线程来说无法获取到数据,简单来说它为变量在每个线程中都创建一个副本,但它本身能够被多个线程共享使用,并且又能够达到线程安全的目的,且绝对线程安全,比如Looper、ActivityThread以及AMS中都用到了ThreadLocal
1.通过ThreadLocal创建的副本,存储在每个线程自己的threadLocals中。
2. threadLocals实际就是ThreadLocalMap,ThreadLocalMap把ThreadLocal做为key。
3.在进行get之前,必须先set,否则会报空指针异常。
4.如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法

  • EventBus解决了哪些问题

对无业务直接关联的服务强耦合
可能存在性能问题,比如你需要关心邮件发送是否会影响主流程
代码可读性变差,过多的逻辑容易导致分不清主体核心功能

  • SharedPreference

由于在一个进程中,sharedPreference是单实例,一般不会出现并发冲突,如果对提交的结果不关心的话,建议使用apply,当然需要确保提交成功且有后续操作的话,还是需要用commit的。

commit和apply的区别

  1. apply没有返回值而commit返回boolean表明修改是否提交成功
  2. apply是将修改数据原子提交到内存, 而后异步真正提交到硬件磁盘, 而commit是同步的提交到硬件磁盘,因此,在多个并发的提交commit的时候,他们会等待正在处理的commit保存到磁盘后在操作,从而降低了效率。而apply只是原子的提交到内容,后面有调用apply的函数的将会直接覆盖前面的内存数据,这样从一定程度上提高了很多效率。
  3. apply方法不会提示任何失败的提示。
  • Svn与Git的区别

Git是分布式的,而Svn不是分布的
Git把内容按元数据方式存储,而SVN是按文件
Git没有一个全局版本号,而SVN有
Git的内容的完整性要优于SVN: GIT的内容存储使用的是SHA-1哈希算法。这能确保代码内容的完整性
Git下载下来后,在OffLine状态下可以看到所有的Log,SVN不可以
Git的特点版本控制可以不依赖网络做任何事情,对分支和合并有更好的支持

  • Android进程的保活方案

前台进程:用户当前操作所必须的进程
可见进程:没有任何前台组件,但仍会影响用户在屏幕上所见内容的进程
服务进程:例如在后台播放音乐,从网络下载数据。除非内存不足以维持所有的前台进程和可见进程同时运行,否则系统会让服务进程保持运行的状态
后台进程:后台进程对用户体验没有直接的影响,系统可能随时终止它们,以回收内存供前台进程、可见进程、服务进程的使用,通常我们会有很多后台进程在运行,因此它们会保存在LRU列表中,以确保包含用户最近查看的Activiy的进程最后一个被终止
空进程:保留这种进程的唯一目的时用作缓存,以缩短下次其中运行组件所需的启动时间

进程死后的拉活方案
1.利用系统广播或者第三方广播拉活
2. 利用系统的sevice机制拉活:将service设置为START_STICKY,利用系统机制在Service死后自动进行拉活。缺点:Service在第一次被异常杀死后5秒之后自启,第二次被杀死10秒之后自启,第五次之后就不在被重启(进程被Root权限管理工具通过foreStop停止的无法被重启
3. 开启一个像素的Activity:手Q的进程保活方案,基本思想,系统一般是不会杀死前台进程的。所以要使得进程常驻,我们只需要在锁屏的时候在本进程开启一个Activity,为了欺骗用户,让这个Activity的大小是1像素,并且透明无切换动画,在开屏幕的时候,把这个Activity关闭掉,所以这个就需要监听系统锁屏广播
4. 前台服务:对于 API level < 18 :调用startForeground(ID, new Notification()),发送空的Notification ,图标则不会显示。
对于 API level >= 18:在需要提优先级的service A启动一个InnerService,两个服务同时startForeground(NOTIFICATION_ID, builder.build());,且绑定同样的 ID。Stop 掉InnerService (NotificationManager.cancel(NOTIFICATION_ID),stopSelf),这样通知栏图标即被移除。

黑色保活:不同的app进程,用广播相互唤醒(包括利用系统广播进行唤醒),开机、网络切换、拍照、拍视频、利用系统产生的广播唤醒app
白色保活:就是调用系统api启动一个前台的Service进程,这样会在系统的通知栏生成一个Notification,用来让用户知道有这样一个app在运行,例如: 后台播放音乐、LBE、360安全卫士
灰色保活:启动一个前台的Service进程,这样的好处就是用户无法察觉到你运行着一个前台进程(因为看不到Notification)

  • Android系统和程序的启动过程

Android系统启动加载内核后----> 执行init进程 ----> 启动Zygote进程 ----> 初始化Dalvik虚拟机 —> 启动system_server进入Zygote模式,用socket等待命令 —> Zygote收到命令后fork一个Dalvik虚拟机实例来执行程序入口函数

  • overdraw 解决方法

移除不必要的background,这是一种快速提升渲染性能的方式
减少布局层级
减少使用透明视图

  • 布局优化常用手段
  1. 调试GPU过度绘制,将Overdraw降低到合理范围内;
  2. 减少嵌套层次及控件个数,保持view的树形结构尽量扁平(使用Hierarchy Viewer可以方便的查看),同时移除所有不需要渲染的view;
  3. 使用GPU配置渲染工具,定位出问题发生在具体哪个步骤,使用TraceView精准定位代码;
  4. 使用标签,Merge减少嵌套层次、ViewStub延迟初始化
    一个容易忽略的点是我们的Activity使用的Theme可能会默认的加上背景色,不需要的情况下可以去掉。
  • TextureView 与 SurfaceView用法区别

SurfaceView和TextureView均继承于android.view.View,与其它View不同的是,两者都能在独立的线程中绘制和渲染,在专用的GPU线程中大大提高渲染的性能。
一、SurfaceView专门提供了嵌入视图层级的绘制界面,开发者可以控制该界面像Size等的形式,能保证界面在屏幕上的正确位置。但也有局限:

由于是独立的一层View,更像是独立的一个Window,不能加上动画、平移、缩放;
两个SurfaceView不能相互覆盖。
二、Texture更像是一般的View,像TextView那样能被缩放、平移,也能加上动画。TextureView只能在开启了硬件加速的Window中使用,并且消费的内存要比SurfaceView多,并伴随着1-3帧的延迟。

三、TextureView和SurfaceView都是继承自View类的,但是TextureView在Andriod4.0之后的API中才能使用。SurfaceView可以通过SurfaceHolder.addCallback方法在子线程中更新UI,TextureView则可以通过TextureView.setSurfaceTextureListener在子线程中更新UI,个人认为能够在子线程中更新UI是上述两种View相比于View的最大优势。
但是,两者更新画面的方式也有些不同,由于SurfaceView的双缓冲功能,可以是画面更加流畅的运行,但是由于其holder的存在导致画面更新会存在间隔(不太好表达,直接上图)。并且,由于holder的存在,SurfaceView也不能进行像View一样的setAlpha和setRotation方法,但是对于一些类似于坦克大战等需要不断告诉更新画布的游戏来说,SurfaceView绝对是极好的选择。但是比如视频播放器或相机应用的开发,TextureView则更加适合。

HTTP

  • http链接流程,三次握手
  1. 简历TCP/IP连接,客户端与服务器通过socket三次握手进行连接
  2. 客户端向服务器发送http请求,如GET/1.html HTTP/1.1
  3. 客户端发送请求头信息,请求内容,最后发送空白行,标识请求完毕
  4. 服务器会做出应答,表示对客户端请求的应答,如HTTP/1.1 200 OK
  5. 服务器向客户端发送应答头信息
  6. 服务器向客户端发送应答头信息后,也发送空白行,标识应答头信息发送完毕,接着就以Content-type要求的数据格式发送数据给客户端
  7. 服务器关闭tcp连接,如果服务器或客户端增加Connection:keep-alive就表示客户端与服务器继续保持连接,在下次请求时可继续使用此次连接
  • http 各个版本之间的区别

1.0 与 1.1
http1.0一次只能处理一个请求,不能同时收发数据
http1.1可以处理多个请求,能同时收发数据
http1.1增加可更多字段,如cache-control,keep-alive.
2.0
http 2.0采用二进制的格式传送数据,不再使用文本格式传送数据
http2.0对消息头采用hpack压缩算法,http1.x的版本消息头带有大量的冗余消息
http2.0 采用多路复用,即用一个tcp连接处理所有的请求,真正意义上做到了并发请求,流还支持优先级和流量控制(HTTP/1.x 虽然通过 pipeline也能并发请求,但是多个请求之间的响应会被阻塞的,所以 pipeline 至今也没有被普及应用,而 HTTP/2 做到了真正的并发请求。同时,流还支持优先级和流量控制。)
http2.0支持server push,服务端可以主动把css,jsp文件主动推送到客户端,不需要客户端解析HTML,再发送请求,当客户端需要的时候,它已经在客户端了。

  • POST和GET的区别
Post一般用于更新或者添加资源信息Get一般用于查询操作,而且应该是安全和幂等的
Post更加安全Get会把请求的信息放到URL的后面
Post传输量一般无大小限制Get不能大于2KB
Post执行效率低Get执行效率略高
  • https
  1. 客户端将自己的has算法和加密算法发给服务器
  2. 服务器接收到客户端发来的加密算法和has算法,取出自己的加密算法与has算法,并将自己的身份信息以证书的形式发送给客户端,该证书信息包括公钥,网站地址,预计颁发机构等
  3. 客户端收到服务器发来的证书(即公钥),开始验证证书的合法性,如果证书信任,则生成一串随机的字符串数字作为私钥,并将私钥(密文)用证书(服务器的公钥)进行加密,发送给服务器
  4. 服务器收到客户端发来的数据之后,通过服务器自己的私钥进行解密客户端发来的数据(客户端的私钥),(这样双方都拥有私钥)再进行hash检验,如果结果一致,则将客户端发来的字符串(第3个步骤发送过来的字符串)通过加密发送给客户端
  5. 客户端解密,如果一致的话,就使用之前客户端随机生成的字符串进行对称加密算法进行加密

Java

  • Java 如何有效地避免OOM:善于利用软引用和弱引用

强引用(StrongReference):
只要某个对象有强引用与之关联,JVM必定不会回收这个对象,即使在内存不足的情况下,JVM宁愿抛出OutOfMemory错误也不会回收这种对象
软引用(SoftReference):
是用来描述一些有用但并不是必需的对象对于软引用关联着的对象,只有在内存不足的时候JVM才会回收该对象。因此,这一点可以很好地用来解决OOM的问题,并且这个特性很适合用来实现缓存:比如网页缓存、图片缓存等。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被JVM回收,这个软引用就会被加入到与之关联的引用队列中
弱引用(WeakReference): 用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象
虚引用(PhantomReference): 在任何时候都可能被垃圾回收器回收

用一个HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效地避免了OOM的问题

  • HashMap和Hashtable的区别
  • hashMap去掉了HashTable 的contains方法,但是加上了containsValue()和containsKey()方法。
  • HashMap是非synchronized,
    Hashtable是synchronized, 线程安全的,多个线程可以共享一个Hashtable, 单线程环境下它比HashMap要慢
  • hashMap允许空键值,而hashTable不允许。
  • JVM内存管理

Dalvik虚拟机的内存大体上可以分为Java Object Heap、Bitmap Memory和Native Heap三种。
Java Object Heap
是用来分配Java对象的,也就是我们在代码new出来的对象都是位于Java Object Heap上的。Dalvik虚拟机在启动的时候,可以通过-Xms和-Xmx选项来指定Java Object Heap的最小值和最大值。为了避免Dalvik虚拟机在运行的过程中对Java Object Heap的大小进行调整而影响性能,我们可以通过-Xms和-Xmx选项来将它的最小值和最大值设置为相等。
这个Java Object Heap的最大值也就是我们平时所说的Android应用程序进程能够使用的最大内存。Android应用程序进程能够使用的最大内存指的是能够用来分配Java Object的堆。通过ActivityManager类的成员函数getMemoryClass来获得Dalvik虚拟机的Java Object Heap的最大值。或者gerprop查看:dalvik.vm.heapsize。
Bitmap Memory
Bitmap占用的内存和Java Object占用的内存加起来不能超过Java Object Heap的最大值。
Native Heap
就是在Native Code中使用malloc等分配出来的内存,这部分内存是不受Java Object Heap的大小限制的,也就是它可以自由使用,当然它是会受到系统的限制。

AndroidManifest.xml的application标签中增加一个值等于“true”的android:largeHeap属性来通知Dalvik虚拟机应用程序需要使用较大的Java Object Heap

  • JVM-双亲委派模型
  1. 虚拟机的类加载机制
    虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验、转换解析和初始化。最终形成可以被虚拟机最直接使用的java类型的过程就是虚拟机的类加载机制。
  2. 类加载器
    BootstrapClassLoader:只能用于加载JDK核心类库,系统变量为sun.boot.class.path下面的类。该目录下的%JAVA_HOME%/jre/lib/下的resources.jar;rt.jar等核心类库,该loader底层采用C++编写,自然你也就不能调用啦。
    ExtClassLoader :用于加载一些扩展类,系统变量为java.ext.dirs中的类。作用:加载开发者自己扩展类
    AppClassLoader:用于加载用户类,这个就是java.class.path下的类,也就是我们自己编写出来的类
  3. 双亲委派模型
    某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
  4. 为什么需要双亲委派机制
    保证唯一性:防止内存中出现多份同样的字节码。试想,如果没有双亲委派模型而是由各个类加载器自行加载的话,如果用户编写了一个java.lang.Object的同名类并放在ClassPath中,多个类加载器都去加载这个类到内存中,系统中将会出现多个不同的Object类,那么类之间的比较结果及类的唯一性将无法保证
    保证安全: 由于所有的用户类都会先通过bootstrapclassloader 查看里面有没有该类资源,有则直接安装或者加载,从而保证了底层的类一定是预先加载的,这样可以对虚拟机的安全得到了很好的保证
  • JAVA静态方法或属性是否可以被继承

java中静态属性和静态方法可以被继承,但是没有被重写(overwrite)而是被隐藏
原因:
1). 静态方法和属性是属于类的,调用的时候直接通过类名.方法名完成,不需要继承机制及可以调用。如果子类里面定义了静态方法和属性,那么这时候父类的静态方法或属性称之为"隐藏"。如果你想要调用父类的静态方法和属性,直接通过父类名.方法或变量名完成,至于是否继承一说,子类是有继承静态方法和属性,但是跟实例方法和属性不太一样,存在"隐藏"的这种情况。
2). 多态之所以能够实现依赖于继承、接口和重写、重载(继承和重写最为关键)。有了继承和重写就可以实现父类的引用指向不同子类的对象。重写的功能是:"重写"后子类的优先级要高于父类的优先级,但是“隐藏”是没有这个优先级之分的。
3). 静态属性、静态方法和非静态的属性都可以被继承和隐藏而不能被重写,因此不能实现多态,不能实现父类的引用可以指向不同子类的对象。非静态方法可以被继承和重写,因此可以实现多态

  • java static代码块执行时机

static块的执行发生在“初始化”的阶段,即第一次被使用的时候。初始化阶段,jvm主要完成对静态变量的初始化,静态块执行等工作

  • 子类、static、变量、代码块、构造方法和父类的构造方法的初始化顺序

1.父类【静态成员】和【静态代码块】,按在代码中出现的顺序依次执行。
2.子类【静态成员】和【静态代码块】,按在代码中出现的顺序依次执行。
3.父类的【普通成员变量被普通成员方法赋值】和【普通代码块】,按在代码中出现的顺序依次执行。
4.执行父类的构造方法。
5.子类的【普通成员变量被普通成员方法赋值】和【普通代码块】,按在代码中出现的顺序依次执行。
6.执行子类的构造方法。

  • 一个类的运行分为以下步骤

装载、连接、初始化
其中装载阶段又三个基本动作组成:
1. 通过类型的完全限定名,产生一个代表该类型的二进制数据流
2. 解析这个二进制数据流为方法区内的内部数据结
3. 构创建一个表示该类型的java.lang.Class类的实例
另外如果一个类装载器在预先装载的时遇到缺失或错误的class文件,它需要等到程序首次主动使用该类时才报告错误。
连接阶段又分为三部分:
1. 验证,确认类型符合Java语言的语义,检查各个类之间的二进制兼容性(比如final的类不用拥有子类等),另外还需要进行符号引用的验证。
2. 准备,Java虚拟机为类变量分配内存,设置默认初始值。
3. 解析(可选的) ,在类型的常量池中寻找类,接口,字段和方法的符号引用,把这些符号引用替换成直接引用的过程。
 当一个类被主动使用时,Java虚拟就会对其初始化,如下六种情况为主动使用:
1. 当创建某个类的新实例时(如通过new或者反射,克隆,反序列化等)
2. 当调用某个类的静态方法时
3. 当使用某个类或接口的静态字段时
4. 当调用Java API中的某些反射方法时,比如类Class中的方法,或者java.lang.reflect中的类的方法时
5. 当初始化某个子类时
6. 当虚拟机启动某个被标明为启动类的类(即包含main方法的那个类)
Java编译器会收集所有的类变量初始化语句和类型的静态初始化器,将这些放到一个特殊的方法中:clinit。

synchronized

Synchronized的作用:
确保线程互斥的访问同步代码
保证共享变量的修改能够及时可见
有效解决重排序问题
Synchronized总共有三种用法:
(1)修饰普通方法:对象同步
(2)修饰静态方法:对类的同步
(3)修饰代码块
Synchronized的语义底层是通过一个monitor(监视器锁)的对象来完成,其实wait/notify等方法也依赖于monitor对象。

synchronized 和 volatile

  • volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
  • volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的。
  • volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性。
  • volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
  • volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。

String、StringBuffer、StringBuilder

  • String 声明的是不可变的对象,每次操作都会生成新的 String 对象,然后将指针指向新的 String 对象,
  • 而 StringBuffer、StringBuilder 可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用 String。
  • StringBuffer 和 StringBuilder 最大的区别在于,StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,但 StringBuilder 的性能却高于 StringBuffer,所以在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer。

BIO、NIO、AIO 有什么区别

  • BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
  • NIO:New IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
  • AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。

List、Set、Map 之间的区别 在这里插入图片描述
HashMap 的实现原理

  • 原理
    基于Map接口的实现,数组+链表的结构,JDK 1.8后加入了红黑树,链表长度>8变红黑树,<6变链表。数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的,如果定位到的数组位置不含链表(当前entry的next指向null),那么对于查找,添加等操作很快,仅需一次寻址即可;如果定位到的数组包含链表,对于添加操作,其时间复杂度为O(n),首先遍历链表,存在即覆盖,否则新增;对于查找操作来讲,仍需遍历链表,然后通过key对象的equals方法逐一比对查找。所以,性能考虑,HashMap中的链表出现越少,性能才会越好。
  • 两个对象的hashcode相同会发生什么? Hash冲突,HashMap通过链表来解决hash冲突
  • HashMap 中 equals() 和 hashCode() 有什么作用?HashMap 的添加、获取时需要通过 key 的 hashCode() 进行 hash(),然后计算下标 ( n-1 & hash),从而获得要找的同的位置。当发生冲突(碰撞)时,利用 key.equals() 方法去链表或树中去查找对应的节点
  • HashMap 何时扩容?put的元素达到容量乘负载因子的时候,默认16*0.75
  • hash 的实现吗?h = key.hashCode()) ^ (h >>> 16), hashCode 进行无符号右移 16 位,然后进行按位异或,得到这个键的哈希值,由于哈希表的容量都是 2 的 N 次方,在当前,元素的 hashCode() 在很多时候下低位是相同的,这将导致冲突(碰撞),因此 1.8 以后做了个移位操作:将元素的 hashCode() 和自己右移 16 位后的结果求异或
  • HashMap线程安全吗?HashMap读写效率较高,但是因为其是非同步的,即读写等操作都是没有锁保护的,所以在多线程场景下是不安全的,容易出现数据不一致的问题,在单线程场景下非常推荐使用。
  • HashMap的put方法执行过程
    ① 判断键值对数组 table[i] 是否为空或为null,否则执行 resize() 进行扩容;
    ② 根据键值 key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加;
    ③ 判断table[i]的首个元素是否和key一样,如果相同直接覆盖value;
    ④ 判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对;
    ⑤ 遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;
    ⑥ 插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容
  • HashMap的get方法执行过程
    1.对key的hashCode()做hash运算,计算index;
    2.如果在bucket⾥的第⼀个节点⾥直接命中,则直接返回;
    3.如果有冲突,则通过key.equals(k)去查找对应的Entry;
    4.若为树,则在树中通过key.equals(k)查找,O(logn);
    5.若为链表,则在链表中通过key.equals(k)查找,O(n)。
  • 为什么槽位数必须使用2^n
    为了让哈希后的结果更均匀的分部,减少哈希碰撞,提升hashmap的运行效率
  • 我⽤LinkedList或者ArrayList代替数组结构可以吗?
    可以,但是⽤数组效率最⾼! 在HashMap中,定位节点的位置是利⽤元素的key的哈希值对数组⻓度取模得到。此时,我们已得到节点的位置。显然数组的查 找效率⽐LinkedList⼤(底层是链表结构)。ArrayList,底层也是数组,查找也快,ArrayList的扩容机制是1.5倍扩容,采⽤基本数组结构,扩容机制可以⾃⼰定义,HashMap中数组扩容刚好是2的次幂,在做取模运算的效率⾼
  • resise方法?
    HashMap 的扩容实现机制是将老table数组中所有的Entry取出来,重新对其Hashcode做Hash散列到新的Table中

ArrayList、Vector和 LinkedList 的区别

  • 最明显的区别是 ArrrayList底层的数据结构是数组,支持随机访问,而 LinkedList 的底层数据结构是双向循环链表,不支持随机访问。使用下标访问一个元素,ArrayList 的时间复杂度是 O(1),而 LinkedList 是 O(n)。
  • Vector是同步的,而ArrayList不是,所以ArrayList速度快。然而,如果你寻求在迭代的时候对列表进行改变,你应该使用CopyOnWriteArrayList。

SparseArray、ArrayMap、HashMap

  • 数据结构
    arraymap是数组结构,里面并没有entry结构,而是由两个数组来维护的,mHashes数组中保存的是每一项的HashCode值,mArray中就是键值对,每两个元素代表一个键值对;
    SparseArray它内部则通过两个数组来进行数据存储的,一个存储key(只能为int值),另外一个存储value,为了优化性能,它内部对数据还采取了压缩的方式来表示稀疏数组的数据;put和get时都是按照二分查找进行的,在数据量小的情况下,查找有着较高效率;
    而hashmap是数组+链表结构;
  • 查找效率:
    HashMap因为其根据hashcode的值直接算出index,所以其查找效率是不随着数组长度增大而增加的。
    ArrayMap使用的是二分法查找,所以当数组长度每增加一倍时,就需要多进行一次判断,效率下降。
    所以对于Map数量比较大的情况下,推荐使用HashMap
  • 内存优化
    SparseArray 比 ArrayMap 节省 1/3 的内存,但 SparseArray 只能存储 key 为 int 类型的 Map。
    ArrayMap 比 HashMap 更节省内存,综合性能方面再数据量不大的情况下,推荐使用 ArrayMap。
    HashMap 需要创建一个额外的数据结构 Node,且容量的利用率比 ArrayMap 低,整体更消耗内存。
  • 性能方面
    SparseArray 查找的时间复杂度为 O(logN),适合频繁删除和插入来回执行的场景,性能比较好。
    ArrayMap 查找时间复杂度也是 O(logN),ArrayMap 增加、删除操作需要移动成员,速度比较慢,对于个数小于 1000 的情况下,性能基本没有明显差异。
    缓存机制。
    SparseArray 有延迟回收机制,提供删除效率(标记为DELETED),同时减少数组成员来回拷贝的次数。
    HashMap 没有缓存机制。
  • 扩容机制
    SparseArray 容量满时触发扩容机制,如果当前容量小于等于 4 则扩容 为 8,否则扩容容量为原容量的 2 倍。
    ArrayMap 容量满时将容量扩大至原来的 1.5 倍,在容量不足 1/3 时触发内存收缩至原来的 0.5 倍。
    HashMap 在容量 > 容量 * 负载因子(默认0.75)时将容量扩大至原来的 2 倍,且没有内存收缩机制。

线程有哪些状态

线程通常都有五种状态,创建、就绪、运行、阻塞和死亡。

  • 创建状态。在生成线程对象,并没有调用该对象的start方法,这是线程处于创建状态。
  • 就绪状态。当调用了线程对象的start方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪状态。
  • 运行状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码。
  • 阻塞状态。线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)之后再继续运行。sleep,suspend,wait等方法都可以导致线程阻塞。
  • 死亡状态。如果一个线程的run方法执行结束或者调用stop方法后,该线程就会死亡。对于已经死亡的线程,无法再使用start方法令其进入就绪

sleep() 和 wait() 有什么区别

  • sleep():方法是线程类(Thread)的静态方法,让调用线程进入睡眠状态,让出执行机会给其他线程,等到休眠时间结束后,线程进入就绪状态和其他线程一起竞争cpu的执行时间。因为sleep() 是static静态的方法,他不能改变对象的机锁,当一个synchronized块中调用了sleep() 方法,线程虽然进入休眠,但是对象的机锁没有被释放,其他线程依然无法访问这个对象。
  • wait():wait()是Object类的方法,当一个线程执行到wait方法时,它就进入到一个和该对象相关的等待池,同时释放对象的机锁,使得其他线程能够访问,可以通过notify,notifyAll方法来唤醒等待的线程。

JNI

  • JNIEnv 和 JavaVM

JavaVM: 是虚拟机在 JNI 层的代表。一个进程只有一个 JavaVM。所有的线程共用一个 JavaVM。
JNIEnv: 表示Java 调用 native 语言的环境,封装了几乎全部 JNI 方法的指针。JNIEnv 只在创建它的线程生效,不能跨线程传递,不同线程的 JNIEnv 彼此独立。
在 native 环境下创建的线程,要想和 java 通信,即需要获取一个 JNIEnv 对象。我们通过AttachCurrentThread 和 DetachCurrentThread 方法将 native 的线程与 JavaVM 关联和解除关联。

  • JNI 中全局引用和局部引用的区别

全局引用: NewGlobalRef 和 DeleteGlobalRef 方法创建和释放一个全局引用,全局引用能在多个线程中被使用,且不会被 GC 回收,只能手动释放。可以用于线程间共享内存数据。
局部引用: NewLocalRef 和 DeleteLocalRef 方法创建和释放一个局部引用,局部引用只在创建它的 native 方法中有效,包括其调用的其它函数中有效。因此我们不能寄望于将一个局部引用直接保存在全局变量中下次使用。不用删除局部引用,它们会在native 方法返回时全部自动释放,但是建议对于不再使用的局部引用手动释放,避免内存过度使用。
弱全局引用:NewWeakGlobalRef 和 DeleteWeakGlobalRef创建和释放一个弱全局引用。弱全局引用类似于全局引用,唯一的区别是它不会阻止被 GC 回收

  • JNI函数的注册过程

Dalvik虚拟机在调用一个成员函数的时候,如果发现该成员函数是一个JNI方法,那么就会直接跳到它的地址去执行。也就是说,JNI方法是直接在本地操作系统上执行的,而不是由Dalvik虚拟机解释器执行。
结构体JNIEnv的成员变量functions指向的是一个函数表,这个函数表又包含了一系列的函数指针,指向了在当前进程中运行的Dalvik虚拟机中定义的函数。对于结构体JNIEnv的成员函数RegisterNatives来说,它就是通过调用这个函数表中名称为RegisterNatives的函数指针来注册参数gMethods所描述的JNI方法的

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值