Android

android 面试题总结

2020-06-10  本文已影响0人  thomasyoungs

Jave部分

一、多线程

  Join()  线程加入,执行此方法的线程优先使用cpu

  Yeild()  线程释放资源使所有线程能有相等的机会使用cpu

  Sleep() 相当于让线程睡眠,交出CPU,让CPU去执行其他的任务(不会释放锁)。

Wait()方法会让线程进入阻塞状态,并且会释放线程占有的锁,并交出CPU执行权限。wait是要释放对象锁,进入等待池。既然是释放对象锁,那么肯定是先要获得锁。所以wait必须要写在synchronized代码块中,否则会报异常。

notify方法,也需要写在synchronized代码块中,调用对象的这两个方法也需要先获得该对象的锁.notify,notifyAll, 唤醒等待该对象同步锁的线程,并放入该对象的锁池中.对象的锁池中线程可以去竞争得到对象锁,然后开始执行.如果是通过notify来唤起的线程,那先进入wait的线程会先被唤起来,并非随机唤醒;如果是通过nootifyAll唤起的线程,默认情况是最后进入的会先被唤起来,即LIFO的策略;notify()或者notifyAll()调用时并不会真正释放对象锁, 必须等到synchronized方法或者语法块执行完才真正释放锁.

Interrupt() 处于阻塞状态(wait sleep)的线程调用时会抛出异常(InterruptedException )

守护线程  不会去实现系统的主要功能,主要用于监控、抓取系统资源明细和运行状态    等操作,如垃圾回收线程setDeamon(true)  在start方法前调用,守护线程中不要做

二、Java中的String,StringBuilder,StringBuffer三者的区别

String,StringBuilder以及StringBuffer这三个类之间有什么区别呢,自己从网上搜索了一些资料,有所了解了之后在这里整理一下,便于大家观看,也便于加深自己学习过程中对这些知识点的记忆,如果哪里有误,恳请指正。 这三个类之间的区别主要是在两个方面,即运行速度和线程安全这两方面。首先说运行速度,或者说是执行速度,在这方面运行速度快慢为:StringBuilder > StringBuffer > String                                                                          String最慢的原因:String为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦创建之后该对象是不可更改的,但后两者的对象是变量,是可以更改的。以下面一段代码为例:                                              1 String str="abc";2 System.out.println(str);3 str=str+"de";4 System.out.println(str);

 如果运行这段代码会发现先输出“abc”,然后又输出“abcde”,好像是str这个对象被更改了,其实,这只是一种假象罢了,JVM对于这几行代码是这样处理的,首先创建一个String对象str,并把“abc”赋值给str,然后在第三行中,其实JVM又创建了一个新的对象也名为str,然后再把原来的str的值和“de”加起来再赋值给新的str,而原来的str就会被JVM的垃圾回收机制(GC)给回收掉了,所以,str实际上并没有被更改,也就是前面说的String对象一旦创建之后就不可更改了。所以,Java中对String对象进行的操作实际上是一个不断创建新的对象并且将旧的对象回收的一个过程,所以执行速度很慢。

  而StringBuilder和StringBuffer的对象是变量,对变量进行操作就是直接对该对象进行更改,而不进行创建和回收的操作,所以速度要比String快很多。

  另外,有时候我们会这样对字符串进行赋值

1 String str="abc"+"de";2 StringBuilder stringBuilder=new StringBuilder().append("abc").append("de");3 System.out.println(str);4 System.out.println(stringBuilder.toString());

  这样输出结果也是“abcde”和“abcde”,但是String的速度却比StringBuilder的反应速度要快很多,这是因为第1行中的操作和

  String str="abcde";

  是完全一样的,所以会很快,而如果写成下面这种形式

1 String str1="abc";2 String str2="de";3 String str=str1+str2;

  那么JVM就会像上面说的那样,不断的创建、回收对象来进行这个操作了。速度就会很慢。

  2. 再来说线程安全

  在线程安全上,StringBuilder是线程不安全的,而StringBuffer是线程安全的

  如果一个StringBuffer对象在字符串缓冲区被多个线程使用时,StringBuffer中很多方法可以带有synchronized关键字,所以可以保证线程是安全的,但StringBuilder的方法则没有该关键字,所以不能保证线程安全,有可能会出现一些错误的操作。所以如果要进行的操作是多线程的,那么就要使用StringBuffer,但是在单线程的情况下,还是建议使用速度比较快的StringBuilder。

  3. 总结一下

  String:适用于少量的字符串操作的情况

  StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况

StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况

三、Hashmap 和hashtable的区别 

1.Hashmap底层数组+链表实现,可以存储null键和null值,线程不安全,初始size为16,扩容:newsize = oldsize*2,size一定为2的n次幂

扩容针对整个Map,每次扩容时,原来数组中的元素依次重新计算存放位置,并重新插入,插入元素后才判断该不该扩容,有可能无效扩容(插入后如果扩容,如果没有再次插入,就会产生无效扩容),当Map中元素总数超过Entry数组的75%,触发扩容操作,为了减少链表长度,元素分配更均匀,计算index方法:index = hash & (tab.length – 1)

2.hashtable底层数组+链表实现,无论key还是value都不能为null,线程安全,实现线程安全的方式是在修改数据时锁住整个HashTable,效率低,ConcurrentHashMap做了相关优化,初始size为11,扩容:newsize = olesize*2+1,计算index的方法:index = (hash & 0x7FFFFFFF) % tab.length

四、java中接口和抽象类的区别

默认的方法实现  它可以有默认的方法实现  接口完全是抽象的。它根本不存在方法的实现

实现  子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。  子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现

构造器  抽象类可以有构造器  接口不能有构造器

与正常Java类的区别  除了你不能实例化抽象类之外,它和普通Java类没有任何区别  接口是完全不同的类型

访问修饰符  抽象方法可以有public、protected和default这些修饰符  接口方法默认修饰符是public。你不可以使用其它修饰符。

main方法  抽象方法可以有main方法并且我们可以运行它  接口没有main方法,因此我们不能运行它。

多继承  抽象方法可以继承一个类和实现多个接口  接口只可以继承一个或多个其它接口

速度  它比接口速度要快  接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。

添加新方法  如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。  如果你往接口中添加方法,那么你必须改变实现该接口的类。

五、java中的内部类

  1 分类:成员内部类,局部内部类(不能有权限访问修饰符),匿名内部类,静态内部类

  2 为什么要用内部类?隐式包含外部类对象并且能够与之通信的特点,完美的解决了多重继承的问题。

六、Synchronize 和volatile

1.volatile 本质是告诉jvm 该变量在寄存器上是不确定的,需要从主存中读取当前变量。Synchronize 则是锁定当前变量,只有持有改锁的线程可以访问。

2.volatile仅能使用在变量级别,synchronize可使用在变量方法级别

3.volatile仅能实现变量的修改可见性,而synchronize则可以保证变量修改可见性和原子性

注: 共享变量:如果一个变量在多个线程中都使用到了,那么这个变量就是这几个线程的共享变量。

可见性:一个线程对共享变量的修改,能够及时地到主内存并且让其他的线程看到。

Synchronize 可见性实现:线程解锁前会把共享变量的值更新到工作内存的;线程加缩时会把工作内存的共享变量值清空,从主内存中获取

Volatile 可见性实现:通过内存屏障,即当对共享变量进行操作后,加入一条store屏障指令,使其值强制更新到主内存;对共享变量进行操作前,加入一条load屏障指令,强制将主内存中的值刷新到工作内存。 

七、Throw 和throws

1.throw 用在方法体中,throws用于方法头中

2.throws表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某种异常对象。

八、java内存模型

1.程序计数器、虚拟机栈、本地方法栈、堆、方法区。

2.按照对象存储时间的不同,堆中的内存可以划分为新生代(Young)和老年代(Old),其中新生代又被划分为 Eden 和 Survivor 区

3.不同的区域存放具有不同生命周期的对象。这样可以根据不同的区域使用不同的垃圾回收算法,从而更具有针对性,进而提高垃圾回收效率。

4.方法区主要是存储已经被 JVM 加载的类信息(版本、字段、方法、接口)、常量、静态变量、即时编译器编译后的代码和数据。

九、TCP与UDP:

1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接

2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付

3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的

4、TCP的三次握手过程:主机A向B发送连接请求;主机B对收到的主机A的报文段进行确认;主机A再次对主机B的确认进行确认。

UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)

4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信

5、TCP首部开销20字节;UDP的首部开销小,只有8个字节

6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道

十、HTTP与HTTPS的区别以及如何实现安全性

两者的区别:

(1)、HTTP是明文传输,传输内容容易被篡改或者被窃取;HTTPS是密文传输,https相当于包装了SSL\TLS协议的HTTP。

(2)、https在网络请求效率上会低于http,因为采用了不同的请求协议以及更复杂的安全验证操作。

(3)、https需要申请CA证书,用于验证公钥这个证书收费,HTTP不用。

主要通过非对称加密+对称加密+CA证书来保证请求安全的。

十二、垃圾回收算法

1.所谓垃圾就是内存中已经没有用的对象。 既然是”垃圾回收",那就必须知道哪些对象是垃圾。Java 虚拟机中使用一种叫作"可达性分析”的算法来决定对象是否可以被回收。

2.JVM 把内存中所有的对象之间的引用关系看作一张图,通过一组名为”GC Root"的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,最后通过判断对象的引用链是否可达来决定对象是否可以被回收。

方法区中静态引用指向的对象。

仍处于存活状态中的线程对象。

Native 方法中 JNI 引用的对象。

十三、ArryList和linkedList

1.对ArrayList和LinkedList而言,在列表末尾增加一个元素所花的开销都是固定的。对ArrayList而言,主要是在内部数组中增加一项,指向所添加的元素,偶尔可能会导致对数组重新进行分配;而对LinkedList而言,这个开销是统一的,分配一个内部Entry对象。

2.在ArrayList的中间插入或删除一个元素意味着这个列表中剩余的元素都会被移动;而在LinkedList的中间插入或删除一个元素的开销是固定的。

3.LinkedList不支持高效的随机元素访问。

4.ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间

十四、Atomic

用volatile关键字修饰value字段

AtomicInteger用value字段来存储数据值,volatile关键字保证了value字段对各个线程的可见性。各线程读取value字段时,会先从主内存把数据同步到工作内存,这样保证可见性。

Unsafe实现操作原子性,用户在使用时无需额外的同步操作。

如AtomicInteger提供了自增方法getAndIncrement,其内部实现是由Unsafe类的compareAndSetInt方法来保证的。

十五、乐观锁和悲观锁

悲观锁:线程一旦得到锁,其他线程就挂起等待,适用于写入操作频繁的场景;synchronized 就是悲观锁

乐观锁:假设没有冲突,不加锁,更新数据时判断该数据是否过期,过期的话则不进行数据更新,适用于读取操作频繁的场景

乐观锁 CAS:Compare And Swap,更新数据时先比较原值是否相等,不相等则表示数据过去,不进行数据更新

乐观锁实现:AtomicInteger、AtomicLong、AtomicBoolean

Android 部分

一、自定义view

  三个方法:绘制,测量,放置。绘制(Android除了游戏用opengl绘制其余基本都是Canvas实现的)

1、绘制对象:

 弧线(arcs) canvas.

填充颜色(argb和color)

Bitmap

圆(circle和oval)

点(point)

线(line)

矩形(Rect)

图片(Picture)

圆角矩形 (RoundRect)

文本(text)

顶点(Vertices)

路径(path)

2、几何变换

在使用Canvas的save和restore方法时,基本上都是伴随着我们需要对Canvas进行平移或者旋转操作

在代码中首先在(40,40)的位置绘制了一个半径为40的圆,这里的数字表示的都是像素,在绘制完成会,我们保存当前的canvas状态,在这里我就理解为复制了一个canvas,并将这个canvas向右移动mWidth/2,这时,canvas的坐标的原点已经发生改变了 这时在执行绘制动作,在(0,40)绘制一个圆,然后调用Canvas的restore方法,相当于将新复制的canvas上的内容与原来的canvas的内容进行合并,这个合并根据屏幕的坐标进行的(个人是这么理解的),在调用restore方法后,新复制的canvas就被销毁了,而原来的canvas的坐标还是以左上角为原点的坐标系。

3、onmessure()方法什么时候被调。

  当执行setText(),addview(),setvisiblity()方法的时候会调用View的onmessure()方法

因为调用 addView、setVisbility、setTextView等都会调用 requestLayout,而调用 requestLayout就会执行 view的 绘制流程,也就是说会执行 performMeasure、performLayout、performDraw,就会执行 onMeasure;

为了防止卡顿、提高性能和效率,要慎用 addView、setVisbility、setTextView等方法,因为这几个方法会重新调用 requestLayout,会重新测量、重新摆放、重新绘制view,影响性能;

二、硬件加速

对于 Android 来说,硬件加速有它专属的意思:在 Android 里,硬件加速专指把 View 中绘制的计算工作交给 GPU 来处理。

在Android中,可以四给不同层次上开启硬件加速:

  1.应用:

<application android:hardwareAccelerated="true">

  2.Activity

<activity android:hardwareAccelerated="true">

  3.Window

getWindow().setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);

  4.View

view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

  在这四个层次中,应用和Activity是可以选择的,Window只能打开,View只能关闭。

我们需要看看哪些属性是不支持硬件加速的

三、activity生命周期

1、A 中打开B

A onPause()---> B onCreate() ---->B onStart() ---> B o'nResume() ---> A onstop()

再点返回A的生命周期函数调用

B onPause() --->A onRestart() ---> A onStart() --->A onResume() ---> B onStop ---> B onDestroy()

2、A 中打开B如果B是透明的activity,则不会执行a的onstop的restart以及start方法(通dialog)

A onPause()---> B onCreate() ---->B onStart() ---> B o'nResume() 

再点返回A的生命周期函数调用

B onPause() --->A onResume() ---> B onStop ---> B onDestroy()

四、Android 布局优化

Include 提高布局的复用,

Merge 用于降低view树的层级

主要应用场景: 1.布局顶节点是fragmLayout且不需要设置padding和backgroud,可以用merge代替,Activity的内容视图的 parentview是Fragmenlayout,所以可以用merge消除

2.某布局用于子布局被include时使用merge当作该布局的顶节点,这样在被引入时顶结点会自动被忽略, 而将其子节点全部合并到主布局中。

3.由于无论布局文件的根节点是什么系统都会在上一层生成一个<FramLayout>标签,因此,在布局文件 的根节点上使用<FramLayout>是多余的但xml文件不能没有根节点 ,因此可以使用<merge>代替<FramLayout>,系统在编译时不会为<merge>生成任何节点,相当于一个xml文件的节点占位符<merge>的意思其实就是合并两个<FramLayout>,所以使用<merge>代替<FramLayout>可以大大减少<FramLayout>的生成;

ViewStub 实际上时一个宽高均为0的view 通过延迟加载的布局的方式优化布局提升渲染性能.当初次渲染布局文件时, ViewStub 控件虽然也占据内存, 但是相比于其他控件, 它所占内存很小. 它主要是作为一个“占位符”, 放置于 View Tree中, 且它本身是不可见的.

五、android 代理模式 

1、静态代理的特点 :一个代理只服务于一类对象

优点:协调了调用者和被调用者,降低了耦合性,代理对象作为客户对象和目标对象的中,起到了对目标对象的保护作用

缺点:在客户和目标中增加了代理对象,使得处理速度变慢,代理需要做额外的工作增加了系统的复杂度

2、动态代理:相比于静态代理直接调用目标对象的方法,动态代理利用反射机制来调用目标对象得方法,动态代理创建时可传入非指定类型的对象,因此可以代理多类对象,提高了代码的重用性

3、插件化打开apk

插件化的优势:宿主和插件分开编译,并行开发,动态更新插件,按需要下载插件

实现:  1.宿主应用中代理activity建立,activity中加载布局生命周期等方法要调用到你要打开的(未安装的apk中的)activity

  2.传入未安装activity的路径,建立classloader

六、Android 中的parcelable 和java中的serializable

1.定义 序列化事对象转为字节序列的过程 反序列化字节序列转为对象的过程

2.在开发中我们经常会遇到这样的问题,activity或者fragment传值的时候一般做法时用intent或者bundle中,或者需要将一些对象数据存在本地,这时候如果对象没有实现parcelable或者serilizable,就无法完成上述操作,那么这两者为我们做了什么呢?简单的说将对象转化可传输的二进制流(二进制序列)的过程我们称之为序列化,我们可以通过序列化(对象转化为可传输二进制流或者序列)以及反序列化(二进制流或者序列转化为对象)实现对象的网络传输或进程传输以及存储!

3.Parcelable和Serializable的区别和比较

Parcelable和Serializable都是实现序列化并且都可以用于Intent间传递数据,Serializable是Java的实现方式,可能会频繁的IO操作,所以消耗比较大,但是实现方式简单 Parcelable是Android提供的方式,效率比较高,但是实现起来复杂一些 , 二者的选取规则是:内存序列化上选择Parcelable, 存储到设备或者网络传输上选择Serializable(当然Parcelable也可以但是稍显复杂)

选择序列化方法的原则

1)在使用内存的时候,Parcelable比Serializable性能高,所以推荐使用Parcelable。

2)Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC,如果是仅仅在内存中使用,比如activity、service之间进行对象的传递,强烈推荐使用Parcelable。

3)Parcelable不能使用在要将数据存储在磁盘上的情况,因为Parcelable不能很好的保证数据的持续性在外界有变化的情况下。尽管Serializable效率低点,但此时还是建议使用Serializable 。

七、getheight() 和getMeasureHeight()有什么区别

getMeasureHeight() 在messure()方法结束后能获取到,而getHeight()需要在layout()执行后能获取到值;

getMeasureHeight()是通过setMeasureHeight()方法设置的值,而getheight()则是通过视图的坐标点作差得到的

八、apk的打包流程和安装流程

  打包:1. appt工具打包资源文件生成R.java文件;

        2.aidl工具处理处理AIDL文件生成对应java文件

  3.javac 编译java文件生成.class文件

  4.把.class文件转化为davik支持的的.dex文件

  5.javasinger对未签名apk仅从签名

  6.zipalign对文件进行对齐处理

九、SurfaceView、TextureView、SurfaceTexture、GLSurfaceView

SurfaceView:使用双缓冲机制,有自己的 surface,在一个独立的线程里绘制,Android7.0之前不能平移、缩放

TextureView:持有 SurfaceTexture,将图像处理为 OpenGL 纹理更新到 HardwareLayer,必须开启硬件加速,Android5.0之前在主线程渲染,之后有独立的渲染线程,可以平移、旋转、缩放

SurfaceTexture:将图像流转为 OpenGL 外部纹理,不直接显示

GLSurfaceView:加入 EGL 管理,自带 GL 上下文和 GL 渲染线程

十、binder传递数据

内核刚开始只是分配了一个物理页,并且分别将这个物理页映射到进程的内核虚拟地址空间V1(修改内核空间的页表映射)和进程的用户虚拟地址空间V2(修改用户空间的页表映射)。在用户空间访问V1和在内核空间访问V2,其实都是访问的是同一个物理内存块,从而实现进程的内核和用户空间共享同一块物理内存的目的。这样binder驱动在内核空间,将一段数据拷贝到这个物理页,则该进程的用户空间则不需要copy_to_user()即可以同步看到内核空间的修改,并能够访问这段物理内存

ProcessState 对应于一个进程,是进程内单例,而 IPCThreadState 对应于一个线程,是线程单例(Thread Local)。ProcessState 中打开了 binder 驱动、进行 mmap 映射,虽然调用了 ioctl() 函数,但主要是一些初始化配置。而具体的 BR_TRANSACTION 等命令都是由 IPCThreadState 负责执行的,当上层传来一个命令,会调用它的 transact 函数

ProcessState 的构造函数初始化了一些重要的变量,包括调用 open_driver() 打开 binder 设备,初始化 binder 线程最大数量,将 BINDER_VM_SIZE (接近 1M ) 的内存与 binder 驱动 mmap.

除了 ProcessState 的初始化,ProcessState 中还有一些比较重要的方法,比如 getStrongProxyForHandle()、getWeakProxyForHandle() 等,可以通过 handle 值获取对应 IBinder 对象

十一、IntentService

IntentService 是继承于 Service 并处理异步请求的一个类,在 IntentService 内有一个工作线程来处理耗时操作,启动 IntentService 的方式和启动传统 Service 一样,同时,当任务执行完后,IntentService 会自动停止,而不需要我们去手动控制。另外,可以启动 IntentService 多次,而每一个耗时操作会以工作队列的方式在IntentService 的 onHandleIntent 回调方法中执行,并且,每次只会执行一个工作线程,执行完第一个再执行第二个,以此类推。

十二、Fragment

1、FragmnetPageAdapter在切换页面时,只是将Fragment进行分离,适合页面较少的Fragment使用以保存一些内存,对系统内存影响不大。

2、FragmentPageStateAdapter在每次切换页面的时候,是将Fragment进行回收,适合页面较多的Fragment使用,这样就不会消耗更多的内存

3、commit/commitAllowingStateLoss两者都可以提交fragment的操作,唯一的不同是第二种方法,允许丢失一些界面的状态和信息,几乎所有的开发者都遇到过这样的错误:无法在activity调用了onSaveInstanceState之后再执行commit(),这种异常时可以理解的,界面被系统回收(界面已经不存在),为了在下次打开的时候恢复原来的样子,系统为我们保存界面的所有状态,这个时候我们再去修改界面理论上肯定是不允许的,所以为了避免这种异常,要使用第二种方法。

4、add/replace  add方式来进行show和add,这种方式你切换fragment不会让fragment重新刷新,只会调用onHiddenChanged(boolean isHidden)。而用replace方式会使fragment重新刷新,因为add方式是将fragment隐藏了而不是销毁再创建,replace方式每次都是重新创建。

十三.handler  

详见 handler、 looper 和messagequeue - 简书

上一篇下一篇

猜你喜欢

热点阅读