金三银四,对自己的面试总结下(继续完善当中)
1 hashmap的实现原理
https://www.jianshu.com/p/45fa4e80b631(比较好的讲解文章)
原理图:https://www.cnblogs.com/chengxiao/p/6059914.html
HashMap其实就是ArrayList和LinkedList的数据结构加上hashCode和equals方法的思想设计出来的,默认底层hash表数组的大小为16
HashMap 而言,系统 key-value 当成一个整体进行处理,系统总是根据 Hash 算法来计算 key-value 的存储位置,这样可以保证能快速存、取 Map 的 key-value 对。HashMap 的存储实例
HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。 HashMap在每个链表节点中储存键值对对象。
当两个不同的键对象的hashcode相同时会发生什么? 它们会储存在同一个bucket位置的链表中。键对象的equals()方法用来找到键值对
ashMap和Hashtable都实现了Map接口,但决定用哪一个之前先要弄清楚它们之间的分别。主要的区别有:线程安全性,同步(synchronization),以及速度。
hashtable是线程安全的,多个线程可以共享一个table
2多线程访问数据
concurrentmodificationexception
3内存泄露怎么检测
严苛模式,有线程策略,VM策略
线程策略检测的内容有
自定义的耗时调用 使用detectCustomSlowCalls()开启
磁盘读取操作 使用detectDiskReads()开启
磁盘写入操作 使用detectDiskWrites()开启
网络操作 使用detectNetwork()开启
VM策略检测的内容
Activity泄露 使用detectActivityLeaks()开启
未关闭的Closable对象泄露 使用detectLeakedClosableObjects()开启
泄露的Sqlite对象 使用detectLeakedSqlLiteObjects()开启
检测实例数量 使用setClassInstanceLimit()开启
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectCustomSlowCalls() //API等级11,使用StrictMode.noteSlowCode.detectDiskReads()
.detectDiskWrites()
.detectNetwork()// or .detectAll() for all detectable problems
.penaltyDialog() //弹出违规提示对话框
.penaltyLog() //在Logcat 中打印违规异常信息
.penaltyFlashScreen() //API等级11.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects() //API等级11.penaltyLog()
.penaltyDeath()
.build());
4内存泄露 https://www.jianshu.com/p/ab4a7e353076
如果一个无用对象(不需要再使用的对象)仍然被其他对象持有引用,造成该对象无法被系统回收,以致该对象在堆中所占用的内存单元无法被释放而造成内存空间浪费,这中情况就是内存泄露
在Android开发中,静态持有很多时候都有可能因为其使用的生命周期不一致而导致内存泄露,所以我们在新建静态持有的变量的时候需要多考虑一下各个成员之间的引用关系,并且尽量少地使用静态持有的变量,以避免发生内存泄露。当然,我们也可以在适当的时候讲静态量重置为null
单例导致的内存泄露静态变量导致的内存泄露非静态内部类导致的内存泄露 匿名内部类导致的内存泄露(默认就隐式的持有外部Activity的引用,导致Activity内存泄露。要避免内存泄露的话还是需要像上面Handler一样使用静态内部类+弱应用的方式)未取消注册或回调导致内存泄露Timer和TimerTask导致内存泄露资源未关闭或释放导致内存泄露属性动画造成内存泄露
总结:
构造单例的时候尽量别用Activity的引用;
静态引用时注意应用对象的置空或者少用静态引用;
使用静态内部类+软引用代替非静态内部类;
及时取消广播或者观察者注册;
耗时任务、属性动画在Activity销毁时记得cancel;
文件流、Cursor等资源及时关闭;
Activity销毁时WebView的移除和销毁。
5内存泄露如何分析的方法(a: Android studio自带工具dump出hprof文件,然后使用MAT工具进行分析 b: Android studio自带工具dump出hprof文件,右上角有performanalazyer,可以分析出发生泄露的activity) c: leakcanary ,square公司开源的检测内存泄露的工具
6 性能优化https://www.jianshu.com/p/da63581a2212
App启动速度( 会利用主题去防止出现白屏;针对启动速度慢,需要尽可能减少Application的onCreate中所要做的事情,比如一些不重要的SDK延迟或者异步加载;多进程情况下一定要可以在onCreate中去区分进程做一些初始化工作;部分将要使用到的类异步加载 )
UI的流畅性一般就是不要在主进程去做耗时的操作,提升UI的绘制速度(减少View的布局层级,避免过渡绘制等)...TraceView、Lint、Hugo、StrictMode等
内存优化消除应用中的内存泄露、避免内存抖动;常用工具就是AS自带的内存检测,可以很好的发现内存抖动;leakcanary可以非常方便的帮助我们发现内存泄露;MAT可以做更多的内存分析。
APK瘦身 利用ProGuard压缩代码去除无用资源 ,andresguard进一步压缩与混淆资源,极致的图片压缩与webp的使用,去除不必要的配置,仅保留中文配置
电量优化 合理的使用一些传感器、谨慎的使用Wake Lock、减少后台的不要的操作
7 traceview 的使用https://www.jianshu.com/p/388c693c1b58
8 MVP模式的介绍 http://blog.csdn.net/lmj623565791/article/details/46596109
View 对应于Activity,负责View的绘制以及与用户交互
Model 依然是业务逻辑和实体模型
Presenter 负责完成View于Model间的交互
MVP把Activity中的UI逻辑抽象成View接口,把业务逻辑抽象成Presenter接口,Model类还是原来的Model。
分离了视图逻辑和业务逻辑,降低了耦合,Activity只处理生命周期的任务,代码变得更加简洁,视图逻辑和业务逻辑分别抽象到了View和Presenter的接口中去,提高代码的可阅读性,Presenter被抽象成接口,可以有多种具体的实现,所以方便进行单元测试
9 如何自定义控件
自定义属性的声明与获取(res/values/attr.xml声明属性,自定义view中通过context.getObtainedStyleAttributes获取属性)onMeasure onLayout onDrawonTouchEventonInterceptTouchEvent(viewgroup)
10 网络以及http请求
ExecutorService(线程池)里面可以做耗时任务
11动态广播和静态广播的区别,
第一种不是常驻型广播,也就是说广播跟随程序的生命周期。
第二种是常驻型,也就是说当应用程序关闭后,如果有信息广播来,程序也会被系统调用自动运行。
本地广播 Localbroadcastmanager lbm= LocalbroadcastManager.getInstance(this)lbm.registerReceiver(new BroadcastReceiver)lbm.sendbroadcast(new Intent(action))和全局广播
12 sleep()和wait()的区别
sleep由线程控制,sleep过程中不释放同步锁,过了规定的时间自己会醒过来,wait()由对象控制,释放同步锁,不会主动醒来,必须调用对象的notify() ,wait只能用在同步方法或者同步代码块当中
13 string stringbuilder stringbuffer
基本原则:如果要操作少量的数据,用String ;单线程操作大量数据,用StringBuilder ;多线程操作大量数据,用StringBuffer。
StringBuilder一般使用在方法内部来完成类似"+"功能,因为是线程不安全的,所以用完以后可以丢弃。StringBuffer主要用在全局变量中。
13 单例模式 (注意懒汉 饿汉 已经同步问题(synchronized),以及volatite关键字,涉及到指令重排,可能先分配空间等,然后才初始化,所以要加关键字,避免重复创建两个对象)
【金三银四,对自己的面试总结下(继续完善当中)】14 recycleview http://blog.csdn.net/lmj623565791/article/details/45059587
15 静态变量什么时候使用
使用场景:
(1)变量所包含的对象体积较大,占用内存较多。
(2)变量所包含的对象生命周期较长。
(3)变量所包含的对象数据稳定。
(4)该类的对象实例有对该变量所包含的对象的共享需求。
(概念)
final:
语法说明:
1、final修饰类的时候,该类里的所有方法均默认为final类型,并且该类不能被继承(当方法为final的时候,子类已经不能重写父类方法,只能被调用,因此不让继承也是可以理解的。)
2、 final修饰方法的时候,当该方法所在的类被继承时,该方法是不能被重写的。
3、当final修饰方法参数的时候,表示在方法体中,该参数不可变,不能进行二次赋值。
4、当方法修饰变量的时候,表示该变量不可变,不能再引用变量的方法体中,重新对该变量赋值,编译直接会报错
使用说明:为什么要使用final修饰类、方法、参数呢,出于两点考虑
1、设计:设计需要
2、效率:编译器在遇到final方法的时候会转入内嵌机制,大大提高效率
static:
语法说明:
1、static修饰成员变量的时候,表示该成员变量属于类变量,可以对比实例变量进行记忆:当jvm第一次加载某类的时候,会主动加载static变量,并且在内存中只有一份该变量数据,其生命周期也是依赖与类的生命周期,与实例变量正好做对应(实例变量依赖与实例,每次产生实例的时候,对实例变量都会有一份内存的拷贝,并非唯一)
2、static修饰方法的时候表示该方法属于类方法,调用的时候也不再需要对象实例。
3、static组成静态代码块,当jvm(java虚拟机)加载类的时候,会执行静态代码块,若有多个静态代码块的情况下,会依照编译后的字节码的顺序,依次执行static块,并且只会加载一次。个人感觉实际开发中挺有用的。说到这,有一个梗:在jvm加载java类文件的时候,构造方法,静态代码块的执行优先顺序问题(无论字节码顺序是什么,总是会先执行静态代码块,并且只执行一次)。
static final一起使用修饰成员变量或方法时,可以理解为“全局常量”,均可通过类名直接访问。
16 内部类可以访问外部对象的原因
普通内部类(不仅形式上和外部有关系,逻辑上也有关系,内部类可以访问外部的属性和方法)和静态内部类(只是形式上和外部有关系,逻辑上一点关系都没有)
内部类持有外部对象的引用,为什么持有外部对象的引用的原理呢,
在内部类Outer$Inner中, 存在一个名字为this$0 , 类型为Outer的成员变量, 并且这个变量是final的。 其实这个就是所谓的“在内部类对象中存在的指向外部类对象的引用”。但是我们在定义这个内部类的时候, 并没有声明它, 所以这个成员变量是编译器加上的。
a:编译器自动为内部类添加一个成员变量, 这个成员变量的类型和外部类的类型相同, 这个成员变量就是指向外部类对象的引用;
b: 编译器自动为内部类的构造方法添加一个参数, 参数的类型是外部类的类型, 在构造方法内部使用这个参数为1中添加的成员变量赋值;
c: 在调用内部类的构造函数初始化内部类对象时, 会默认传入外部类的引用。
17 sleep()和wait()的区别
http://blog.csdn.net/xyh269/article/details/52613507
sleep()是线程的方法
wait()是Object的方法
sleep()让出cpu,但是不释放资源,还持有锁,执行完sleep()后,其他的方法才能获得这个锁,
wait() ,当等待时,释放锁,其他对象可以访问,不能自启,必须通过notify()唤醒
18跨进程访问的几种方式
http://blog.csdn.net/sinat_29255093/article/details/51817640
startActivity(),广播, contentprovider, AIDL
19 google为什么使用handler来更新UI,为什么在子线程更新UI会报错
http://blog.csdn.net/luojiusan520/article/details/46783479
子线程中是不能进行UI 更新的,而可以更新的结果只是一个幻像:因为子线程代码执行完毕了,又自动进入到了主线程,执行了子线程中的UI更新的函数栈,这中间的时间非常的短,就让大家误以为子线程可以更新UI。如果子线程一直在运行,则子线程中的UI更新的函数栈 主线程无法获知,即无法更新,
UI是非线程安全的,主线程和子线程同时更新UI的话会导致错误,如UI错乱之类的。
UI更新是很耗性能的,更别说为了线程安全加锁了,最简单的方法就是更新UI的操作放到一个线程中,即主线程
20 Contentprovider的(原理)以及跨进程通过URI访问其他进程的底层原理
https://www.jianshu.com/p/ea8bc4aaf057
21 looper是怎么来的(调用Looper.prepare()会new一个Looper对象,Looper的构造函数中,又会new一个消息队列MessageQueue。)
https://www.2cto.com/kf/201607/531616.html
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
(扩展)
主线程之所以可以接收handler的消息,是因为在activitytjread的main方法中调用了looper.prepareMainLooper()方法,创建了looper,如果我们直接创建一个线程时,是无法接收消息的,因为线程中没有looper,我们必须手动创建一个looper,实际上我们自己定义的线程的实现原理和activitythread当中是一样的,都需要调用Looper.prepare()looper.loop()
Android应用程序是通过消息来驱动的,每一个拥有Looper的线程(如主线程),都有一个消息队列,其他线程向消息队列里放入消息,Looper线程不断循环地从消息队列里取出消息并处理。没有消息可处理时,Looper线程就进入阻塞状态,直到有新的消息需要处理时被唤醒。
MessageQueue是在Looper的构造函数中new出来的,looper的构造函数中也得到当前的thread
一般可能会抛的异常,可以看下原理
Looper.java当中的关键代码
private static void prepare(booleanquitAllowed) {
//同一个线程不能重复创建Looper
if(sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(newLooper(quitAllowed));
public static void loop() {
// 获取当前线程的looper
finalLooper me = myLooper();
if(me == null) {
thrownewRuntimeException("No Looper;
Looper.prepare() wasn't called on this thread.");
}
//获取looper对象的消息队列
finalMessageQueue queue = me.mQueue;
for(;
;
) {(死循环)
//循环从消息队列中获取消息
//消息队列中没有消息,或者都是若干时间后才要处理的消息,就会阻塞在这里
//等有新的需要马上处理的消息或者到时间后,就会取出消息继续执行。
Message msg = queue.next();
// might block
if(msg == null) {
// No message indicates that the message queue is quitting.
return;
}
//从消息队列中取出消息
msg.target.dispatchMessage(msg);
msg.target指的是handler本身,这个方法是关键点,里面调用handlemessage()
msg.recycleUnchecked();
}
}
quit()以及quitsafely(),两者的区别: 一个是直接退出,并且回收队列里面的所有消息,第二个是处理完当前的消息,并且回收队里要在将来某个时刻处理的消息。
handler.java当中的关键代码
new Handler之前必须调用Looper.prepare()初始化Looper对象(主线程的Looper由系统初始化),否则会抛异常。
new Handler不带Looper参数时,Handler默认获取当前线程的Looper,即由哪个线程创建的Handler,发消息时,就是哪个线程来处理消息。
Handler对象还得到了Looper对应的消息队列。
22 binder的底层原理,AIDL的使用以及原理
https://www.jianshu.com/p/4ee3fd07da14
Binder的作用是:连接 两个进程,实现了mmap()系统调用,主要负责 创建数据接收的缓存空间 & 管理数据接收缓存, copy from user()以及内存映射
注:一个进程空间分为用户空间和内核空间,传统的跨进程通信需拷贝数据2次(用户空间的数据不共享,内核空间的数据是共享),但Binder机制只需1次,主要是使用到了内存映射
(以下为了帮助理解,盗图)
Binder驱动 & Service Manager进程 属于 Android基础架构(即系统已经实现好了);而Client 进程 和 Server 进程 属于Android应用层(需要开发者自己实现)
23 synchronized关键字以及另外一个和锁有关的关键字
http://blog.csdn.net/vking_wang/article/details/9952063
24 队列的特点
先进先出的线性表,允许在队尾(rear)插入,队头(front)删除
25 系统编译APK时的签名以及权限
在Android.mk里面LOCAL_CERTIFICATE := platform
26 线程池有没有使用过(自己了解过,但是没有使用)
引入线程池的作用:a:每次new Thread()耗费性能 b:调用new Thread()创建的线程缺乏管理,被称为野线程,而且可以无限制创建,之间相互竞争,会导致过多占用系统资源导致系统瘫痪。c:不利于扩展,比如如定时执行、定期执行、线程中断
Executors工厂有四种线程池。newFixedThreadPool、newCachedThreadPool、newSingleThreadExecutor、newScheduledThreadPool
使用方法:申明一个runnable,然后使用ExecutorServices executorservices=Executors.newSingleThreadExecutor();
executorservices.execute(runnable);
27 activity的四种启动模式,以及在某种模式下,某个场景下生命周期是怎么走的(原谅我场景想不起来)
28 自定义View的性能优化
29通过URI形式跨进程访问数据的原理
30 多线程有没有使用过?
进程以及线程的概念
进程:进程指正在运行的程序。确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能。
线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至
少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
http://blog.csdn.net/qq_32199531/article/details/53435497
解决耗时任务(IO,联网请求,数据库),ANR,掉帧,提高并发能力
使用多线程的几种姿势:
Thread
a:new thread,实现run()方法 b: 实现runnable接口,将接口传入thread当中
AsyncTask
轻量级的异步任务类,但是不适合做耗时操作,只有doInbackgroud()方法在子线程,其余方法在主线程
使用注意点:
1)想象一种情况:在一个Activity页面,如果发起了AsyncTask任务,然后页面离开/销毁了,此时如果doInBackground没执行完,会有什么问题?
首先,AsyncTask白白消耗资源,结果已经用不上了,因为UI也不在;
2)那么如何优雅的终止AsyncTask呢?
鉴于以上的问题,一般我们要在Activity onDestory的时候cancel掉AsyncTask任务。
3)关于cancel()方法
cancel()方法并非是直接停止Asynctask后台线程,而是发送一个停止线程的状态位,
因而需要在doInBackground 不断的检查此状态位。
如:if(isCancelled()) return null;
// Task被取消了,马上退出
HandlerThread
实际上是一个带有Looper的Thread,从而可向子线程传递消息
Intentservice
内部实现其实是HandlerThread,且优先级比较高,适合高优先级的后台任务
ExecutorService
普通场景,要求并发性高
31arraylist的底层实现原理以及扩容
32归并算法
33 Arraylist和数组的区别
数组在内存中是连续存储的,所以它的索引速度是非常的快,而且赋值与修改元素也很简单,比如:
string[] s=newstring[3];
//赋值s[0]="a";
s[1]="b";
s[2]="c";
//修改s[1]="b1";
但是数组也存在一些不足的地方。比如在数组的两个数据间插入数据也是很麻烦的,还有我们在声明数组的时候,必须同时指明数组的长度,数组的长度过长,会造成内存浪费,数组和长度过短,会造成数据溢出的错误。这样如果在声明数组时我们并不清楚数组的长度,就变的很麻烦了。C#中最先提供了ArrayList对象来克服这些缺点。
ArrayList是.Net Framework提供的用于数据存储和检索的专用类,它是命名空间System.Collections下的一部分。它的大小是按照其中存储的数据来动态扩充与收缩的。所以,我们在声明ArrayList对象时并不需要指定它的长度。ArrayList继承了IList接口,所以它可以很方便的进行数据的添加,插入和移除.
https://www.cnblogs.com/a164266729/p/4561651.html
34接口和抽象类的区别
a:抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。
b:抽象类要被子类继承,接口要被类实现。
c、接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现
d、接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
e、抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类。同样,一个实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类。
f、抽象方法只能申明,不能实现。abstract void abc();
不能写成abstract void abc(){}。
g、抽象类里可以没有抽象方法
h、如果一个类里有抽象方法,那么这个类只能是抽象类
i、抽象方法要被实现,所以不能是静态的,也不能是私有的。
j、接口可继承接口,并可多继承接口,但类只能单根继承。
使用场景; 多线程通信;
35 androidactivity生命周期, 然后举了个例子两个全屏activity切换的生命周期和后面一个activity,前面一个dialog样式的activity切换的生命周期的区别;
http://blog.csdn.net/u012489412/article/details/53745110
https://www.cnblogs.com/listened/p/4142646.html(主要是第一个activity的onstop不会调用,因为dialog浮在activity上面。activity还是可见的)
36 activity四种启动模式,问我如果设计一个直播应用供三方应用调用打开的,我会设计成什么启动模式的activity,为什么设计?
37 自定义view, 重写什么方法,还有requestLayout和invalidate方法干嘛的,还问了invalidate和postInvalidate方法的区别
38 重写和重载
http://blog.csdn.net/linzhaojie525/article/details/55213010
推荐阅读
- 布丽吉特,人生绝对的赢家
- 进必趋|进必趋 退必迟,问起对 视勿移
- 跌跌撞撞奔向你|跌跌撞撞奔向你 第四章(你补英语,我补物理)
- 奔向你的城市
- 对称加密和非对称加密的区别
- 四首关于旅行记忆的外文歌曲
- 对抗抑郁最好的方法
- CET4听力微技能一
- 装聋作哑,关系融洽
- 亲子日记第186篇,2018、7、26、星期四、晴