Android面试题整理
2018-09-18 本文已影响299人
gybguohao
Android方面:
1、View事件分发机制
2、资源加载原理
3、内存优化(内存泄漏、内存溢出),如何查找内存泄漏,如何优化
内存泄露:是指程序在申请内存后,无法释放本应该被回收的内存。内存泄露的堆积就会导致内存溢出。
内存溢出:是指程序在申请内存时,没有足够的内存空间供其使用。
查找内存泄露:
1、使用AS自带的内存分析界面,可以查看内存使用情况(蓝色区域表示程序使用的内存,灰色区域表示空闲内存)
内存泄露的原因以及解决方法:
1、静态的上下文,比如static Context context,应该尽量避免静态上下文的使用;
2、未取消注册或者监听,比如BrocastReceiver和EventBus需要在Activity的onDestroy中取消注册;
3、资源没有关闭或者释放,比如IO、File流或者Cursor在操作完成后没有及时关闭;
4、耗时任务或者属性动画没有取消,比如属性动画需要在Activity的onDestroy中取消;
5、WebView造成内存泄露,WebView也需要在在Activity的onDestroy中调用它的destroy()方法
4、Activity启动模式及区别
有以下四种启动模式:standard、singleTop、singleTask、singleInstance。
1、standard标准模式
也是系统默认模式。
在该模式下,每次启动一个Activity都会创建一个新的实例,不管这个实例是否已经存在。并且谁启动了这个
Activity,那么这个Activity就运行在启动它的那个Activity所在的栈中。比如OneActivity启动了
TwoActivity(TwoActivity是标准模式),则TwoActivity就会进入到OnaActivity所在的栈中。
2、singleTop栈顶模式
具体分为以下两种情况:
2.1、如果被启动的Activity已经存在且位于栈顶,那么该Activity就不会重建,而是调用其onNewIntent()
方法重用该实例。
2.2、如果被启动的Activity已经存在但不是位于栈顶,那么就会重新创建该Activity的实例。
3、singleTask栈内模式
具体分为以下三种情况:
3.1、如果被启动的Activity所需的任务栈不存在,就会先创建该Activity所需的任务栈,然后再创建该
Activity的实例并将它加入到任务栈中。
3.2、如果被启动的Activity所需的任务栈存在且该任务栈中不存在该Activity的实例,就会创建该Activity
的实例并将它加入到任务栈中。
3.3、如果被启动的Activity所需的任务栈存在且该任务栈中存在该Activity的实例。则有以下两种情况:
3.3.1、如果该Activity位于任务栈的栈顶,则该Activity不会重建,系统会调用其onNewIntent()方法
重用该实例;
3.3.2、如果该Activity不是位于任务栈的栈顶,那么该Activity不会重建,系统会调用其
onNewIntent()方法重用该实例并且清除任务栈中该Activity之上所有的Activity实例。
4、singleInstance单例模式
该模式具备singleTask所有特性。
具体分为以下两种情况:
4.1、如果被启动的Activity不存在,系统先会创建一个全新的任务栈,然后再创建该Activity实例并将它加入
到新的任务栈栈顶。
4.2、如果被启动的Activity已经存在,那么无论它位于哪个应用程序,哪个任务栈,系统都会把该Activity
所在的任务栈切换前台,从而使该Activity显示出来。
注意:采用singleInstance加载模式的Activity总是位于任务栈栈顶,并且该任务栈中只包含该Activity实例。
5、进程保活方法
1、提高进程的优先级
2、利用广播拉活Activity
6、Activity启动过程
7、动画
1、Android的动画可以分为三种:View动画、帧动画、属性动画。
8、UI(同一张图片放在不同dpi中,加载内存的大小有何不同)
9、Handler的原理,手写两个子线程Handler通信,loop wait时如何唤醒的
Handler原理:
Handler主要是由Looper和MessageQuene构建消息的处理。Looper负责循环从MessageQuene中读取消息,
如果有就交给Handler处理,如果没有就一直等待。MessageQuene负责消息的插入和读取,里面维护了一个
单向链表,根据消息的执行时间排列(即最先被执行的在链表前面)。
两个子线程Handler通信
public Handler mHandler;
new Thread(new Runnable(){
@Override
public void run(){
Looper.prepare();
mHandler = new Handler(){
@Override
public void handleMessage(Message msg){
super.handleMessage(msg);
Log.e("gybguohao","msg.obj = " + msg.obj);
}
}
Looper.loop();
}
}).start();
new Thread(new Runnable(){
@Override
public void run(){
Message msg = mHandler.obtainMessage();
msg.obj = "这是从第二个子线程过来的消息";
mHandler.sendMessage(msg);
}
}).start();
10、Context类
11、热更新
12、WebView优化,及H5的js调用
WebView的优化:
1、第一次打开WebView时间过长,可以在合适的时候创建一个全局的WebView供所有页面使用;
2、设置WebView的文件缓存,加快加载速度。
H5的js调用:
1、首先设置允许与JS交互webSettings.setJavaScriptEnabled(true);
2、添加交互接口mWebView.addJavascriptInterface(new JsCommunicate(), "App");
3、在JsCommunicate类中通过注解@JavascriptInterface定义具体的交互方法;
4、js端通过window.App.方法名()调用对应的方法。
13、性能优化
1、应用启动速度优化,避免在自定义Application类中做过多耗时的操作;
2、减少布局多层嵌套,避免GPU过度绘制,使用merge减少布局嵌套;
3、尽量减少APK的大小,删除无用代码和资源,重复布局尽量采用include;
4、内存优化,及时释放内存,避免内存泄露;
5、避免无用的网络请求,给用户造成流量的浪费。
14、Binder机制
Android Binder是用来做进程通信的,Android的各个应用以及系统服务都运行在独立的进程中,它们的通信都依赖
于Binder。
15、65536是怎么回事?如何解决
表示方法数超过了65536个
解决方法:
1、在项目的build.gradle文件下defaultConfig闭包中配置multiDexEnabled true
2、在dependencies下添加依赖implementation 'com.android.support:multidex:1.0.0'
3、在自定义的Application类中重写attachBaseContext方法,并且执行MultiDex.install(this)代码。
@Override
public void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
16、LruCache原理
LruCache的全称是Least Recently Used,也就是最近最少使用算法。
17、断点续传
18、主线程Lopper为什么不会ANR
因为Android是由事件驱动的,looper.loop() 不断地接收事件、处理事件,每一个点击触摸或者说Activity的
生命周期都是运行在Looper.loop()的控制之下,如果Looper停止了,应用也就停止了。只能是某一个消息或者
说对消息的处理阻塞了 Looper.loop(),而不是 Looper.loop()阻塞它。
19、AIDL
Java方面:
1、线程安全、多线程通信、同步方式
线程安全:
在多线程环境下对共享资源的访问,保证此共享资源的一致性。
线程安全问题多是由全局变量和静态变量引起的,
当多个线程对共享数据只执行读操作,不执行写操作时,一般是线程安全的;
当多个线程都执行写操作时,需要考虑线程同步来解决线程安全问题。
多线程通信:
1、全局变量
2、Handler
3、AsyncTask
4、EventBus
同步方式:
1、使用synchronized关键字(同步方法和同步代码块)
2、线程池的原理
3、进程间通信方式
1、Binder
2、Intent
3、ContentProvider
4、AIDL
4、进程和线程区别
一个程序至少一个进程,一个进程至少一个线程。
进程是系统进行资源分配和调度的最小单位,比如分配内存。
线程是程序执行的最小单位,可以与同属一个进程的其他的线程共享该进程所拥有的全部资源。
5、sleep和wait区别
1、sleep是Thread类的方法,wait是object类的方法;
2、sleep方法没有释放锁所以不能调用里面的同步方法。而wait方法释放了锁,使得其他线程可以调用里面的同步方法。
4、sleep必须捕获异常,而wait不需要捕获异常
6、堆和栈的区别
1、堆中存储的是对象
2、栈中存储的是局部变量
7、final的作用
final可以修饰类、方法、变量(包括成员变量和局部变量)
1、final修饰类,表示这个类不能被继承,并且该类中所有的成员方法会被隐式的指定为final方法;
2、final修饰方法,该方法在子类中不能重写,只能继承;
3、final修饰变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,
则在对其初始化之后便不能再让其指向另一个对象
8、简单描述封装、继承、多态
1、封装:将事物的属性和实现细节隐藏起来,只对外提供访问和修改接口(例如set和get方法);
2、继承:当多个类具有相同的属性和方法时,可以将相同部分抽取出来放到一个类中作为父类,其他的类继承这个父类。
3、多态:方法的重写和重载是Java多态性的不同表现。
重写是父类与子类之间多态性的一种表现,重载是一个类中多态性的一种表现。
9、==和equals的区别
==:
1、对于基本数据类型比较的是它们的值是否相等;
2、对于对象比较的是它们是否是同一个对象(即内存地址是否相同);
equals:
1、equals比较的是值是否相等
10、java基本数据类型和区别
基本数据类型:
1、byte:Java中最小的数据类型,1个字节,取值范围-128~127,默认值0
2、short:短整型,2个字节,取值范围-32768~32767,默认值0
3、int:整型,4个字节,取值范围-2147483648~2147483647,默认值0
4、long:长整型,在内存中占64位,即8个字节-2^63~2^63-1,默认值0L
5、float:浮点型,4个字节,与double的区别在于float类型有效小数点只有6~7位,默认值0
6、double:双精度浮点型,8个字节,默认值0
7、char:字符型,2个字节,取值范围0~65535,默认值为空
8、boolean:布尔类型,占1个字节,用于判断真或假,默认值false
引用数据类型:
1、类、接口类型、数组类型、枚举类型、注解类型。
区别:
1、基本数据类型在被创建时,在栈上给其划分一块内存,将数值直接存储在栈上。
2、引用数据类型在被创建时,首先要在栈上给其引用分配一块内存,而具体对象存储在堆内存上,
然后由栈上面的引用指向堆中对象的地址。
11、如何实现一个死锁
12、synchronized的实现原理
13、volatile的实现原理
Java内存模型三大性质:可见性、原子性和有序性。
1、可见性:是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的,volatile修饰的变量不允许
线程内部缓存和重排序,即直接修改内存,所以对其他线程是可见的。但是这里需要注意一个问题,volatile
只能让被它修饰内容具有可见性,但不能保证它具有原子性。
2、原子性:原子是世界上的最小单位,具有不可分割性。
3、有序性:Java 语言提供了 volatile 和 synchronized 两个关键字来保证线程之间操作的有序性,
volatile 是因为其本身包含“禁止指令重排序”的语义,synchronized 是由“一个变量在同一个时刻
只允许一条线程对其进行 lock 操作”这条规则获得的,此规则决定了持有同一个对象锁的两个同步块只能串行执行。
volatile变量用来确保将变量的更新操作通知到其他线程。当把变量声明为volatile类型后,编译器与运行时都会
注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器
或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。
在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized
关键字更轻量级的同步机制。
声明变量是 volatile 的,JVM 保证了每次读变量都从内存中读,跳过 CPU cache 这一步。
14、子类重写父类方法,返回值和参数有什么要求
框架搭建:
1、MVC和MVP的区别
1、MVC中无法区分Activity到底是V还是C,因为它即承担了界面的显示,又负责一部分逻辑的处理;
2、MVP中V和M不存在直接联系,而是通过P来交互。P和V以及P和M之间通过接口来交互,从而降低了代码的耦合度。
2、手写MVP框架(用户登录)
public class Activity extends AppCompatActivity implements LoginView {
private LoginPresenter mLoginPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mLoginPresenter = new LoginPresenter(this);
// 请求登录
mLoginPresenter.login("18855586110", "123456");
}
@Override
public void loginSuccess(LoginModel model) {
}
@Override
public void loginFail(int code, int msg) {
}
}
public interface LoginView() {
void loginSuccess(LoginModel model);
void loginFail(int code, int msg);
}
public class LoginPresenter {
public LoginView mView;
public LoginPresenter(LoginView view) {
this.mView = view;
}
public void login(String phone, String password) {
NetClient.getInstance().login(new BaseCallback<LoginModel>() {
@Override
public void onSuccess(LoginModel model) {
mView.loginSuccess(model);
}
@Override
public void onFail(int code, int msg) {
mView.loginSuccess(code, msg);
}
})
}
}
3、MVVM框架
Android的MVVM框架主要是通过DataBinding这个库实现
设计模式:
1、用到的设计模式都有哪些
常用的设计模式有:单例模式、Builder、工厂模式、策略模式、观察者模式、等等
2、实现单例模式都有哪几种方式
有以下几种方法:饿汉、懒汉、Double checked locking、静态内部类
饿汉模式
public class Singleton {
private static final Singleton mInstance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return mInstacen;
}
}
懒汉模式
public class Singleton {
private static Singleton mInstance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (mInstance == null) {
mInstance = new Singleton();
}
return mInstacen;
}
}
3、手写单例模式
双重加锁
public class Singleton{
private volatile static Singleton mInstance = null;
private Singleton(){
}
public static Singleton getInsatnce(){
if (mInstance == null){
synchronized(Singleton.class){
if(mInstance == null){
mInstance = new Singleton();
}
}
}
return mInstance;
}
}
静态内部类
public class Singleton{
private Singleton(){
}
public static Singleton getInsatnce(){
return SingletonHolder.mInstance;
}
private static class SingletonHolder{
private static final Singleton mInstance = new Singleton();
}
}
4、装饰模式
三方框架:
1、用到的三方框架都有哪些
1、Butterknife
2、EventBus
3、Retrofit
4、OkHttp
5、Glide
2、OkHttp的原理
3、Picasso缓存机制
算法方面:
1、冒泡排序
假设无序数组为[6, 1, 2, 7, 9, 3, 4, 5, 10, 8]
当i = 0时,进行第一次内部比较
1、第一次循环后的结果是[1, 6, 2, 7, 9, 3, 4, 5, 10, 8]
2、第二次循环后的结果是[1, 2, 6, 7, 9, 3, 4, 5, 10, 8]
3、第三次循环后的结果是[1, 2, 6, 7, 9, 3, 4, 5, 10, 8]
4、第四次循环后的结果是[1, 2, 6, 7, 9, 3, 4, 5, 10, 8]
5、第五次循环后的结果是[1, 2, 6, 7, 3, 9, 4, 5, 10, 8]
6、第六次循环后的结果是[1, 2, 6, 7, 3, 4, 9, 5, 10, 8]
7、第七次循环后的结果是[1, 2, 6, 7, 3, 4, 5, 9, 10, 8]
8、第八次循环后的结果是[1, 2, 6, 7, 3, 4, 5, 9, 10, 8]
9、第九次循环后的结果是[1, 2, 6, 7, 3, 4, 5, 9, 8, 10]
......
当i = 1时,进行第一次内部比较
int number[] = new int[10]{6, 1, 2, 7, 9, 3, 4, 5, 10, 8};
int temp = 0;
for (int i = 0; i < number.length - 1; i++) {
for (int j = 0; j < number.length - i - 1; j++) {
if (number[j] > number[j + 1]) {
temp = number[j];
number[j] = number[j + 1];
number[j + 1] = temp;
}
}
}
2、快速排序
3、两个栈实现一个队列
思路:因为队列是先进先出,而栈是先进后出
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
// 入列
public void push (int node) {
stack1.push(node);
}
// 出列
public int pop () {
if (stack2.size() <= 0) {
while (stack1.size() > 0) {
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
4、斐波那契数列
斐波那契数列通项公式:
f(1) = 1;
f(2) = 1;
f(n) = f(n - 1) + f(n - 2) ( n >= 3)
public int Fibonacci (int n) {
if (n == 0) {
return 0;
}
if (n == 1 || n == 2) {
return 1;
}
// f(n)
int a = 0;
// f(n - 1)
int b = 1;
// f(n - 2)
int c = 1;
for (int i = 3; i <= n; i++) {
a = b + c;
c = b;
b = a;
}
return a;
}
5、二叉树广度优先遍历
6、二叉树根节点到叶节点的和
网络编程:
1、Http、Https区别
Https是在Http的基础上加了SSL层,SSL层负责对传输的数据进行加密
1、Http是明文传输,Https对传输的数据进行了加密
2、Http使用80端口连接,Https使用443端口连接
3、Https需要申请证书
2、Https的原理
3、post、get方式的区别
4、三次握手过程
数据加密:
1、加密方式
1、MD5:MD5是不可逆的加密算法,也就是无法解密,主要用于客户端的用户密码加密。
2、SHA1
3、AES
4、RSA:RSA算法在客户端使用公钥加密,在服务端使用私钥解密。这样即使加密的公钥被泄露,没有私钥仍然无法解密。
2、对称加密和非对称加密
对称加密:加密解密用同一个密钥。
非对称加密:公钥加密,私钥解密。例子:RSA
数据结构:
1、ArrayList和LinkedList的区别及各自的使用场景
ArrayList内部采用数组的形式存储数据,所以更适合需要大量进行随机访问的场景(即改查操作);
LinkedList内部采用双向链表的形式存储数据,所以更适合需要对集合进行元素的增加和删除的场景(即增删操作)。
2、HashMap的底层数据结构,及原理
HashMap是由数组+链表+红黑树组成的。根据key的hash值决定元素所在数组中的位置,数组里面保存的是由每个
元素组成的单向链表,当链表的长度超过8时,会转换成红黑树的形式(提高元素的查找效率)。
3、String、StringBuffer、StringBuilder的区别
1、执行速度。执行速度的快慢为:StringBuilder > StringBuffer > String。
2、线程安全。StringBuilder是线程不安全的,而StringBuffer是线程安全的。因为StringBuffer中很多方法带有
synchronized关键字,可以保证是线程安全的。所以在多线程情况下建议使用StringBuffer,确保线程安全,但是在
单线程的情况下,建议使用StringBuilder,因为执行速度更快。
3、HashMap、HaseSet、HashTable、ConcurrentHashMap、LinkHashMap的区别
1、HaseMap储存是无序不重复的,允许key和value为null(如果key为null,则会在代码里面将null保存为0),是
线程不安全的。
2、HashSet内部其实是用的HashMap作为数据的保存,储存的只是map的key,默认value为null(所以HashSet也是
无序不重复的,也允许key为null的,也是线程不安全的)
3、HashTable储存无序不重复的,不允许key和value为null,通过synchronized关键字保证线程安全
4、ConcurrentHashMap储存无序不重复的,不允许key和value为null,通过volatile关键字保证线程安全
5、LinkHashMap继承至HashMap,通过一个双向链表保存了元素的储存顺序(所以LinkHashMap也允许key为null的,也是线程不安全的)。
Kotlin方面:
1、const和val的区别
const只能修饰val,不能修饰var。
const val的可见性是public final static,可以直接方法;
val的可见性是private final static,并且val会生成getNormalObject(),通过方法调用。
2、kotlin闭包
3、run、apply、let、also、with的用法和区别
Linux方面:
1、修改权限的命令
chmod 777
r表示read,读取权限,代表数字4
w表示write,写入权限,代表数字2
x表示execute,执行权限,代表数字1