1、描述Android应用进程的启动流程
- 1、点击启动一个App,Launcher进程采用Binder IPC向ActivityManagerService发起startActivity请求;
- 2、ActivityManagerService接收到请求后,向zygote进程发送创建进程的请求;Zygote进程fork出新的子进程,即App进程;
- 3、App进程通过Binder IPC向sytem_server进程发起绑定Application请求;
- 4、system_server进程在收到请求后,进行一系列准备工作后,再通过binder IPC向App进程发送scheduleLaunchActivity请求;
- 5、App进程的binder线程(ApplicationThread)在收到请求后,通过handler向主线程发送LAUNCH_ACTIVITY消息;
- 6、主线程在收到Message后,通过发射机制创建目标Activity,并回调Activity.onCreate()等方法。
到此,App便正式启动,开始进入Activity生命周期,执行完onCreate/onStart/onResume方法,UI渲染结束后便可以看到App的主界面。
其流程图如下所示:
2、简述Android启动过程
1、概述:Loader > Kernel > Native > Framework > Application
2、细分:BootRom > Bootloader > Kernel > Init > Zygote > SystemServer > Launcher
Loader层主要包括Boot Rom和Boot Loader
Kernel层主要是Android内核层
Native层主要是包括init进程以及其fork出来的用户空间的守护进程、HAL层、开机动画等
Framework层主要是AMS和PMS等Service的初始化
Application层主要指SystemUI、Launcher的启动
3、Android UI有大量的界面元素,造成页面显示白屏,如何进行优化?
1、产生原因
其实显示黑屏或者白屏实属正常,这是因为还没加载到布局文件,就已经显示了window窗口背景,黑屏白屏就是window窗口背景
2、解决办法:通过设置主题来防止白屏或者黑屏
(1)设置背景图Theme
通过设置一张背景图。 当程序启动时,首先显示这张背景图,避免出现黑屏
(2)设置透明Theme
通过把样式设置为透明,程序启动后不会黑屏而是整个透明了,等到界面初始化完才一次性显示出来
3、是否了解插件化开发和热修复,请简单介绍
1、模块化
模块(Module),Android Studio提出的概念,根据不同关注点将原项目中共享的部分或业务抽取出来形成独立module,这就类似我们最集成的第三方库的SDK。
2、组件化
组件化是基于模块化的,可以在打包时是设置为library,开始调试运行是设置成application。目的是解耦与加快开发。组件化适用于多人合作开发的场景,隔离不需要关注的模块,大家各自分工、各守其职。简而言之,就是把一个项目分开成多个项目。
3、插件化
也是属于模块化的一种体现。将完整的项目按业务划分不同的插件,分治法,越小的模块越容易维护。单位是apk,一个完整的项目。插件化比热修复简单,插件化只是增加新的功能或资源文件。灵活性在于加载apk,按需下载,动态更新。
4、热修复
热修复与插件化都利用classloader实现加载新功能。热修复比插件化复杂,插件化只是增加新的功能或资源文件,所以不涉及抢先加载旧类的使命。热修复为了修复bug,要将新的同名类替旧的同名bug类,要抢在加载bug类之前加载新的类。
4、什么是单例?
1、构造方法不对外开放,一般是 private;
2、通过一个静态方法或者枚举返回单例对象;
3、注意多线程的场景;
4、注意单例类对象在反序列化时不会重新创建对象;
1.饿汉 如果应用程序总是创建并使用单例实例或在创建和运行时开销不大
class Single {
private Single(){}
private static Single single= new Single();
public static Single getInstance(){
return single;
}
}
2.懒汉 如果开销比较大,希望用到时才创建就要考虑延迟实例化 Singleton的初
始化需要某些外部资源(比如网络或存储设备)
class Single {
private Single(){}
//添加 volatile,禁止指令重排
private volatile static Single single= null;
public static Single getInstance(){
if ( single == null ) {
synchronized (Single.class) {
if ( single == null ) {
single = new Single();
}
}
return single;
}
}
3.静态内部类
class Single {
private Single(){}
private static class SingleHandler{
private static Single single = new Single();
}
public static Single getInstance(){
return Single.SingleHandler.single;
}
5、什么是匿名内部类,它有什么特征?
匿名内部类使用注意事项:
1、匿名内部类也就是没有名字的内部类
2、正因为没有名字,所以匿名内部类只能使用一次,它通常用来简化代码编写
3、但使用匿名内部类还有个前提条件:必须继承一个父类或实现一个接口
4、匿名内部类中是不能定义构造函数的
**使用的形参为何要为final **
- 我们给匿名内部类传递参数的时候,若该形参在内部类中需要被使用,那么该形参必须要为final。也就是说:当所在的方法的形参需要被内部类里面使用时,该形参必须为final.
2. 为什么必须要为final呢?
- 首先我们知道在内部类编译成功后,它会产生一个class文
件,该class文件与外部类并不是同一class文件,仅仅只保
留对外部类的引用。当外部类传入的参数需要被内部类调
用时,从java程序的角度来看是直接被调用- 在内部类中的属性和外部方法的参数两者从外表上看是同
一个东西,但实际上却不是,所以他们两者是可以任意变
化的,也就是说在内部类中我对属性的改变并不会影响到
外部的形参,而然这从程序员的角度来看这是不可行的,
毕竟站在程序的角度来看这两个根本就是同一个,如果内
部类该变了,而外部方法的形参却没有改变这是难以理解
和不可接受的,所以为了保持参数的一致性,就规定使用
ænal来避免形参的不改变。- 简单理解就是,拷贝引用,为了避免引用值发生改变,例
如被外部类的方法修改等,而导致内部类得到的值不一
致,于是用ænal来让该引用不可改变。- 故如果定义了一个匿名内部类,并且希望它使用一个其外
部定义的参数,那么编译器会要求该参数引用是final的。
6、String中"=="与equals()的区别?
1、"=="是Java中的比较运算符,用于比较两个对象的引用是否相等。当使用"=="比较两个String对象时,它会比较两个对象的引用地址,即判断两个对象是否指向同一个内存地址。
2、而equals()是String类中的方法,用于比较两个String对象的内容是否相等。当使用equals()方法比较两个String对象时,它会比较两个对象的内容是否相同,即判断两个对象所包含的字符序列是否相同。
3、总结起来,"=="比较的是对象的引用地址,而equals()比较的是对象的内容。在大多数情况下,我们应该使用equals()方法来比较两个String对象的内容是否相等。
7、为什么说 String 不可变的
String 不可变是因为一旦字符串被创建,它的值不能被改变。任何对字符串的改变都会生成一个新的字符串对象,并在堆内存中分配一个新的位置。这是因为字符串常量池是一个特殊的内存区域,用于存储字符串常量。由于字符串被频繁使用,所以String不可变性可以提高应用程序的性能。
8、什么是内存泄漏,Java是如何处理它的?
1、内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
2、产生的原因:一个长生命周期的对象持有一个短生命周期对象的引用;
3、通俗讲就是该回收的对象,因为引用问题没有被回收,最终会产生OOM。
9、Java回收机制,如何减少OOM的概率?
1、GC如何判断对象的存活:可达性分析(Java)
2、尽可能少的发生内存泄漏
3、尽可能不在循环中申请内存
4、尽可能不在调用次数多的函数中申请内存
在Java, 可作为GC Roots的对象包括:
方法区: 类静态属性的对象;
方法区: 常量的对象;
虚拟机栈(本地变量表)中的对象.
本地方法栈JNI(Native方法)中的对象。
10、Java中强引用、软引用、弱引用以及虚引用?
强引用 Object obj=new Object();
软引用 内存不足时回收
弱引用 GC到来时回收
虚引用 GC回收时可得到一个通知
11、什么是依赖注入,能说说几个依赖注入的 库吗?
依赖是类与类之间的连接,依赖关系表示一个类依赖于另一个类的定义,通俗来就是一种需要,例如一个人(Person)可以买车(Car)和房子(House),Person类依赖于Car类和House类。
表达通俗一点就是:我不想自己实例化依赖,你(injector)创建它们,然 后在合适的时候注入给我
实现依赖注入有 3 种方式:
1. 构造函数中注入
2. setter 方式注入
3. 接口注入
12、关键字synchronized的作用是什么?
synchronized 关键字可以在多线程环境下用来作为线程安全的同步锁。
synchronized关键字主要有以下三种用法:
- 修饰代码块
- 修饰成员方法
- 修饰静态方法
13、两个Activity或者Fragment如何进行通信
在Activity与Activity�Activity与Fragment�Fragment与Fragment�之间通信使用LivedataBus。
14、什么是Fragment?�它和activity的关系
1、Fragment是依赖于Activity的,不能独立存在,Activity是Fragment的一个容器。
2、一个Activity里可以有多个Fragment。
3、一个Fragment可以被多个Activity重用。
4、Fragment有自己的生命周期,并能接收输入事件。
我们能在Activity运行时动态地添加或删除Fragment。
15、为什么只使用默认的构造方法来创建Fragment
1、当系统内存不足的情况下,app处于后台条件下,内存会被系统回收,这时用户将app切换到前台,系统会重新将之前回收的内容实例化回来。
2、这个过程是Android系统通过两个方法来实现的:
onSaveInstantceState();
onRestoreInstantceState();
3、onSaveInstantceState是系统要回收该界面(Activity、Fragment)时调用的方法,用于保存该(Activity、Fragment)的实例变量到外存。
4、onRestoreInstantceState是系统恢复该(Activity、Fragment)时调用的方法,用于恢复之前被回收的(Activity、Fragment)实例。
16、Bundle被用来传递数据,为什么不用HashMap代替?
1、ArrayMap适合于小数据量操作,如果在数据量比较大的情况下,它的性能将退化。HashMap内部则是数组+链表结构,所以在数据量较少的时候,HashMap的Entry Array比ArrayMap占用更多的内存。而使用Bundle的场景大多数为小数据量。所以使用ArrayMap实现更合适。
2、Android中如果使用Intent来携带数据的话,需要数据是基本类型或者是可序列化类型,Bundle使用Parcelable进行序列化,而HashMap则是使用Serializable进行序列化。
17、广播和EventBus的区别?
1、广播是四大组件之一,EventBus是开源框架。
2、广播不能直接执行耗时操作,如果超过10秒,会导致 ANR
3、广播非常消耗资源,而EventBus非常轻量
4、广播很容易获取Context和Intent
5、EventBus切换线程非常方便,只需要修改下注解就行了
6、广播可以跨进程,而EventBus不可以
18、什么是 support libary �为什么要引入 support library ?
- support library引入主要是因为安卓版本有很多,新版本的更新也快,每个版本都会有一个开发版本号
- 在进行安卓开发的时候,我们通常需要考虑,应该选择什么样的 API 级别进行开发?
谷歌为此也给出了解决方案,我们在开发过程中可以给出三个设置项:
minSdkVersion <= targetSdkVersion <= compileSdkVersion
当我们targetSdkVersion为 24 时,需要使用 API 28 的新增功能时,这时需要向后兼容,使用低版本的 API 兼容高版本的 API,而支持库就是这个作用,它会跟着每个新发布的 API 级别同步发布,所以这里支持库我们选择与 compileSdkVersion 一样的版本即可。� support libary 只能保证我们针对24开发的APP在28上能正常运行,但运行效果不一定相同。
19、什么是 ANR,如何避免 ANR?
1、ANR(Application Not responding),是指应用程序未响应,Android系统对于一些事件的处理需要在一定的时间范围内完成,如果超过预定时间能未能得到有效响应或者响应时间过长,都会造成ANR。
Timeout时长
- 对于前台服务,则超时为SERVICE_TIMEOUT = 20s;
- 对于后台服务,则超时为SERVICE_BACKGROUND_TIMEOUT = 200s
- 对于前台广播,则超时为BROADCAST_FG_TIMEOUT = 10s;
- 对于后台广播,则超时为BROADCAST_BG_TIMEOUT = 60s;
- ContentProvider超时为CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10s;
- InputDispatching Timeout: 输入事件分发超时5s,包括按键和触摸事件。
2、如何避免ANR发生
- 主线程尽量只做UI相关的操作,避免耗时操作,比如过度复杂的UI绘制,网络操作,文件IO操作;
- 避免主线程跟工作线程发生锁的竞争,减少系统耗时binder的调用,谨慎使用sharePreference,注意主线程执行provider query操作。
20、三级缓存?
三级缓存的原理就是当 App 需要引用缓存时,首先到内存缓存中读取,读取不到再到本地缓存中读取,还获取不到就到网络异步读取,读取成功之后再缓存到内存和本地中。
21、Dalvik和ART的区别
1、Dalvik环境中,应用每次运行时,字节码都需要通过即时编译器 ( Just In Time,JIT) 转换为机器码。ART 环境中,应用会在安装的时候,就将字节码 预编译(Ahead Of Time,AOT) 成机器码,使其成为真正的本地应用。
2、ART 占用的空间比 Dalvik 大,就是用 空间换时间。
3、ART 不用每次运行时都重复编译,减少了CPU 的使用频率,降低了能耗。
22、内存溢出(OutOfMemory)�是怎么发生的?
1.内存泄漏�
2.频繁申请内存得不到急时的回收
23、Context内存泄漏问题
1、静态资源导致的内存泄漏
2、单例模式导致内存泄漏
- Context是什么?Context是”运行上下文环境“,从代码角度看
Application,Service,Activity都是Context。- 所有Context都是在应用的主线程ActivityThread中创建的,由于
Application,Service,Activity的祖先都是Context抽象类,所以在创
建它们的同时也会为每一个类创建一个ContextImpl类,ContextImpl 是Context的之类,真正实现Context功能方法的类。因此
Application,Service,Activity都关联着一个ContextImpl对象。
java.lang.RuntimeException: Unable to create application com.xjp.toucheventdemo.MyApplication: android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want? Caused by: java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.- 尽量少用Context对象去获取静态变量,静态方法,以及单例对象。以免导致内存泄漏。
- 在创建与UI相关的地方,比如创建一个Dialog,或者在代码中创建一个TextView,都用Activity的Context去创建。然而在引用静态资源,创建静态方法,单例模式等情况下,使用生命周期更长的Application 的Context才不会导致内存泄漏。
24、onTrimMemory() 方法是什么?
能让activity得到内存情况的通知
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
switch (level) {
case TRIM_MEMORY_UI_HIDDEN:
// 进行资源释放操作
break;
}
}
运行时的回调
TRIM_MEMORY_RUNNING_MODERATE
表示应用程序正常运行,并且不会被杀掉。但是目前手机的内存已经有点低了
TRIM_MEMORY_RUNNING_LOW 表示应用程序正常运行,并且不会被杀掉。但是目前手机的内存已经非常低了,我们应该去释放掉一些不必要的资源以提升系统的性能。
TRIM_MEMORY_RUNNING_CRITICAL 表示应用程序仍然正常运行,但是系统已经根据LRU缓存规则杀掉了大部分缓存的进程了。
缓存的回调:
TRIM_MEMORY_UI_HIDDEN
UI组件全部不可见的时候才会触发,一旦触发了之后就说明用户已经离开了我们的程序
TRIM_MEMORY_BACKGROUND 表示手机目前内存已经很低了,系统准备开始根据LRU缓存来清理进程。这个时候我们的程序在LRU缓存列表的最近位置,是不太可能被清理掉的。
TRIM_MEMORY_MODERATE 表示手机目前内存已经很低了,并且我们的程序处于LRU缓存列表的中间位置。
TRIM_MEMORY_COMPLETE 表示手机目前内存已经很低了,并且我们的程序处于LRU缓存列表的最边缘位置。
25、线程的创建
线程创建的常用方法
1.继承Thread重写run方法
2.实现Runnable重写run方法
3.实现Callable重写call方法
26、线程中断
一般情况下,线程不执行完任务不会退出,但是在有些场景下,我们需要手动控制线程中断结束任务,Java中有提供线程中断机制相关的Api,每个线程都一个状态位用于标识当前线程对象是否是中断状态
public boolean isInterrupted() //判断中断标识位是否是true,不会改变标识位
public void interrupt() //将中断标识位设置为true
public static boolean interrupted() //判断当前线程是否被中断,并且该方法调用结束的时候会清空中断标识位
需要注意的是interrupt()方法并不会真的中断线程,它只是将中断标识位设置为true,具体是否要中断由程序来判断,如下,只要线程中断标识位为false,也就是没有中断就一直执行线程方法
new Thread(new Runnable(){
while(!Thread.currentThread().isInterrupted()){
//执行线程方法
}
}).start();
27、Thread为什么不能用stop方法停止线程
从SUN的官方文档可以得知,调用Thread.stop()方法是不安全的,这是因为当调用Thread.stop()方法时,会发生下面两件事:
1.即刻抛出 ThreadDeath异常,在线程的run()方法内,任何一点都有可能抛出ThreadDeath Error,包括在catch或finally语句中。
2.释放该线程所持有的所有的锁。调用thread.stop()后导致了该线程所持有的所有锁的突然释放,那么被保护数据就有可能呈现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用程序错误。
28、volatile关键字
volatile为实例域的同步访问提供了免锁机制,如果声明一个域为volatile,那么编译器和虚拟机就直到该域可能被另一个线程并发更新。
29、java内存模型
堆内存是被所有线程共享的运行时内存区域,存在可见性的问题。线程之间共享变量存储在主存中,每个线程都有一个私有的本地内存,本地内存存储了该线程共享变量的副本(本地内存是一个抽象概念,并不真实存在),两个线程要通信的话,首先A线程把本地内存更新过的共享变量更新到主存中,然后B线程去主存中读取A线程更新过的共享变量,也就是说假设线程A执行了i = 1这行代码更新主线程变量i的值,会首先在自己的工作线程中堆变量i进行赋值,然后再写入主存当中,而不是直接写入主存.
30、线程特性:
- 原子性:
对基本数据类型的读取和赋值操作是原子性操作,这些操作不可被中断,是一步到位的,例如x=3是原子性操作,而y = x就不是,它包含两步:第一读取x,第二将x写入工作内存;x++也不是原子性操作,它包含三部,第一,读取x,第二,对x加1,第三,写入内存。原子性操作的类如:AtomicInteger AtomicBoolean AtomicLong AtomicReference- 可见性:
指线程之间的可见性,既一个线程修改的状态对另一个线程是可见的。volatile修饰可以保证可见性,它会保证修改的值会立即被更新到主存,所以对其他线程是可见的,普通的共享变量不能保证可见性,因为被修改后不会立即写入主存,何时被写入主存是不确定的,所以其他线程去读取的时候可能读到的还是旧值- 有序性:
Java中的指令重排序(包括编译器重排序和运行期重排序)可以起到优化代码的作用,但是在多线程中会影响到并发执行的正确性,使用volatile可以保证有序性,禁止指令重排volatile可以保证可见性 有序性,但是无法保证原子性,在某些情况下可以提供优于锁的性能和伸缩性,替代sychronized关键字简化代码,但是要严格遵循使用条件。
31、线程池ThreadPoolExecutor
线程池的工作原理:线程池可以减少创建和销毁线程的次数,从而减少系统资源的消耗,当一个任务提交到线程池时
a. 首先判断核心线程池中的线程是否已经满了,如果没满,则创建一个核心线程执行任务,否则进入下一步
b. 判断工作队列是否已满,没有满则加入工作队列,否则执行下一步
c. 判断线程数是否达到了最大值,如果不是,则创建非核心线程执行任务,否则执行饱和策略,默认抛出异常
32、线程池的种类
1.FixedThreadPool:
可重用固定线程数的线程池,只有核心线程,没有非核心线程,核心线程不会被回收,有任务时,有空闲的核心线程就用核心线程执行,没有则加入队列排队
2.SingleThreadExecutor:
单线程线程池,只有一个核心线程,没有非核心线程,当任务到达时,如果没有运行线程,则创建一个线程执行,如果正在运行则加入队列等待,可以保证所有任务在一个线程中按照顺序执行,和FixedThreadPool的区别只有数量
3.CachedThreadPool:
按需创建的线程池,没有核心线程,非核心线程有Integer.MAX_VALUE个,每次提交
4.ScheduledThreadPoolExecutor:
继承自ThreadPoolExecutor,用于延时执行任务或定期执行任务,核心线程数固定,线程总数为Integer.MAX_VALUE
33、线程同步机制与原理,举例说明
为什么需要线程同步?当多个线程操作同一个变量的时候,存在这个变量何时对另一个线程可见的问题,也就是可见性。每一个线程都持有主存中变量的一个副本,当他更新这个变量时,首先更新的是自己线程中副本的变量值,然后会将这个值更新到主存中,但是是否立即更新以及更新到主存的时机是不确定的,这就导致当另一个线程操作这个变量的时候,他从主存中读取的这个变量还是旧的值,导致两个线程不同步的问题。线程同步就是为了保证多线程操作的可见性和原子性,比如我们用synchronized关键字包裹一端代码,我们希望这段代码执行完成后,对另一个线程立即可见,另一个线程再次操作的时候得到的是上一个线程更新之后的内容,还有就是保证这段代码的原子性,这段代码可能涉及到了好几部操作,我们希望这好几步的操作一次完成不会被中间打断,锁的同步机制就可以实现这一点。一般说的synchronized用来做多线程同步功能,其实synchronized只是提供多线程互斥,而对象的wait()和notify()方法才提供线程的同步功能。JVM通过Monitor对象实现线程同步,当多个线程同时请求synchronized方法或块时,monitor会设置几个虚拟逻辑数据结构来管理这些多线程。新请求的线程会首先被加入到线程排队队列中,线程阻塞,当某个拥有锁的线程unlock之后,则排队队列里的线程竞争上岗(synchronized是不公平竞争锁,下面还会讲到)。如果运行的线程调用对象的wait()后就释放锁并进入wait线程集合那边,当调用对象的notify()或notifyall()后,wait线程就到排队那边。这是大致的逻辑。
34、arrayList与linkedList的读写时间复杂度
1.ArrayList:
ArrayList是一个泛型类,底层采用数组结构保存对象。数组结构的优点是便于对集合进行快速的随机访问,即如果需要经常根据索引位置访问集合中的对象,使用由ArrayList类实现的List集合的效率较好。数组结构的缺点是向指定索引位置插入对象和删除指定索引位置对象的速度较慢,并且插入或删除对象的索引位置越小效率越低,原因是当向指定的索引位置插入对象时,会同时将指定索引位置及之后的所有对象相应的向后移动一位。
2.LinkedList:
LinkedList是一个泛型类,底层是一个双向链表,所以它在执行插入和删除操作时比ArrayList更加的高效,但也因为链表的数据结构,所以在随机访问方面要比ArrayList差。
3.ArrayList是线性表(数组)get()O(1)add(E)O(1)add(index, E)O(n)remove()O(n)
LinkedList 是链表的操作get()add(E)add(index, E)remove()
35、为什么HashMap线程不安全(hash碰撞与扩容导致)
HashMap的底层存储结构是一个Entry数组,每个Entry又是一个单链表,一旦发生Hash冲突的的时候,HashMap采用拉链法解决碰撞冲突,因为hashMap的put方法不是同步的,所以他的扩容方法也不是同步的,在扩容过程中,会新生成一个新的容量的数组,然后对原数组的所有键值对重新进行计算和写入新的数组,之后指向新生成的数组。当多个线程同时检测到hashmap需要扩容的时候就会同时调用resize操作,各自生成新的数组并rehash后赋给该map底层的数组table,结果最终只有最后一个线程生成的新数组被赋给table变量,其他线程的均会丢失。而且当某些线程已经完成赋值而其他线程刚开始的时候,就会用已经被赋值的table作为原始数组,这样也会有问题。扩容的时候 可能会引发链表形成环状结构.
36、Java内存模型(记住堆栈是内存分区,不是模型)
Java内存模型(即Java Memory Model,简称JMM)本身是一种抽象的概念,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈空间),用于存储线程私有的数据,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,工作内存中存储着主内存中的变量副本拷贝,前面说过,工作内存是每个线程的私有数据区域,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成.
37、双亲委托模式
类加载器查找class所采用的是双亲委托模式,所谓双亲委托模式就是判断该类是否已经加载,如果没有则不是自身去查找而是委托给父加载器进行查找,这样依次进行递归,直到委托到最顶层的Bootstrap ClassLoader,如果Bootstrap ClassLoader找到了该Class,就会直接返回,如果没找到,则继续依次向下查找,如果还没找到则最后交给自身去查找.
38、双亲委托模式的好处
1.避免重复加载,如果已经加载过一次Class,则不需要再次加载,而是直接读取已经加载的Class.
2.更加安全,确保,java核心api中定义类型不会被随意替换,比如,采用双亲委托模式可以使得系统在Java虚拟机启动时旧加载了String类,也就无法用自定义的String类来替换系统的String类,这样便可以防止核心`API库被随意篡改。
39、死锁的产生条件,如何避免死锁
死锁的四个必要条件
1.互斥条件:
一个资源每次只能被一个进程使用
2.请求与保持条件:
进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
3.不可剥夺条件:
进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。
4.循环等待条件:
若干进程间形成首尾相接循环等待资源的关系
避免死锁的方法:
系统对进程发出每一个系统能够满足的资源申请进行动态检查,并根据检查结果决定是否分配资源,如果分配后系统可能发生死锁,则不予分配,否则予以分配,这是一种保证系统不进入死锁状态的动态策略。
一般来说互斥条件是无法破坏的,所以在预防死锁时主要从其他三个方面入手
(1)破坏请求和保持条件:
在系统中不允许进程在已获得某种资源的情况下,申请其他资源,即要想出一个办法,阻止进程在持有资源的同时申请其它资源。
(2)破坏不可抢占条件:
允许对资源实行抢夺。
(3)破坏循环等待条件
对系统所有资源进行线性排序并赋予不同的序号,这样我们便可以规定进程在申请资源时必须按照序号递增的顺序进行资源的申请,当以后要申请时需检查要申请的资源的编号大于当前编号时,才能进行申请。
40、App启动流程
1.App启动时,AMS会检查这个应用程序所需要的进程是否存在,不存在就会请求Zygote进程启动需要的应用程序进程Zygote进程接收到AMS请求并通过fock自身创建应用程序进程,这样应用程序进程就会获取虚拟机的实例,还会创建Binder线程池(ProcessState.startThreadPool())和消息循环(ActivityThread looper.loop)App进程,通过Binder IPC向sytem_server进程发起attachApplication请求;system_server进程在收到请求后,进行一系列准备工作后,再通过Binder IPC向App进程发送scheduleLaunchActivity请求;App进程的binder线程(ApplicationThread)在收到请求后,通过handler向主线程发送LAUNCH_ACTIVITY消息;Message后,通过反射机制创建目标Activity,并回调Activity.onCreate()等方法。App便正式启动,开始进入Activity生命周期,执行完onCreate/onStart/onResume方法,UI渲染结束后便可以看到App的主界面。
41、Android单线程模型
Android单线程模型的核心原则就是:(Main Thread)中对UI进行处理。当一个程序第一次启动时,Android会同时启动一个对应的 主线程(Main Thread),主线程主要负责处理与UI相关的事件,如:用户的按键事件,用户接触屏幕的事件以及屏幕绘图事 件,并把相关的事件分发到对应的组件进行处理。所以主线程通常又被叫做UI线 程。在开发Android应用时必须遵守单线程模型的原则: Android UI操作并不是线程安全的并且这些操作必须在UI线程中执行。
Android的单线程模型有两条原则:
1.不要阻塞UI线程。
2.不要在UI线程之外访问Android UI toolkit(主要是这两个包中的组件:android.widget and android.view
42、RecyclerView在很多方面能取代ListView,Google为什么没把ListView划上一条过时的横线?
ListView采用的是RecyclerBin的回收机制在一些轻量级的List显示时效率更高。
43、什么是Lifecycle?
简单的说就是用来监听Activity与Fragment的生命周期变化。
- 在生命周期拥有者与生命周期的观察者之间快速方便的建立一种联系。在生命周期拥有者的生命周期变化时,观察者会收到对应的通知。
- 可以方便的判断当前生命周期拥有者所处在的生命周期状态。
44、Service和IntentService的区别概述
- Service不是运行在独立的线程,所以不建议在Service中编写耗时的逻辑和操作,否则会引起ANR。
IntentService
- 可用于执行后台耗时的任务,任务执行后会自动停止。
- 具有高优先级,适合高优先级的后台任务,且不容易被系统杀死。
- 可以多次启动,每个耗时操作都会以工作队列的方式在IntentService的onHandleIntent回调方法中执行
45、什么是Android的模块化、组件化、插件化
- 模块化:一个程序按照其功能做拆分,分成相互独立的模块(例如:登陆,注册)。模块化的具体实施方法分为插件化和组件化。
- 组件化:是将一个app分成多个模块,每个模块都是一个组件(module),开发的过程中我们可以让这些组件相互依赖或者单独调试部分组件,但是最终发布的时候将这些组件合并成一个统一的apk,这就是组件化开发。
- 插件化:插件化开发和组件化不同,插件化开发就是将整个app拆分成很多模块,每个模块都是一个apk(组件化的每个模块是一个lib),最终打包的时候将宿主apk和插件apk分开打包,插件apk通过动态下发到宿主apk,这就是插件化。
参考文章地址
46、浅述APK的打包流程
- 第一步 aapt阶段: 资源打包工具,将res资源文件打包成R.java文件和res文件。
- 第二步 aidl阶段: 这个阶段处理.aidl文件,生成对应的Java接口文件。
- 第三步 Java Compiler阶段:通过Java Compiler编译R.java、Java接口文件、Java源文件,生成.class文件。
- 第四步 dex阶段: 通过dex2.jar将.class文件处理生成class.dex。
- 第五步 apkbuilder阶段:将classes.dex、res文件夹、AndroidManifest.xml打包成apk文件。
- 第六步 Jarsigner阶段:对apk文件加签,形成一个可以运行的apk。
参考文章地址一
参考文章地址二
47、打apk包时,V1、V2签名的区别是什么
- v1签名是对jar进行签名,V2签名是对整个apk签名:官方介绍就是:v2签名是在整个APK文件的二进制内容上计算和验证的,v1是在归档文件中解压缩文件内容。
- 二者签名所产生的结果:
- v1:在v1中只对未压缩的文件内容进行了验证,所以在APK签名之后可以进行很多修改——文件可以移动,甚至可以重新压缩。即可以对签名后的文件在进行处理
- v2:v2签名验证了归档中的所有字节,而不是单独的ZIP条目,如果您在构建过程中有任何定制任务,包括篡改或处理APK文件,请确保禁用它们,否则您可能会使v2签名失效,从而使您的APKs与Android 7.0和以上版本不兼容。
根据实际开发的经验总结:
一定可行的方案: 只使用 v1 方案
不一定可行的方案:同时使用 v1 和 v2 方案
对 7.0 以下一定不行的方案:只使用 v2 方案
48、ANR问题
- ANR全称:Application Not Responding,也就是应用程序无响应。
- 以下四个条件都可以造成ANR发生:
- InputDispatching Timeout:5秒内无法响应屏幕触摸事件或键盘输入事件
BroadcastQueue Timeout :在执行前台广播(BroadcastReceiver)的onReceive()函数时10秒没有处理完成,后台为60秒。
Service Timeout :前台服务20秒内,后台服务在200秒内没有执行完毕。
ContentProvider Timeout :ContentProvider的publish在10s内没进行完成。
- 造成ANR的原因及解决办法:
1、主线程阻塞或主线程数据读取。解决办法:使用子线程处理耗时任务或者阻塞任务
2、CPU满负荷,I/O阻塞。解决办法:文件读写或者数据库操作放在子线程。
3、内存不足。解决办法:优化内存,防止内存泄漏。
4、各大组件ANR。解决办法:各大组件的生命周期也应避免耗时操作。