、主要分为以下几部分:
(1)java面试题
(2)Android面试题
(3)高级开发技术面试题
(4)跨平台Hybrid 开发
一、java面试题
熟练掌握java是很关键的,大公司不仅仅要求你会使用几个api,更多的是要你熟悉源码实现原理,甚至要你知道有哪些不足,怎么改进,还有一些java有关的一些算法,设计模式等等。
(一) java基础面试知识点
java中==和equals和hashCode的区别
equals是Object的方法可以重写 ,仅仅用来判断两个对象的值
==是操作符,主要用来判断两个对象的地址
hashCode用来获取对象的哈希码,用于确定对象在哈希表中的位置。
int、char、long各占多少字节数
一个字节占8位:
char:1个字节,8位
short:2字节,16位
int:4个字节,32位
long:8个字节, 64位
int与integer的区别
int是值类型,Integer是引用类型
Integer必须实例化后才能使用,而int不需要
int默认是0,Integer默认是null
探探对java多态的理解
多态的定义:同一消息可以根据发送对象的不同而采用多种不同的行为方式
多态的好处:消除类型之间的耦合关系
多态的条件:要有继承,要有重写,父类引用指向子类对象
String、StringBuffer、StringBuilder区别
1:是否从可变的角度
String是final类既不允许继承也不允许改变对象
StringBuffer、StringBuilder 都继承AbstractStringBuilder,没有final修饰,对象可变
2、从线程安全角度来看
String因为被final修饰因此不可变,自然也就是线程安全的
StringBuffer 内部append方法添加了同步锁synchronized ,因此它也是线程安全的
StringBuilder内部append方法没有同步锁,因此它不是线程安全的
什么是内部类?内部类的作用
定义:可以把一个类定义到另一个类的内部,当作外部类的成员,这个类就叫做内部类
作用:根据内部类的分类说一下各个内部类的作用
成员内部类:可以访问外部类的所有方法、属性、甚至包括私有方法属性。
局部内部类:定义在某个方法或者一个作用域里面的类,只能访问该方法或者作用域里面的对象
静态内部类:不依赖于外部类而存在,只能访问外部类的静态成员
匿名内部类:无名内部类,常见于定义控件事件中
抽象类和接口区别
默认方法实现:抽象类可以有默认方法实现,接口没有
访问修饰符:抽象类public、protected;接口只能public
构造器:抽象类可以有构造器,接口没有
抽象类的意义
1、为子类提供一个公共的类型
2、封装子类中重复的方法和属性
3、定义有抽象方法,子类虽然有不同的实现,但该方法的定义是一致的
抽象类与接口的应用场景
如果你拥有一些方法并且想让它们中的一些有默认实现,那么使用抽象类吧。
如果你想实现多重继承,那么你必须使用接口。由于Java不支持多继承,子类不能够继承多个类,但可以实现多个接口。因此你就可以使用接口来解决它。
如果基本功能在不断改变,那么就需要使用抽象类。如果不断改变基本功能并且使用接口,那么就需要改变所有实现了该接口的类
抽象类是否可以没有方法和属性?
可以
接口的意义
使用接口可以达到一定的解耦和作用,比如:我们在接口中定义一个变量,所有继承它的子类,都用到这个变量,那么当需求改变时只需要修改着一个变量即可;
解决java中不能多继承的缺点
泛型中extends和super的区别
extends T>限定参数类型的上界:参数类型必须是T或T的子类型
super T> 限定参数类型的下界:参数类型必须是T或T的超类型
父类的静态方法能否被子类重写
父类的静态方法可以被子类继承,但是不能被重写
进程和线程的区别
进程是指一个执行单元,一般指一个程序或者应用
线程是cpu调度的最小单元,主要用于执行耗时操作,一个进程可以有多个线程
final,finally,finalize的区别
final:修饰符,使用final修饰的类、方法、属性不能被继承、重写和改变变量的值
finally:作为异常处理的一部分,通常配合try catch一起使用,标识最终被执行的意思
finalize:Object的一个方法,意为假定该对象被回收,什么意思呢?我们先了解一下java中垃圾回收器(GC)的工作原理,我们都知道java中的GC只回收JVM分配的内存,如果对象使用jni底层c、c++来进行分配的内存那么finalize就是负责回收这部分内存的,在GC工作时先调用finalize()方法,并在下一次垃圾回收发生,才真正回收该对象占用的内存。
Serializable 和Parcelable 的区别
Serializable Parcelable
平台: java自带 Android自带
序列化原理: 将对象转化为可存储的状态 将对象进行分解,分解的部门都是传递可支持的数据类型
优缺点: 简单但是效率低 高效但是使用复杂
静态属性和静态方法是否可以被继承?是否可以被重写?以及原因?
可以被继承,但是不能被重写,静态属性和方法只属于当前类。通过类名.属性,类名.方法()调用
静态内部类的设计意图
非静态内部类在创建的时候会持有外部的引用,而静态内部类没有
因此非静态内部类有两个重要的作用:
1、它的创建不需要外围,不支持外部类的引用
2、静态内部类不能访问外部任何非静态属性和方法
谈谈对kotlin的理解
1、kotlin是一门静态语言,支持多种平台 移动端、服务端、以及浏览器端
2、空安全的语言,支持泛型,空指针的判断,支持与java进行完全的交互
3、代码末尾没有分号、被调用的方法放在上边等特点
闭包和局部内部类的区别
闭包是一个可调用的对象,它包含了一些信息,这些信息来自创建它的作用域,就是我们常见的lambda 表达式()=>{ 作用域 };
局部内部类是定义在某个方法或者某个局部中的类,它只能访问该方法中的变量
string 转换成 integer的方式及原理
1、循环调用string的每个字符的十进制数值
2、通过*=或/=进行计算拼接
3、最后判断是否为正负值,返回结果
(二) java深入源码级的面试题(有难度)
哪些情况下的对象会被垃圾回收机制处理掉?
GC确定哪些对象回收有两种算法:
1、引用计数法
对象头处维护了一个引用计数去counter一个对象被引用一次计数器就会+1,不会引用就会-1,为0的时候GC工作的时候就会被回收,但是它不能解决循环引用带来的问题
2、可达性算法
java默认的垃圾回收算法,虚拟机会先将一些对象定义为 GC Roots,沿着GC Roots向下寻找,如果对象不能通过GC Roots寻找到,那么虚拟机就认为该对象是被回收的
讲一下常见编码方式?
ASCII:我理解为中文中的GB2312,使用8为二进制来表示字母和字符
UTF-8:英文一个字符一个字节,中文占用3/4字节
UTF-16:定长编码,所有字符占两个字节
utf-8编码中的中文占几个字节;int型几个字节?
中文少数占3个字节,多数占4个字节,int型数字占1个字节
静态代理和动态代理的区别,什么场景使用?
静态代理:在程序运行前已经存在的,提前写好的。代理类和委托类的关系提前确定好的。
优点:业务只关注业务本身,专一性
缺点:代理对象只能服务一种类型的对象,代理需要新的方法,每个委托都要实现
动态代理:源码在运行期间JVM根据反射等机制动态的生成
Java的异常体系
最近在看java编程思想,根据自己编码经历,谈谈对异常的理解:
首先我们都知道java每个对象都有一个异常的超类Throwable,它由两个子类Error(错误)和Exception(异常)
Error:严重的不可处理的异常,会导致程序直接挂掉,比如内存溢出、类定义错误等
Exception:可处理异常,可以捕获并处理,比如对象空指针,文件找不到路径等
运行时异常:时在程序运行前编译前检查语法错误来提醒
编译时异常:主要是程序在运行后出现的逻辑错误或者内存溢出而导致的异常,
1、除非你能解决处理异常,否则不要捕获它,如果是记录错误消息,别忘了把它在抛出去
2、异常即代表一种错误 也代表一种消息
Java中实现多态的机制是什么?
多态的实现有三个必要条件:
继承:再多态中必须有继承关系的子类、父类
重写:子类重写父类中的方法,调用这些方法就会调用子类的方法
向上转型:将子类的引用赋给父类,这样可以调用父类中的方法,和父类中没有,子类中有的方法
如何将一个Java对象序列化到文件里?
FileOutputStream fileOutputStream = new FileOutputStream("d://test.out");
ObjectOutputStream objectOutputStream= new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject("object");
objectOutputStream.close();
说说你对Java反射的理解
定义:反射就是程序运行时动态的获取类和对象的方法或者属性
作用:通过反射,我们可以获取相应的属性和方法或者资源,比如我们在程序中想获取第三方的资源文件,就不能通过R.id的方式获取,必须通过反射才能拿到资源文件
使用:可以通过对象.getClass()、类.class、Class.forName("")
说说你对Java注解的理解
定义:注解也称为元数据,是一种代码的级别说明,声明在包、类、字段、方法、局部变量、方法参数等前面,来对这些元素进行说明,注释等
作用:
1)编写文档:通过代码里的标识的元数据生成文档【生成文档doc文档】
2)代码分析:通过代码里的标识的元数据对代码进行分析【使用反射】
3)编译检查:通过代码里的标识的元数据让编译器能过实现基本的编译检查
java提供了四种元注解:
1.@Target,:规定注解所修饰的对象范围
2.@Retention:表示注解的生命周期
3.@Documented:用于描述
4.@Inherited:主要说明了一种继承性,子类可以继承父类注解
说说你对依赖注入的理解
我理解的依赖注入是你不需要关心对象的生命周期,什么时候调用、销毁等过程,只需要关注,调用它的外部类即可:
代码举例说明:对象注入、属性注入
说一下泛型原理,并举例说明
泛型的本质是参数化类型,它可以用在接口,方法和类中,泛型接口、泛型方法、泛型类
比如List
: 1、它的好处就是在编译时期就帮我们检查类型是否安全,不需要运行,并且所有的强转都是自动和隐士的,效率高
Java中String的了解
String为什么要设计成不可变的?
final修饰,不可被继承,不可被重写,类型安全,
Object类的equal和hashCode方法重写,为什么?
1、如果两个对象的equal相同,那么hashCode一定相同,
2、如果hashCode相同。equal并不一定相同
如果equal重写,hashCode没有被重写,可能会导致两个没有关系的对象equal相同。
(四) 线程、多线程和线程池
开启线程的三种方式?
继承Thread类,调用run()或start()方法
实现Runnable接口,调用run()方法 没有返回值,不能进行容错处理
Callable接口实现类,调用call()实现 有返回值,并且能容错处理,抛出错误信息
线程和进程的区别?
进程是程序的执行单元,一般指一个应用或程序
线程是cpu调度的最小单元,是一种有限的系统资源,分为UI线程和子线程
一个进程可以有多个线程。
为什么要有线程,而不是仅仅用进程?
进程的缺点:
进程在同一时间只能干一件事,如果同时干多件事就无能为力
进程在处理任务一旦遇到阻塞,当前程序就会挂起,下一步操作就无法继续进行
线程的优点:
减少程序的响应时间,同时执行多个耗时操作,充分利用系统资源,避免闲置浪费。
run()和start()方法区别
我们首先来了解一下线程的五个状态:
创建:new Thread(new Runnable);这是线程的创建阶段,等待被执行
就绪:调用线程的start()方法,当前线程就会告知虚拟机我已准备就绪,由JVM进行调度
运行:线程会调用实现runnable的run()方法,此时线程才真正执行
阻塞:执行过程中的暂停等操作。sleep
死亡:run方法执行完毕,线程死亡
总结:start()方法只是让线程处于就绪状态,告诉cpu我已准备好了,请开始执行,然后调用run方法按顺序执行。
如何控制某个方法允许并发访问线程的个数?
java中有一个叫做信号量的类Semaphore,负责控制线程的载入、释放以及最大并发访问的个数,只需要在构造函数中传入一个最大的并发数就可以。
在Java中wait和sleep方法的不同;
sleep:睡眠,一直持有锁,事件过后任务继续执行,Thread方法
wait:等待,会释放锁,需要notify()唤醒才能继续持有,Object方法
什么导致线程阻塞?
Thread.sleep() 线程睡眠
Object.wait() 线程等待
Thread.yeild() 线程礼让
Thread.join() 线程自闭
线程如何关闭?
通常情况下线程运行完毕会自动结束,但是有时候需要提前让用户取消操作等。
1、通过退出标识,自定义一个isFlag标识,在Run方法中进行判断
2、 线程提供interrupted()方法判断线程是否已经中断来停止
讲一下java中的同步的方法
java允许多线程并发操作,在多线程并发中同时操作一个可共享的变量是容易造成数据不准确,比如数据库增删查改,java引入同步来保持数据的一致性final:变量的唯一性,不可变synchronized:作用域代码块,方法,通过线程互斥,同一时间只允许一个线程操作。Volatile:修饰变量变化保证立即对线程可见
数据一致性如何保证?
java允许多线程并发操作,在多线程并发中同时操作一个可共享的变量是容易造成数据不准确,比如数据库增删查改,java引入同步来保持数据的一致性
final:变量的唯一性,不可变
synchronized:作用域代码块,方法,通过线程互斥,同一时间只允许一个线程操作。
Volatile:修饰变量变化保证立即对线程可见
如何保证线程安全?
java中死锁和脏数据就是典型的线程安全问题,只有存在共享数据时才需要考虑线程安全问题
synchronized:作用域代码块,方法,通过线程互斥,同一时间只允许一个线程操作。Volatile:修饰变量变化保证立即对线程可见
如何实现线程同步?
三种方式:
synchronized 同步代码块,同一时间只允许一个线程操作
Lock,手动获取锁,释放锁
synchronized 通过方法。
两个进程同时要求写或者读,能不能实现?如何防止进程的同步?
线程间操作List
多线程操作list容易引起并发操作导致数据的不一致性,
Java中对象的生命周期
1. 创建阶段(Created) New一个对象,有jvm分配内存
2. 应用阶段(In Use) 至少被一个强引用持有
3. 不可见阶段(Invisible) 是指该对象不在当前作用域中被访问,编译直接报错
4. 不可达阶段(Unreachable) 不再持有,但是会被静态变量或者jni底层引用
5. 收集阶段(Collected)
6. 终结阶段(Finalized) 对象运行完finalize()等待被回收
7. 对象空间重分配阶段(De-allocated) 对象彻底消失了
Synchronized用法
Synchronized(this){代码块}:其他想要访问此处代码时,会被阻塞
public syncchronized void method(){...} 修饰一个方法,表示其他任务访问该方法会被阻塞
synchronize的原理
Java对象头和monitor是实现synchronized的基础
对象头:通过对象头来确定对象是哪个类的实例
monitor:对象监视器,用来监视对象状态,它是一种同步机制
谈谈对Synchronized关键字,类锁,方法锁,重入锁的理解
Synchronized是同步的一种机制,主要用来解决并发访问同一对象所造成的安全问题。
Synchronized 修饰静态方法获取到的就是类锁,修饰整个类
Synchronized 修饰代码块修饰一个方法获取到的就是方法锁,修饰当前方法
冲入所:子类继承父类的方法,子类父类都有方法锁,
static synchronized 方法的多线程访问和作用
static synchronized标识当前锁的整个类的静态方法,修饰的是整个类,也成为类锁。
作用:它可以对类的所有对象实例起作用,
同一个类里面两个synchronized方法,两个线程同时访问的问题
不能同步执行,多线程同时访问同一个类的两个synchronized方法时,jvm会检测到当前类对象前面的synchronized关键字,会对对象ID加锁,因此,两个线程同时访问,会等待一个执行完成才能执行另一个。
谈谈volatile关键字的作用
禁止指令重排,修饰变量变化立即对线程可见,线程每次使用的时候都会使用修改后的值
synchronized 和volatile 关键字的区别
synchronized 作用于方法,代码块,volatile只能修饰变量
synchronized 会造成线程阻塞,volatile不会
synchronized只要用于并发操作是保证数据的唯一性,volatile主要是变量变化时立马对其他线程可见。
synchronized与Lock的区别
synchronized 时java内置锁,Lock不是java内置
synchronized不需要用户去手动的释放锁,使用完后会自动释放,Lock需要用户手动释放,不手动释放会造成死锁现象
ReentrantLock 、synchronized和volatile比较
ReentrantLock的内部实现
lock原理
死锁的四个必要条件?
互斥:某个资源一次只能有一个进程访问,其他进程需要先等待
占有且等待,当前进程本身占有着一种资源,同时也需要其他进程正在访问的其他资源,需要等待
不可抢占:别人已经占有,不可使用
循环等待:
怎么避免死锁?
1、避免一个线程同时获取多个锁;
2、避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源
对象锁和类锁是否会互相影响?
类锁和对象锁不是同1个东西,一个是类的Class对象的锁,一个是类的实例的锁。也就是说:1个线程访问静态synchronized的时候,允许另一个线程访问对象的实例synchronized方法。反过来也是成立的,因为他们需要的锁是不同的
什么是线程池,如何使用?
线程池就是将任务添加到队列中顺序或并发执行的一个集合,android线程池一般有这几个参数:核心线程数,缓冲线程数,最大线程数
Java的并发、多线程、线程模型
谈谈对多线程的理解
优点:多线程可以处理耗时操作,方便多任务同时执行,比如网络操作,后台下载等。
缺点:线程是一种有限的系统资源,因此,需要避免大量线程的开销以及内存泄漏
多线程有什么要注意的问题?
多线程是一种有限的系统资源,大量的创建会导致资源消耗,尽量使用线程池
Android中容易引起内存泄漏:持有外部类的引用。
谈谈你对并发编程的理解并举例说明
cpu在同一时间只能处理一件事,因此并发看似是同时执行,实际上实在不停的切换进程
谈谈你对多线程同步机制的理解?
线程同步是一种安全机制,它主要是解决多线程同时访问统一资源导致的数据安全问题。
synchronized;volatile
如何保证多线程读写文件的安全?
多线程 同时访问同一个文件回导致数据安全问题,因此可以使用同步锁来解决这种问题。
可以使用synchronzied来保证在同一时间只能由一个线程来操作
断点续传原理及实现
断点续传分为单线程断点续传和多线程断点续传,
单线程断点续传:比较简单,至开启一个线程下载某一个文件起始位置是0-文件总大小,网络断开记住当前所下载的位置,下次下载重新定义http的Range
多线程断点续传:开启多个线程同时下载某个文件中的某一个部分,举例说明
实现:
不管是单线程还是多线程都要用到:
1、断点续传需要指定http的Range和Content-Rang
2、如果检测到网络断开则要记录当前下载的位置也就是Range,等待下次重新连接的时候,指定当前Range
二、Android面试题
Android面试题包括Android基础,还有一些源码级别的、原理这些等。所以想去大公司面试,一定要多看看源码和实现方式,常用框架可以试试自己能不能手写实现一下,锻炼一下自己。
(一)Android基础知识点
四大组件是什么
Activity:Service:BoradcastReceiver:ContentProvider:
四大组件的生命周期和简单用法
Activity之间的通信方式
1、通过Intent传递,大小限制1Mb
2、通过类静态变量
3、通过全局变量
4、通过SharedPreferences、文件等用的较少
Activity各种情况下的生命周期
横竖屏切换的时候,Activity 各种情况下的生命周期
横屏:onSaveInstanceState->onPause()->onStop->onDestroy()->onCreate()->onStart()->onRestoreInstanceState()->onResume()
竖屏:横屏*2
对android:configChanges属性,一般认为有以下几点:
1、不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行两次
2、设置Activity的android:configChanges="orientation"时,切屏还是会重新调用各个生命周期,切横、竖屏时只会执行一次
3、设置Activity的android:configChanges="orientation|keyboardHidden"时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法
Activity与Fragment之间生命周期比较
Activity:onCreate、onStart、onResume、onPause、onStop、onDestroy、onRestart
Fragment:
onAttach(与Activyty关联时调用)、 onCreate、onCreateView(创建Fragment视图时调用)、onActivityCreated(Activity 的Create方法调用时调用)
onStart、
onResume、
onPause、
onStop、
onDestroyView(Fragment视图被移除时调用)、onDestroy、onDetach(与Activity取消关联时调用)
Activity上有Dialog的时候按Home键时的生命周期
onPause()、onStop()
两个Activity 之间跳转时必然会执行的是哪几个方法?
A:onPause、B:onCreate、onStart、onResume、A:onStop
前台切换到后台,然后再回到前台,Activity生命周期回调方法。
A->B:
A:onPause、B:onCreate、onStart、onResume、A:onStop
B返回A:
B:onPause、A:onRestart、onStart、onResume、B:onStop、onDestroy
弹出Dialog,生命值周期回调方法。
A:onPause、B:onCreate、onStart、onResume
Activity的四种启动模式对比
standard:默认模式,每次都会创建一个新的页面
singleTop:栈顶模式,创建时优先检查栈顶是否存在相同的活动,有展示,没有创建
singleTask:栈内模式,创建时优先检查栈内是否存在相同的活动,有展示并清除当前活动以上所有页面,没有创建
singleInstance:单利模式,存在于单独的栈中,且只有一个实例
Activity状态保存于恢复
一般我们指的状态保存和恢复是指的非正常状态下的activity生命周期
onSaveInstanceState
onRetoreInstanceState
如何实现Fragment的滑动?
Fragment和Viewpager配合使用
给Viewpager设置setAdapter和setOnPageChangeListener即可
fragment之间传递数据的方式?
调用getFragmentManager()的findFragmentById()获取fragment对象,根据对象调用方法来实现
Activity 怎么和Service 绑定?
Activity-Intent-Service
bindService(new Intent(Activity,Service.class),)
怎么在Activity 中启动自己对应的Service?
startService(new Intent(...))
service和activity怎么进行数据交互?
通过Intent进行传值
Service的开启方式 以及Service 的生命周期
startService:
startService、onCreate、onStart、service running、onStop、onDestroy
bindService、onCreate、onBind、service running,onUnBind,onDestroy
谈谈你对ContentProvider的理解
定义:
ContentProvider它是一种数据共享性组件,用户向其他组件乃至其他应用共享数据,和广播一样无法被用户感知,他内部需要实现增删查改方法,内部维护了一个数据集合,通过数据库来实现
日常开发:自定义类继承ContentProvider,实现增删查改方法,处理好线程同步,对外实现URL来实现。
说说ContentProvider、ContentResolver、ContentObserver 之间的关系
ContentProvider 内容提供者,向外提供共享数据
ContentResolver:内容解析者,对内容提供者提供的数据进行分析
ContentObserver:内容观察者,观察内容在各个阶段的状态
请描述一下广播BroadcastReceiver的理解
1、BroadcastReceiver 时一种消息型组件,在不同组件乃至不同应用之间传递消息,无法被用户感知,因为它工作在系统内部,
2、广播有两种注册方式:静态注册和动态注册。生命周期也根据注册不同
3、广播默认运行在主线程中,不支持耗时操作。
广播的分类
有序广播:消息的照发送的顺序接收
无序广播:所有设备几乎在同一时刻接收到广播
本地广播:只能在当前应用中接收到广播
粘性广播:先发送,后注册
在manifest 和代码中如何注册和使用BroadcastReceiver?
静态注册:mainfest中
action 动态注册:regeisterReceiver(new MyReceiver(),filter);
广播发送的原理:
1、首先自定义一个广播接收者BroadcastRecevier,并重写onReceiver();
2、通过Binder机制像AMS进行注册;
3、广播发送者通过Binder机制向AMS发送广播;
4、AMS查找符合条件的广播,并发送到消息循环队列中;
5、消息循环拿到此广播并回调onReceiver()方法。
Application 和 Activity 的 Context 对象的区别
生命周期不同:
Application的Context代表的是整个应用程序的生命周期,Activity的Context代表的是当前Activity的生命周期。
Android属性动画工作原理
在一定时间间隔内,通过不断对值进行改变,并不断将该值赋给对象的属性,从而实现该对象在该属性上的动画效果
如何导入外部数据库?
1、将外部数据库放到文件目录assets中,
2、通过InputStream读取外部数据库,通过FileOutputStream导入内部数据库
3、需要注意外部数据库与我们新建的数据库属性和数据类型要一致
LinearLayout、RelativeLayout、FrameLayout的特性及对比,并介绍使用场景。
RelativeLayout 会横向,纵向进行两次测量,也就是执行两次measure,效率肯定相对底
LinearLayout 线性布局,从上往下的顺序绘制元素,如果LinearLayout 有weight属性,也会执行两次measure
FrameLayout 无法控制子元素的位置,全部堆在左上角,无法改变。
谈谈对接口与回调的理解
接口的实现很简单,
1、定义接口,编写回掉方法,给接口赋值
我们想象平时为什么需要接口
1、传值,利用接口传值,我们不关心过程只关心结果,
2、接口回调可以理解为一种设计模式,类似于观察者,程序负责项目大的时候,有利于页面之间的解耦
介绍下SurefaceView
1、SurefaceView主要在被动的情况下更新,
2、SurefaceView主要在子线程中进行,常用于平凡刷新以及刷新是数据量大的情况下
3、SurefaceView底部采用了双缓存机制,常见的视频播放。
RecycleView的使用
recyckeView.setLayoutManager(设置布局管理器,支持三种:横/纵向,流式布局,瀑布流);
recyckeView.setAdapter(设置适配源)
//也可以设置分割线、动画等
序列化的作用,以及Android两种序列化的区别
序列化是指将对象转化为文件存储在本地存储中的操作,主要是为了保存对象的状态
Serializable:java自带,使用简单,但是要重复读写内存,效率低
Parcelable:android自带,使用复杂,重复利用内存,效率高。
插值器
android中的插值器主要是为了实现动画的非线性需求而定义的,例如加减速等
估值器
协助插值器 实现非线性运动的动画效果
Android中数据存储方式
sharedpreferences:android提供的基于key、value保存在xml文件中的存储方式,基本数据类型等。非线程安全
文件存储:将对象采用序列化的方式保存到本地
sqlite:数据库存储
contentprovider:通过程序之间共享数据存储:
网络云存储:
(二)Android源码相关分析
invalidate和postInvalidate的区别及使用
invalidate在主线程中使用,通知UI更新View
postInvalidate在子线程中调用,通知UI更新View,底层通过Handler来通知UI更新
Activity-Window-View三者的差别
1、Activity 创建时通过attach()初始化了
2、一个 Window 一个 Window 持有一个 DecorView 的实例,DecorView 本身是一个 FrameLayout,继承于View, 3、Activty通过setContentView将xml布局控件不断addView()添加到View中,最终显示到Window于我们交互;
谈谈对Volley的理解
google推出的异步网络框架,还能加载图片,适合请求量小
使用:
1、将网络请求添加到RequestQueue中
2、RequestQueue中有两个分发器:CacheDispatch(缓存分发器)和NetworkDispatch(网络分发器),其实就是开启两个线程
3、网络请求会有优先从缓存中获取,如果缓存中没有就开启一个networkdispatch,并且将请求添加到cachediapatch中,
4、将请求结果传递到主线程。
如何优化自定义View
优化自定义view可以从两个方面考虑:
1、减少invaildate调用次数,invaildate在主线程中运行,调用它会执行view的onDraw方法,造成UI卡顿
2、requestLayout操作非常耗时,因为执行requestLayout会使android Ui系统遍历整个view层级来计算view大小
3、如果UI复杂,可以考虑使用ViewGroup,与view不同的是,自定义view仅仅测量一部分
低版本SDK如何实现高版本api?
低版本使用高版本的api最常见的是编译报错,android为开发者提供了避免编译报错的解决方案,那就是注解:
@SuppressLint(newApi)
让编译器忽略所有对新api版本的调用检查
@TargetApi(11)
让编译器忽略对特定版本的便宜检查
描述一次网络请求的流程
1、通过url找到IP
2、根据IP简历TCP连接(三次握手)
3、向服务器发送数据
4、服务器解析并返回结果
5、对结果进行处理
Bitmap对象的理解
Bitmap核心思想有三个:高效加载,缓存策略,性能优化;
高效加载:在不影响图片显示的情况下,使用采样率对图片就行高效加载;流程
1、将BitmapFactory.Options的inJustDecodeBounds设为true并加载图片;
2、从BitmapFactory.Options获取图片的信息,outHeight和outWidth参数
3、根据采样率的规则结果目标view的大小,计算出inSampleSize采样率
4、将BitmapFactory.inJustDecodeBounds设为false,然后重新加载图片
缓存策略:在实际开发中我们经常用bitmap进行图片缓存,使用缓存策略,我们不用每次都从网络下载图片,缓存策略一般是指缓存的添加、获取和删除,因此实际开发中配合LRUCache能更高效的加载图片
性能优化:bitmap加载图片所占用内存一部分来自jvm分配,另一部分来自native也就是底层分配,jvm的分配的内存有gc来回收,而native非配的内存可以由recyle()进行回收,因此如果我们当前如果确定对象不是用可以调用recyle进行释放底层分配的内存,实际上android可以不用我们调用这个方法,如果gc检测到当前bitmap没有引用,会自动释放recycle,因此手动调用也没有关系
ActivityThread,AMS,WMS的工作原理
自定义View如何考虑机型适配
合理使用warp_content,match_parent.
使用RelativeLayout 减少层级布局
尽量使用点9图片
针对不同的机型,使用不同的布局文件放在对应的目录下,android会自动匹配
自定义View的事件
一个touch事件由,down事件、move事件、up事件组成,当一个时间产生以后,系统会将这个点击事件传递到某个具体的view上,传递的顺序是activity、viewgroup、view,传递的过程中经过三个过程。
AsyncTask 工作流程?重要方法?
AsyncTask内部封装了线程池和Handler,便于执行后台任务和在子线程中更新UI
工作流程:
1、耗时操作之前准备 (Main Thread)
2、处理耗时操作 & 向主线程发送更新进度的 message(Work Thread)
3、获取进度的回调并处理 (Work Thread)
4、耗时操作结束的处理 (Main Thread)
5、(如果调用cancel),则要处理取消后的相应操作 (Main Thread)
主要涉及到的四个核心方法
onPreExecute(): 在主线程处理一些准备工作。
doInBackground(Params…params): 在子线程中处理异步耗时任务,可以通过 publishProgress 方法来更新任务的进度。
onProgressUpdate(Progress…values): 在主线程中执行,当后台任务进度改变触发回调。
onPostExecute(Result result): 在主线程中,异步任务结束触发回调,其中 result 就是后台任务的返回值。
SparseArray原理
1,SpareArray用两个数组存储key和value,保持相同索引,int数组和Object数组。key键是int基本数据类型,不需要hash计算,直接返回索引。
2,HashMap的key键必须是引用类型,SpareArray可以避免key的自动装箱,数据量不大时可以代替HashMap,更省内存。
3,采用二分查找算法获取数据value作者:光晨子链接:https://www.jianshu.com/p/3dba26007242来源:著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
请介绍下ContentProvider 是如何实现数据共享的?
1、自定义一个ContentProvider类,继承ContentProvider,
2、实现它的增删查改方法
3、在配置文件中进行注册,并未这个Contentprovider制定一个URL供外部访问/
Android Service与Activity之间通信的几种方式
1、Activity传递数据到Service,通过startService或者BinderService传递Intent传递数据通信
2、Service传递数据到Activity,通过Binder来传递
3、通过广播来传递数据,
4、通过接口回调
IntentService原理及作用是什么?
IntentService继承Service,它是一个异步自动停止的高级服务类,优先级比线程高,不容易被杀死,内部封装了可供外部使用HanderThread.
原理:内部封装了HandlerThread和Handler,
作用:因为它内部的onHandleIntent是一个异步线程,因此可以执行耗时操作,并将结果通过Handler通知给UI
SP是进程同步的吗?有什么方法做到同步?
android中进程之间不支持内存共享,每个进程访问sp都有一个单独的实例,因此多进程访问sp容易造成数据丢失,不安全等因素。
配合ContentProvider 使用
谈谈多线程在Android中的使用
android中ui线程不允许执行耗时操作,因此我们平常都来开启多线程等操作来解决网络请求,后台下载,耗时操作的问题,同时利用Handler来解决UI线程和子线程之间的通信问题,这就解决了UI更新,
asyncTask、headerThread、interService
RecycleView原理
RecyclerView拥有四级缓存:
屏幕内缓存 :指在屏幕中显示的ViewHolder,这些ViewHolder会缓存在mAttachedScrap、mChangedScrap中 。mChangedScrap表示数据已经改变的ViewHolder列表
mAttachedScrap未与RecyclerView分离的ViewHolder列表
屏幕外缓存:当列表滑动出了屏幕时,ViewHolder会被缓存在 mCachedViews,其大小由mViewCacheMax决定,默认DEFAULT_CACHE_SIZE为2,可通过Recyclerview.setItemViewCacheSize()动态设置。
自定义缓存:可以自己实现ViewCacheExtension类实现自定义缓存,可通过Recyclerview.setViewCacheExtension()设置。通常我们也不会去设置他,系统已经预先提供了两级缓存了,除非有特殊需求,比如要在调用系统的缓存池之前,返回一个特定的视图,才会用到他。
缓存池 :ViewHolder首先会缓存在mCachedViews中,当超过了2个(默认为2),就会添加到mRecyclerPool中。mRecyclerPool会根据ViewType把ViewHolder分别存储在不同的集合中,每个集合最多缓存5个ViewHolder。
(三)常见的一些原理性问题
Handler机制和底层实现
定义:负责跨线程通信,这是因为在主线程不能做耗时操作,而子线程不能更新UI,所以handle用于接收子线程的数据,配合UI线程更新界面
Handler包括Handler在内有四大要素:handler、message、messageQueue、Looper
工作机制:异步通信准备->消息入列->消息循环->消息处理
1、初始化Handler,主线程会默认创建Looper,Looper会自动创建一个MessageQueue,并开启自动循环,
2、Handler通过sendMessage/post两个方法发送消息到消息队列中。
3、Looper通过无限循环从消息队列中取出消息,并交由Handler处理,如果MessageQueue为null,当前会阻塞,不会继续执行。
4、Handler接收Looper发来的消息并处理
Handler 引起的内存泄露原因以及最佳解决方案
泄露原因:
Handler 允许我们发送延时消息,如果在延时期间用户关闭了 Activity,那么该 Activity 会泄露。 这个泄露是因为 Message 会持有 Handler,而又因为 Java 的特性,内部类会持有外部类,使得 Activity 会被 Handler 持有,这样最终就导致 Activity 泄露。
解决方案:
将 Handler 定义成静态的内部类,在内部持有Activity的弱引用,并在Acitivity的onDestroy()中调用handler.removeCallbacksAndMessages(null)及时移除所有消息。
ThreadLocal原理,实现及如何保证Local属性?
ThreadLocal 不是 Thread,是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,对数据存储后,只有在线程中才可以获取到存储的数据,对于其他线程来说是无法获取到数据
请描述一下View事件传递分发机制
1、事件分发的机制就是手指触摸屏幕后所产生的一些列事件,这些事件包括:action_down、action_move、action_up
2、当一个点击事件产生以后系统会将这个点击事件传递到某个具体的view上,传递顺序是:activity、viewgroup、view
3、传递过程中有三个重要事件:
diapatchTouchEvent:
对事件进行分发,标识是否消耗当前事件
onInterceptTouchEvent:
在上述方法内部调用,标识当前事件事件是否被拦截;
onTouchEvent:
在第一个方法内部调用,表示用来处理点击事件
对于一个根ViewGroup来说,当点击事件产生以后,首先会传递给它,这时它的diapatchTouchEvent就会被调用,如果这个viewgroup的onInterceptTouchEvent方法返回true,表示它要拦截当前事件,接着这个事件就会交给viewgroup处理,即它的TouchEvent方法会被调用,如果不拦截,那么事件就传递给它的子元素,接着子元素的diapatchTouchEvent就会被调用,如此反复
View和ViewGroup分别有哪些事件分发相关的回调方法
View刷新机制
在Android的View刷新机制中,父View负责刷新(invalidateChild)、布局(layoutChild)显示子View。而当子View需要刷新时,则是通知父View刷新子view来完成。
View绘制流程
View的绘制是从根节点开始,是一种自上而下的过程,分别经历测量、布局、绘制,即:measure、layout、draw
mesasure:负责确定view四个顶点的位置;
layout:确定view最终四个顶点的位置和宽高
draw:将view绘制到界面中
AsyncTask机制
一些方法:
execute串行执行(一次只能执行一个任务)
executeOnExecutor并行执行(多个任务同时执行)
onPreExecute 运行在主线程中
doInBackground工作线程
publishProgress工作线程,通过Handler通知更新UI
工作原理: AnsycTask执行任务时,内部会创建一个进程作用域的线程池来管理要运行的任务,也就就是 说当你调用了AsyncTask.execute()后,AsyncTask会把任务交给线程池,由线程池来管理创建Thread和运行Therad。最后和UI打交道就交给Handler去处理了
接着问线程池问题:线程池可以同时执行多少个TASK
3.0以前核心线程池5个,缓冲线程池10个,最大线程池128个,面试时画图描述
AsyncTask任务是串行还是并行?
3.0以前是并行执行,3.0以后是串行执行,默认定义了一个串行调度。可以根据设置来调用串行或者并行方法。
使用AsyncTask遇到过哪些问题? 定义过AsyncTask为Activity的非静态内部类导致内存泄漏,java特性,内部类持有外部类的引用导致的。 解决办法:定义为静态内部类持有activity的弱引用
如何取消AsyncTask?
AsyncTask.cancle()
为什么不能在子线程更新UI?
android中的UI控件都是非线程安全的,子线程中并发访问可能会导致控件处于不可预期的状态
ANR产生的原因是什么?
ANR只会发生在主线程中,产生的原因主要是主线程进行了耗时操作超过固定时间得不到响应:
1、耗时的网络操作
2、界面绘制得不到相应
3、大量的数据读写操作
ANR定位和修正
ANR产生时, 系统会生成一个traces.txt的文件放在/data/anr/下. 开发人员可通过adb命令将其导出到本地 ($adb pull data/anr/traces.txt .)通过分析,我们可以根据具体的日志查看Anr原因( 如: 普通阻塞,CPU满负荷,内存泄露 )
oom是什么?
内存溢出
当一个对象分配内存,当前系统没有内存可供非配时会导致内存溢出,常见的有图片加载
什么情况导致oom?
1、图片加载过大;
2、重复创建view
3、一些常见的内存泄漏也引起内存溢出的原因之一,比如:单例、静态变量、属性动画、Handler等
有什么解决方法可以避免OOM?
1、使用bitmap的inSampleSize采样率加载大图,
2、重复创建view不仅会造成内存溢出,还会造成界面卡顿,因此重复的利用view,比如在listview中
3、规范代码编程,尽可能少使用静态变量,
Oom 是否可以try catch?为什么?
oom不能被try catch,会直接挂掉
我们都知道Java中异常超类时Throwable,Throwable派生两个子类Error和Exception,Error是不会被捕获得,Exception会被捕获,oom继承Error因此它不会被try catch
内存泄漏是什么?
内存泄漏是指当前程序申请内存,申请的内存得不到释放,这就是内存泄漏
什么情况导致内存泄漏?
1、静态变量引起的内存泄漏
2、单例引起的内存泄漏
3、属性动画引起的内存泄漏
4、handler引起的内存泄漏
如何防止线程的内存泄漏?
1、避免使用静态变量引用当前activity上下文,引文静态变量会始终常驻内存得不到释放
2、避免过多的使用单例,单例的实现也会用到static
3、属性动画中有一类无限循环的动画,如果当前页面退出要机制停止
4、Handler在进行跨线程通信中,如果在子线程中持有了外部类的引用就得不到及时释放,将handler定义为静态内部类并持有外部类的弱引用,及时执行removeCallbackAndMessage方法
Android中缓存更新策略 ?
Android的缓存策略是指缓存的添加、获取和删除这三类操作,但不管是内存缓存还是存储设备缓存,它们的缓存容量是有限制的,因此针对这种限制android为我们提供了LRU算法。
LRU的原理 ?
为减少流量消耗,可采用缓存策略。常用的缓存算法是LRU(Least Recently Used):当缓存满时, 会优先淘汰那些近期最少使用的缓存对象。主要是两种方式:
LruCache(内存缓存):LruCache类是一个线程安全的泛型类:内部采用一个LinkedHashMap以强引用的方式存储外界的缓存对象,并提供get和put方法来完成缓存的获取和添加操作,当缓存满时会移除较早使用的缓存对象,再添加新的缓存对象。
DiskLruCache(磁盘缓存): 通过将缓存对象写入文件系统从而实现缓存效果
ContentProvider的权限管理(解答:读写分离,权限控制-精确到表级,URL控制)
如何通过广播拦截和abort一条短信?
1、自定义一个SmsReceiver继承Receiver
2、重写onReciver方法,
3、在onReceiver中监听系统短信,如果监听到来了短信,判断intent.getAction()和系统的广播action是否相等,如果相等就拦截,调用abourboardCast()
广播是否可以请求网络?
不可以,广播默认执行在主线程中,不能进行耗时操作
广播引起anr的时间限制是多少?
10s
计算一个view的嵌套层级
public void getParents(ViewParent parent) {
if (parent == null) {
Log.w("parent", "没有啦!!!");
return;
}
Log.w("parent", parent.toString());
getParents(parent.getParent());
}
Android线程有没有上限?
线程是cpu的最小执行单元,同时也是一种有线的资源,相对于系统来说,只要系统有足够的cpu资源,线程就能无限的开启
线程池有没有上限?
有,
核心线程:5个
缓冲线程:10个
最大线程:128个
Android为什么引入Parcelable?
我们来看一下android两种序列化的方式,Serializable和Parcelable
Serializable 序列化利用反射的原理,过程需要大量的I/O操作,性能低
Parcelable 原理是将对象进行分解,分解的部门都是传递可支持的数据类型,操作不需要用反射,数据也存放在 Native 内存中,效率要快很多
有没有尝试简化Parcelable的使用?
kotlin使用Parcelize注解简化Parcelable的书写
(四)开发中常见的一些问题
ListView 中图片错位的问题是如何产生的?
图片错位的问题 原因是使用了缓存,当ListView从底网上滑动的时候,当最顶部的view移出当前屏幕的时候,底部的进入的屏幕的view就复用了顶部的view,因此如果当前view数据源没有及时清空就会导致图片错位问题。
解决办法:给convertView绑定viewHolder,利用viewHodler的tag机制解决,预先给图片设置一张默认加载图,同时也减少重复创建view的问题
混合开发有了解吗?
混合开发就是在app中嵌套一个轻量级的浏览器,一部分功能采用html 5来开发,好处就是在不升级app的情况下就能实现动态更新,同时也能在其他客户端使用。
混合开发最主要的功能就是实现html5 和native的交互
mWebView.addJavascriptInterface(new JsBridge(), "bxbxbai")
知道哪些混合开发的方式?说出它们的优缺点和各自使用场景?(解答:比如:RN,weex,H5,小程序,WPA等。做Android的了解一些前- 端js等还是很有好处的);
屏幕适配的处理技巧都有哪些?
动态布局的理解
动态布局相对静态布局xml相比,它不是可视化,需要运行起来才能看见效果,但它忽略了将xml转化为布局代码,提高了效率;
动态布局使用较为灵活,但是需要技巧,需要掌握常见的集中布局的属性设置。
怎么去除重复代码?
项目越大,activity或者fragment就会越多,因此难免会有一些重复的代码,
1、设置Base(基)activity和fragment
2、采用提炼技巧,提炼方法,抽象基类,提炼常量
3、使用include减少布局重复,原理:引用其他布局,id要相同,
4、用ViewStub减少整体的布局的重复,适合整体相同,局部不同的情况
画出 Android 的大体架构图
Linux内核:
Android是基于Linux内核开发
Linux提供了安全、内存管理、进程管理等服务。
系统库和Android运行时:
系统库是一个C/C++库的集合,包含OpenGL,SQlite等,在开发过程中,开发者通过框架层来调用这些库
Android虚拟机位于Android运行时
框架层:
框架成提供了日常开发所用的API包管理器、内容提供者等位于此层
应用程序层:
包含了一些原生应用程序,如日历、短信等
Recycleview和ListView的区别
布局:
Recycleview 支持横向/纵向布局、流式布局、瀑布流;ListView仅支持横向布局
点击事件:
Recycleview不支持itemClick事件,ListView支持
动画:
Recycleview 支持item动画,ListView不支持
ViewHolder:
我们都知道ViewHolder是保存视图引用的类,在ListView中ViewHolder可用可不用,用需要自定义,而RecyclerView中则必须使用,RecyclerView.ViewHolder
缓存机制:ListView缓存机制是RecyclerBin,RecyclerView是Recycler和ViewHolder配合使用
动态权限适配方案,权限组的概念
我们每个程序机会都会用到用户权限,权限提醒分为,系统弹窗和自定义弹窗,自定义弹窗一般用于用户拒绝系统弹窗的提醒窗口后并不再提醒后,我们为了用户体验,自己后台检测比较人性化的一种弹窗体验。
Android系统为什么会设计ContentProvider?
如果我们项目中有需求要使用通讯录、短信等内容,假如说:我们读取内容后将内容以数据库、SP或者xml的形式保存到本地,当数据来源发生改变时,那么我们保存的内容也需要修改,这就造成了很大的关联性的问题,因此系统为开发者提供了用于不同程序之间共享数据(跨境成通信)的一种方式ContentPrivoder;
ContentProvider厉害的地方在于:
1、封装、对数据进行了封装、提供统一接口,当数据来源改变时,程序不需要做任何修改
2、提供了一种跨进程数据共享的方式
既然是对外提供数据共享,那么如何限制对方的使用呢?
答:android:exported属性非常重要 true可以交互,false不能交互,同一app组件可以使用
ContentProvider接口方法运行在哪个线程中呢?
答:配置文件中有一个叫android:multiprocess,false为单例,true为每个进程创建一个实例
ContentProvider和调用者在同一个进程,ContentProvider的增删查改方法和调用者在同一线程中;
ContentProvider和调用者在不同的进程,ContentProvider的方法会运行在它自身所在进程的一个Binder线程中。
ContentProvider是如何在不同应用程序之间传输数据的?
答:
下拉状态栏是不是影响activity的生命周期
不会
Bitmap 使用时候注意什么?
内存溢出:
使用缓存
Bitmap的recycler()
Bitmap对象占用的内存分为两部分:JVM分配和native分配,jvm分配的由GC自动回收,native分配的执行recycler()方法才能回收,recycler()并不会立即回收掉,等待下一次GC工作之前才会被回收掉,当前目前的android版本即使不调用这个方法,系统也会自动执行recycler回收native部分内存
Android中开启摄像头的主要步骤
1、配置文件中添加权限
2、 要将摄像头捕获的图像实时地显示在手机上,使用surfaceView
3、设置窗口的显示方式
LRUCache原理
LRUCache是一个线程安全的泛型类,它内部维护了一个LinkHashMap以强引用的方式对外缓存对象,并提供get和put方法用来获取和添加缓存,它内部原理是删除掉最近最少使用的缓存,添加新的缓存。
MVC、MVP、MVVM
MVC:Model(数据模型层)、View(视图展示层)、Controller(业务逻辑层)
MVP:Model(数据模型层)、View(视图展示层)、Presenter(业务逻辑层)
MVP可以降低代码耦合度,提高代码的结构清晰度、可读性更高、复用性更强。
具体些来说(参考JessYan的例子):
现在有这么一个需求:Activity中从网络获取数据然后展示在A控件上。
如果不用MVP的话,那就直接把获取展示等代码都写在Activity中,很快便可以写完。
但现在需求变动了:
1.要求加入缓存功能,如果本地有数据,则先从本地获取数据,然后再从网络获取最新数据进行替换
2.要求数据展示在B控件上而不是A控件。
如果代码都是你自己写的,那改起来还比较轻松,但假如是团队开发,代码不是你写的,你需要花时间把逻辑重新看一遍再开始改,而且如果改错的话,会影响之前已经写好的功能。
但使用MVP模式进行开发就不同了。由于它的分工结构清晰,V层仅负责数据展示,P层仅负责业务逻辑,M层仅负责数据获取/处理。所以改动起来就轻松很多。
对于变动的需求1:我们只需在P层加入逻辑判断(先从本地获取,再网络获取),然后M层增加一个从本地获取数据方法。
对于变动的需求2:我们只需在V层修改获取到数据后的展示方式,从控件A改成控件B。