不知不觉金三银四已过去,随后看到很多朋友在博客,简书等发布自己的面试总结,其中有很多知识点都是模糊的,因为项目中很少用到,或者是用到了没有深入去研究。其实在过去,我常常有这种心理,觉得项目中不常用或者几乎不用普及的大可不必去研究。完全是为了开发而开发。久而久之我发现我所做的项目或多或少会出现一些bug,而这些bug有时候会困扰我几个小时,甚至大半天。后面当我去研究那些我认为项目中很少用,或者几乎用不到的知识点后,才发现自己所犯的错,而且你会很清楚的明白错误原由,以及解决办法。我之所以说这些就想说明要多去研究知识点,不要为了开发而开发。对一些不理解的api要学会看源码。学会读源码。好了,言归正算。下面我就汇总下我们平常面试中常见的问题(我认为)及自己的答题。
1.正常情况activity生命周期是什么?
onCreate()-onStart-onResume-onPause-onStop-onDestory
2.两个activity分别是AB,在A中启动B,然后返回A,请问各自的生命周期?
当A中启动B时:A:onPause()进入暂停状态,当启动B时:onCreate()-onStart-onResume后B可见,A再执行onStop()停止
返回A时B的生命周期:先onPause()进入暂停状态,当返回A时:A生命周期:onReStart()-onStart()-onResume()后A重新可见。B再执行onStop()-onDestory()销毁界面
总结:当从一个activity到另一个acitvity的时候,第一个activity会先进入一个暂停状态,只有当另外一个activity可见(调用onResume())的时候第一个acitvity才会停止或者销毁
3.activity启动模式有几种并简单说明下各自的应用场景
(1)standard:默认模式,即正常启动activity情况下
(2)singleTop:栈顶模式,即当activity处于栈顶的时候不重新创建
(3)singleTask:栈中模式,即当activity处于栈中并重新回到栈顶的时候不重新创建
(4)singleInstance:这种模式下的acitvity会单独创建一个栈来存放acitvityq切其他activity不会存放到此栈
4.service的启动方式且各自的生命周期?
(1)startservice:这种启动方式与启动者的生命周期无关,onCreate()-onStartcommand()-onDestory()
(2)bindservice:这种启动方式会将service生命周期与启动者的生命周期绑定到一起,onCreate()-onBind()-onUnBind()-onDestory()
提示:如果我们的service继承的时intentservice,一般这种情况就是我们要在service中做耗时操作,然而intentservice在
onHandleIntent()中创建一个工作线程。然后在onDestory()中会自动关闭线程
5.BroadCastReceiver有几种分类及注册方式?
分类:(1)有序广播(2)标准广播
注册方式:(1)动态注册:就是代码注册广播(2)静态注册:在注册文件中注册
6.activity,window,decorView三者之间的关系?
对于这个问题如果有开发经验的就算猜估计也能猜出七七八八,一个acitvity就是一个界面,而window就是个虚拟概念并且附属在activtiy上的窗口,它唯一的实现类就是PhoneWindow,而一个PhoneWindow对应一个DecorView,其实就是一个FrameLayout。对于这个问题我们可以看看acitvity源码,找到window和decorview大概就明白了
7.activtiy,fragment数据传递
一般来说都可以用bundle传递,还有就是eventbus跨线程通讯进行传递。
在Fragment这个知识点上我首先想说明一下,如果你被问到Fragment生命周期,你可能会回答:
onAttach()-onCreate-onCreateView()-onActivityCreated()-onStart()-onResume(0-onPause-onStop-onDestoryView()-onDestory()-onDetach()。可能HR想问的也是这种情况,但是我认为这不是最全面的回答,举个例子:当我们在使用导航栏及Tablayout+ViewPager或者BottonNavigationView+ViewPager的时候,Fragment首先执行的不是onAttach()而是onUserVisiableHint()。至于原因我想用过这个控件的都清楚viewpager会默认缓存当前可见Fragment相邻两个Fragment,所以即便Fragment不可见也有可能执行其生命周期
1.Fragment的生命周期?
见上文
2.Fragemnt栈管理方式?
答:show(),hide()或add(),remove()
3.Fragment中出现内存泄露并导致闪退,请举例说出一种场景?
回答这个问题,我们首先需要知道什么是内存泄露,什么会导致内存泄露。
内存泄露:在android中不再使用的对象,Gc垃圾回收机制无法对其回收就会产生内存泄漏注:过多的内存泄漏会导致内存溢出OOM
什么情况会导致内存泄露?
(1)在单例模式中引用acitvity或其context:我们知道单例模式其生命周期跟应用程序一样,如果传入activtiy引用,当我们退出activity的时候,需要回收acitvity但是此时单例模式中单例对象持有acitvity引用导致GC无法回收acitvity,从而导致内存泄漏
(2)handle机制导致内存泄漏,我们都知道Handler创建在主线程而此时作为acitvity内部类的handler持有acitvity引用,当我们在子线程中sendMessage时创建message对象,而message持有handler引用,从而我们可以理解为message持有activity引用,当我们退出activity的时候,而这时候消息队列中还有消息未发出,又因为message持有activity引用,导致GC无法回收acitvity从而导致内存泄露
(3)匿名内部类/非静态内部类导致内存泄漏:由于非静态内部类持有外部类的引用,如果我们在内部类做了耗时操作,当退出当前activity的时候,GC无法回收activity,导致内存泄漏
(4)在退出界面的时候,未取消注册,释放资源会导致内存泄漏
好了说了这么多,回到正题,fragment中出现内存泄露并闪退说明内存泄露频繁导致oom.
(1)在销毁界面的时候未取消注册
(2)另外就是上面说的内存泄漏第三种情况,匿名内部类/非静态内部类导致内存泄漏。这种情况一般都是我们在进行网络请求的时候销毁页面,而请求在非静态内部类实现,导致gc无法回收activity,从而导致内存泄漏(在MVP模式中)
1.说下对内存优化的理解?
这个问题就不是三言两语了,内存优化是个比较广的知识面,他包括内存泄漏,布局优化,资源优化,内存溢出等知识点
我这里就总结内存优化的几点吧:
(1)减少内存泄漏,因为过多的内存泄露会导致内存溢出(具体流程见上文)
(2)适当的减少内存消耗,比如正常使用单例模式,对一些资源图片进行压缩处理
(3)布局优化。布局优化主要是减少嵌套层次,减少内存消耗,对于布局优化可能很多朋友比较模糊,其实布局优化也能提高app性能的。
事件分发机制主要涉及到三种情况:activtiy,viewgroup,view的事件分发机制
首先说明:事件分发机制涉及到三个函数,OnInterceptTouchEvent(),DispatchTouchEvent(),OnTouchEvent()
(1)activity事件分发机制
在activtiy的事件分发机制中没有OnInterceptTouchEvent()函数,这个很好理解,activity本来就是一个界面对于事件拦截不拦截都没有意义,因此activity事件分发DispatchTouchEvent()-OntouchEvent()
一般情况DispatchTouchEvent()都是返回Super.DispatchTouchEvent(),意思是本次事件交给ViewGroup下的DispatchTouchEvent()方法处理,如果DispatchTouchEvent()返回true,表示activity消费此次事件,并不向下传递,如果返回false,表示acitvity不消费此次事件交给上层view处理,而由于是activity中所以此次事件系统消费了。所以一般情况我们都是不重写DispatchTouchEvent()。
(2)ViewGroup事件分发
DispatchTouchEvent()-OnInterceptTouchEvent()-OnTouchEvent(),当事件传递到viewgroup时首先调用DispatchTouchEvent(),如果此方法返回Super.DispatchTouchEvent(),表示此次事件交给OnInterceptTouchEvent()处理,如果返回true,表示消费事件并不向下传递了,如果返回false表示不消费事件,并把事件交给上层view处理,
OnInterceptTouchEvent()如果返回super.OnInterceptTouchEvent()表示此次事件交给onTouchEvent()处理,如果返回true,表示对此次事件进行拦截了,不会传递给子View,自身消费此次事件并交给OnTouchEvent()处理,如果返回false表示不拦截此次事件,向下传递给子view,
OnTouchEvent():如果返回super.OnTouchevent(),表示不消费此次事件交给上层view处理直到某个view的OnTouchEvent返回true如果返回到activty还没有消费事件就由activtiy的onTouchEvent处理,如果返回true表示消费事件并终止传递,返回false跟super.OnTouchevent()一样
(3)view的事件分发
view的事件分发跟acitvity一样,没有OnInterceptTouchEvent()函数
当事件分发到View的时候首先调用DispatchTouchEvent(),如果返回super.DispatchTouchEvent()表示交给OnTouchEvent()处理,如果返回true就消费,返回false就不消费事件,将事件交给上次view处理。
onTouchEvent()如果返回的是super.onTouchView()表示事件可以向下传递,如果返回true,表示消费事件,false跟super.onTouchView()一样
有朋友可能有疑问为什么view中onTouchView()事件还可以向下传递呐?我们知道每个事件都是由 down-move-up这三个action组成,其中down,up是必要事件,我们再看看view事件传递顺序onTouch()-onTouchEvent()-onClick()也就是说如果onTouch()返回true那么就不会执行后面两个方法,也就是说消费事件了,如果返回false,就先执行onTouchEvent(),如果onTouchEvent()返回true就不会执行onClick().如果返回false就执行onClick()
特别注意:返回super.onTouchEvent()源码中可以看到他会自动实现onclick,有朋友可以会想那么是不是返回super.onTouchEvent()会自动调用onClick()方法,其实不是,我们认真看源码,其实在actino在UP的时候要满足很多条件才会执行onClick().当然onTouchEvent()中有一个方法PerformClick()它内部会调用onClick()方法。因此也可以满足我们需求
1.说说你对RxJava的理解及使用场景?
简单来说rxjava就是一种响应式编程,也可以说链式编程(观察者模式),他配合retrofit使用及一些列操作符可以很优雅而简单的实现一些复杂的网络请求,他也可以实现轮询,定时器的一些线程切换的功能实现
2.rxbinding
我就说下rxview吧,其他接触很少,rxview就是防止连续操作控件而规定控制多少时间才能响应。比较常用的就是防抖
3.rxlifecycle
这个框架结合rxjava使用,主要防止网络请求产生的内存泄漏,他可以根据生命周期来取消订阅,
因为RXjava其原理及内部实现比较难理解所以就简单阐述下几个知识点
1.单例模式
2.Builder模式
3.观察者模式
4.工程模式
5.mvp设计模式
此处我说下MVP模式,既然是设计模式那么写法肯定不是固定的,因此每个人对其理解都不一样,我就在此说下我对mvp模式的理解吧。
MVP是MVC的升级版,其中M-Model数据模型,V-View就是我们的视图,P-Presenter控制层,他控制M,V逻辑,使M,V完全解耦。一般来说我们在M层定义一个接口和一个实现类,实现类中获取网络数据或者进行一些数据操作,P层中获取到,M,V层引用,把M层返回的结果交给V层,最后在V层做处理(更新ui一些操作)。
自定义view涉及到的知识点比较全面,也是个难点,一般来说自定义view就是去实现一些我们系统控件实现不了的效果,其具体流程就是onMeasure()-onLayout-onDraw()其中onMeasue()就是测量,这个一般在组合控件中比较常见,onLayout()就是控制view的显示位置,onDraw()就是绘制view,绘制view涉及到canvas与paint一个画布一个画笔
这个问题是很常见也问的很多的问题,一般来说我们从三个方面回答
(1)可变性:string为常量不可变因此每次拼接字符串都要重新创建对象,stringbuffer,stringbuilder为变量直接调用相应api操作
(2)安全性:string由于为常量因此是线性安全的,而stringbuffer内部实现了双重锁因此也是线性安全的,而stringbuilder则线性不安全
(3)效率:如果少量拼接string比较合适,如果单线程大量操作stringbuilder比较合适,如果多线程大量操作stringbuffer比较合适
1.关于scollview嵌套recyclerview嵌套卡顿或者显示问题
这个问题网上很多人说在布局中各种操作,其实我认为最好的解决办法不是这样,在MD中有个NewtedScollView完全可见解决这个问题
2.关于rv的优点
三点:一是rv复用性,二是:rv缓存级别高,三是:rv可以局部刷新
谈谈对ConsTraintLayout约束布局优缺点的理解?
从as2.2后就支持constraintlayout也就是约束布局,这个布局非常强大,他可以完全替换以前的linearlayout,framelayout等布局
优点:
1.可以轻松实现复杂的布局
2.减少布局嵌套,优化内存作用
3.增加一些特殊属性
缺点:
由于布局我是手动写的,我觉得唯一的缺点就是每个控件无论需要赋值与否都要设置相应的id,来约束其他控件位置。
说说跨进程通讯?
跨进程通讯即IPC,但是跨进行通讯也很常见
1.打开系统进程(一般在activity中启动系统进程比较常见)
2.contentProvide
3.BroadCastReceiver广播
4.Service(messenger)
此处说下service中跨进程通讯:
上面也提到service跨进程通讯用到messenger,客户端启动bindservice,我们在服务得创建service时创建一个handler来接受客户端发送过来得消息,并且创建一个Messenger对象传入handler,由于启动方式是bindservice就实现了onbind,onunbind方法,在onbind方法中我们通过messenger返回一个ibinder对象,最后在清单文件中定义该service
在客户端创建一个serviceconection类,重写onServiceConnected与onServiceDisconnected方法,在onServiceConnected中我们通过服务端创建得ibinder创建messenger对象,然后客户端就可以通过bindservice启动服务了,当我们需要发送消息给服务端就调用messenger.send(传入message)
public class MessengerService extends Service {
public final static String TAG = MessengerService.class.getSimpleName();
public final static int MSG_SAY_HELLO = 1;
public MessengerService() {
}
@Override
public void onCreate() {
Log.d(TAG, "onCreate");
super.onCreate();
}
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy");
super.onDestroy();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
/**
* 1. 定义一个Handler,用于处理客户端发来的请求
*/
class ProcessHandler extends Handler{
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case MSG_SAY_HELLO:
Log.d(TAG, "service receiver from client: say hello");
break;
default:
super.handleMessage(msg);
}
}
}
/**
* 2. 创建一个Messenger并传入Handler实例对象
*/
final Messenger mMessager = new Messenger(new ProcessHandler());
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind");
// 3.通过onBinder返回Messenger的Binder对象
return mMessager.getBinder();
}
@Override
public boolean onUnbind(Intent intent) {
Log.d(TAG, "onUnbind");
return super.onUnbind(intent);
}
}
/**
* 与服务端交互的Messender
*/
Messenger mService = null;
boolean mBound = false;
/**
* ServiceConnection代表与服务的连接,
*/
ServiceConnection cnn2 = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
Log.d("###", "onServiceConnected");
/**
* 3. 通过服务端传递的IBinder对象,创建相应的Messenger
* 然后通过Messenger对象与服务端进行交互
*/
mService = new Messenger(iBinder);
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
Log.d("###", "onServiceDisconnected");
mService = null;
mBound = false;
}
};
// 绑定服务
Intent intent = new Intent(this, MessengerService.class);
bindService(intent, cnn2, BIND_AUTO_CREATE);
// 解绑服务
unbindService(cnn2);
mBound = false;
// 发送消息
Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO);
try {
// 4. 通过Messenger发送消息
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
我想对于android开发者来说对于ANR这个词并不陌生,那么ANR是什么?什么情况会ANR?怎么避免?
(1)ANR:application not response应用程序无响应
(2)ANR产生主要有以下几点引起:1.在主线程做过多耗时操作2.在activity事件操作5s无响应(跟第一点差不多)3.braodcastrecevier事件操作10s无响应4.service事件操作20s无响应
(3)避免ANR:主线程中只做ui处理,耗时操作开启新线程,线程切换利用rxjava或静态内部类handler机制处理
(1)说下常用得数据存储方式?
1.文件存储 2.网络存储 3.ContentProvider数据存储 4.SharePreferences数据存储 5.本地数据库存储
提示:一般数据量小用sp,数据量大数据库
(2)sp线性安全吗?apply()与commit()方法区别?
1.首先sp是利用单例模式实现得,从源码可以看到有synchronized因此是线程安全得,
2.commit()是同步得并返回提交结果,apply()是异步操作不会返回结果.此外从效率上说apply()比commit()效率高,如果不关心返回结果建议使用apply()进行提交
所谓gc机制就是垃圾处理器一共需要两部:检测和回收垃圾
(1)检测垃圾算法
1.引用计数法:一个对象引用一次计数+1,引用失效-1
2.根搜索法:以根集对象为起始点进行搜索,如果有对象不可达的话,即为垃圾对象。这里的根集一般包括:java栈中的引用对象、本地方法栈中JNI的引用对象、方法区中运行常量池中的引用对象、方法区中静态属性引用的对象、运行中的线程、由引导类加载器加载的对象、GC控制的对象。总之,JVM在做垃圾回收的时候,会检查堆中的所有对象是否被这些根集中的对象所引用,不能够被引用的对象就会被垃圾回收器回收。
(2)回收算法
1.标记-清除:将所有需要回收的对象进行标记,然后统一清除
2.复制算法:将内存分两个区域,只使用其中一个区域存储对象,垃圾回收时遍历使用区域的所有对象并将使用对象复制到另外一个区域。
3.标记-整理:此算法为前两个算法的结合体,分两个阶段,第一部将所有被使用的对象标记,第二部遍历堆中所有对象将未被标记的对象进行回收并整理存活对象
4.分代收集算法:年轻代:复制算法,老年代:标记-整理
1.对于HaspMap原理我也是才通过一些资料研究了一下,我先根据我的理解说说HashMap原理及结构
(1)HaspMap结构在jdk1.8以前:数组+链表也就是散列表形式,在jdk1.8加了一个红黑树其结构:数组+链表+红黑树
至于其意义接下来请看HashMap原理
(2)HashMap原理:首先我们通过源码看HashMap他默认的数组大小16,每次扩容为其2倍,且数组大小为2的N次方。
数据由Entry实体类以键值对形式存储,而每个实体类对应一个HashCode.在java中每个对象都有一个hashcode()方法,它是一个由32位整数组成的,而在HashMap中我们只需要9位Int整数型,当我们put一个键值对的时候,会首先判断key是否位null,(为null默认返回第一个)然后根据K找到获取HashCode值然后对其进行hash运算获取到hash值,接着循环table根据其hash值及key判断是否相同如果相同就替换,如果不相同就添加一个Entry
另外特别注意:有可能hash冲突,也就是说我们随机参数的int整数型重复了,这时候我们就通过拉链表的方式进行处理,让每个Entry以单向链表形式存在,当链表长度达到8就会采用红黑树提高性能,当链表长度小于8就重新由转换成单向链表
总结:HashMap默认数组大小为16,每个键值对都是以Entry实体类存储的,然后每个K会对应一个HashCode也就是Hash值,当我们put一个数据的时候,首先会根据这个K获取到对应的hashcode通过hashcode获取到hash值,然后循环散列表也就是循环table判断k,hash值是否相等,如果相等就表示是更新数据,如果有一个不相等就表示添加数据(包括hash冲突,上面已经说到)。最后我们也可以通过不同构造函数设置数组默认大小,负载因子。
2.ArrayList结构及原理?
ArrayList结构及原理就比较简单了,其实他就是List接口的实现类,他内部结构就是数组切大小可变
ArrayList:是一个大小可变可重复切有序的数组,每次添加一个元素数组大小都会+1
写在前面:说到android动画我们脑海里就会想起自定义,平移,旋转等一些特效。其实android动画也是个知识面比较广的。
1.android动画分几类,说说他们的区别与特点?
(1)帧动画(frame):通过顺序的播放排列好的图片来实现
(2)补间动画(tween):包括移动、渐变、伸缩、旋转,一般是定义在res-anim这个资源文件夹下,然后res-style中定义这个动画
(3)属性动画(Property ):属性动画可以定义在res-animator的资源文件中,它是用来在特定的时间修改对象的属性,例如背景颜色和alpha等等,常用的Java类有:ValueAnimator 、ObjectAnimator和AnimatorSet
总结:帧动画与补间动画归为:视图动画。属性动画为另一类,此外我推荐另外两个动画,共享动画和过渡动画,有兴趣的朋友可以研究下
虽然现在有constraintlayout对屏幕适配又多了一些技巧,但是我们真正了解sp,dp,dpi嘛?为什么我们字体要用sp,控件用dp?
首先:字体用sp设置大小可以跟随系统字体大小改变而改变,控件用dp至少对不同分辨率适配了
接下来我们了解下sp,dp,dpi这几个单位:
sp:字体的单位,和dp差不多,区别是如果字体使用的sp为单位,那如果你手机字体调大了,那你app的字体会随之变大,如果用dp则不会变化。
dp:最常用的长、宽、margin、padding等的单位
dpi:像素密度
px:像素
其中height,width为长宽像素密度,size对角线长度
px=dp*(dpi/160)
特别注意:假设现在我有两部手机分辨率不一样,但是手机尺寸大小一样,假如第一部手机的像素密度及dpi=320,第二部手机的像素密度及dpi=160,我设置了一个2dp的宽的控件根据px=dp*(dpi/160)公式可得像素密度为160的px=dp及1px1dp,像素密度320的px=2*dp及1dp=2px.我们可以想象像素密度越大那么说明像素之间很紧密,反之就稀疏。那么当我在160dpi手机上设置宽为2dp的时候此时2dp=2px,在320dpi手机上设置宽的为2dp的时候此时2dp=4px.上面说到像素密度越大像素之间就紧密,那么此时在手机尺寸大小一样的情况下。此时2px与4px宽度相差不大的。你可以把一个像素想象成一个点,像素密度(dpi)越大每个点的距离就越小,反正就越大。这就是为什么设置宽高要用dp了,因为它适配了不同分辨率。
线程池的作用及原理?
首先我们看下使用线程池的原因:在我们开发中我们有时候需要频繁创建工作线程,然后这些工作线程及有可能导致线程阻塞,而且频繁创建线程也会降低性能
那么线程池的作用是什么呐?
根据上诉我们知道:线程池就是用来管理一个个线程的,它不会频繁创建线程,而且对其进行复用这样就不会频繁调用gc,提高了效率及性能。
那么线程池的原理是什么?
要知道线程池原理首先我们看下其构造函数:
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory)
corePoolSize:核心线程最大数量
maximumPoolSize:最大线程数量
keepAliveTime:非核心线程的保活时间,就是说当非核心线程在空闲状态在保活时间,这样当工作任务时间短,可以避免频繁创建线程。提高效率及性能
unit:上面时间属性的单位
workQueue:任务队列
threadFactory:线程工厂,可用于设置线程名字等等
通过构造函数我们就不难推出线程池的原理了:
1.当有新的任务进入的时候(execute一个线程之后)会首先判断当前核心线程是否达到最大,如果没有就直接创建一个核心线程开执行任务
2.当有新任务进入的时候(execute一个线程之后)如果核心线程达到最大但是任务队列未满,那么次任务就会进入任务队列等待
3.当有新的任务进入的时候(execute一个线程之后)如果核心线程达到最大并且任务队列已经满了,但是线程数量未超过非核心线程最大数量,就创建一个新非核心线程执行任务
4.当有新任务进入的时候(execute一个线程之后)如果线程池中的线程数已经超过非核心线程数,则拒绝执行该任务,采取饱和策略,并抛出RejectedExecutionException异常
之前我看别人面试有被问到过这个问题,于是就在网上查阅了一番,总体来说面向对象一共有六大基本原则:
1.单一职责原则
2.开闭原则
3.里氏替换原则
4.依赖倒置原则
5.迪米特原则
6.接口隔离原则
我们知道String是个不可变的常理,每次赋值都会创建一个对象,但是今天遇到个问题,String a=new String("a")创建了几个对象?
首先我举个列子来复习下String;
String s="Hellow";
s="android";
String str="android";
String s=new String("a");
在jvm的工作过程中,会创建一片的内存空间专门存入string对象。我们把这片内存空间叫做string池。
我们先来看下String s="Hellow",定义了一个String s的变量,“Hellow”为String 对象,当JVM检查到“Hellow”的时候。如果string池中有“Hellow”对象,就直接把其引用给s不会重新创建string对象,如果没有就创建string对象,并将其引用给s。这里没有就创建string对象
再来看下s="android";同样当JVM检查到“android”的时候。如果string池中有“android”对象,就直接把其引用给s不会重新创建string对象,如果没有就创建string对象,并将其引用给s。这样也没有就创建string对象.
所以
String s="Hellow";
s="android";就创建了两个对象。
String str="android";再来看下,我们定义了一个str的字符串。并将“"android"”对象引用给他,上面提到JVM检查到”android“,就将其引用给str,由于上面s="android"已经创建了"android"string对象,所以这里就直接将"android"对象引用给str,并且str==s.因为两个字符串引用相同。
最后String s=new String("a");
"a",JVM检查没有就创建,有就引用,
new String这里又手动创建了一个string对象.
因此String a=new String("a")一共创建了两个对象
Queue接口与List、Set同一级别,都是继承了Collection接口。
看图你会发现,LinkedList既可以实现Queue接口,也可以实现List接口.只不过呢, LinkedList实现了Queue接口。Queue接口窄化了对LinkedList的方法的访问权限(即在方法中的参数类型如果是Queue时,就完全只能访问Queue接口所定义的方法 了,而不能直接访问 LinkedList的非Queue的方法),以使得只有恰当的方法才可以使用。
SortedSet是个接口,它里面的(只有TreeSet这一个实现可用)中的元素一定是有序的。
— List 有序,可重复
—Set 无序,唯一
HashSet
底层数据结构是哈希表。(无序,唯一)
如何来保证元素唯一性?
1.依赖两个方法:hashCode()和equals()
LinkedHashSet
底层数据结构是链表和哈希表。(FIFO插入有序,唯一)
1.由链表保证元素有序
2.由哈希表保证元素唯一
TreeSet
底层数据结构是红黑树。(唯一,有序)
1. 如何保证元素排序的呢?
自然排序
比较器排序
2.如何保证元素唯一性的呢?
根据比较的返回值是否是0来决定
针对Collection集合我们到底使用谁呢?(掌握)
唯一吗?
是:Set
排序吗?
是:TreeSet或LinkedHashSet
否:HashSet
如果你知道是Set,但是不知道是哪个Set,就用HashSet。
否:List
要安全吗?
是:Vector
否:ArrayList或者LinkedList查询多:ArrayList
增删多:LinkedList
如果你知道是List,但是不知道是哪个List,就用ArrayList。
如果你知道是Collection集合,但是不知道使用谁,就用ArrayList。
如果你知道用集合,就用ArrayList。
说完了Collection,来简单说一下Map.
Map接口有三个比较重要的实现类,分别是HashMap、TreeMap和HashTable。
这就意味着:
1. 介绍
- TreeSet, LinkedHashSet and HashSet 在java中都是实现Set的数据结构
2. 相同点
- Duplicates elements: 因为三者都实现Set interface,所以三者都不包含duplicate elements
- Thread safety: 三者都不是线程安全的,如果要使用线程安全可以Collections.synchronizedSet()
3. 不同点
- Performance and Speed: HashSet插入数据最快,其次LinkHashSet,最慢的是TreeSet因为内部实现排序
由于TreeSet可以实现对元素按照某种规则进行排序,例如下面的例子
public class MyClass {
public static void main(String[] args) {
// 创建集合对象
// 自然顺序进行排序
TreeSet
// 创建元素并添加
// 20,18,23,22,17,24,19,18,24
ts.add(20);
ts.add(18);
ts.add(23);
ts.add(22);
ts.add(17);
ts.add(24);
ts.add(19);
ts.add(18);
ts.add(24);
// 遍历
for (Integer i : ts) {
System.out.println(i);
}
}
}
运行结果:
17
18
19
20
22
23
24
————————————————
(1).自然排序
自然排序要进行一下操作:
1.Student类中实现 Comparable接口
2.重写Comparable接口中的Compareto方法
(2).比较器排序
比较器排序步骤:
1.单独创建一个比较类,这里以MyComparator为例,并且要让其继承Comparator接口
2.重写Comparator接口中的Compare方法
1.热插件:hook,热更新:底层替换,类加载器
重点说下类加载器实现热更新吧:
首先我们知道热更新需要实现对应插件得sdk(比如thinker),然后打包补丁,去自动下载更新,
那么怎么实现更新得呐,首先我们知道android得虚拟机时Dalvik,java虚拟机时jvm,我们得应用程序最终会编译成,dex文件,它包含多个.class文件,这样.dex文件比之前会小很多,性能,效率提高了,那么我们知道了android程序最终会编译成.dex文件,我们来看看类加载器,系统自带的dexelements列表里面包含多个.dex文件,每个.dex文件都在dexelemenets里面有相应的位置,位置靠前的就优先加载,如果dexlements里面有,dex文件就会直接返回加载,因此我们要实现热更新就是在系统默认的dexelements列表里面把我们的,dex文件放到前面,这里可以优先加载了,由于是系统默认的基本上都是private修饰,所以需要用到反射机制,当我们更改后,我们自己的dex文件就会放到dexelements前面,优先加载与系统默认的dexelements。从而实现热更新了
另外我这里提到过Dalvik与jvm虚拟机,那么这两个虚拟机有什么不同呐?
dalvik虚拟机时基于寄存器,jvm是基于栈堆的,dalvik比jvm性能,效率高。
1.volite与synchronized区别:
(1)volite不会导致线程阻塞,synchronized可能会
(2)volite不能保证原子性,只有可见性,synchronized保证可见性,原子性
(3)volite只能修饰变量,synchronized可以修饰方式
java并发编程三大特性:原子性,可见性,有序性
被synchronized修饰的代码只能被被当前线程占用,避免由于其他线程的执行导致的无序行。
volatile关键字包含了禁止指令重排序的语义,使其具有有序性。
synchronized 关键值,开始时会从内存中读取,结束时,会将变化刷新到内存中,所以是可见的。
volatile关键值,通过添加lock指令,也是可见的。
2.sleep与wait方法
1.sleep是thread方法,wait是object的方法
2.sleep不释放对象锁,wait放弃对象锁
3.sleep线程进入暂停,处于监控状态结束后自动恢复
4.wait后只有对此对象进行对象锁后才能进入运行状态
1.http是超文本传输协议,客户端通过http协议向服务端发送请求,服务端收到请求后响应信息给客户端
2.tcp:传输协议,有连接,三次握手,四次挥手
3.udp:传输协议,无连接,知道ip及端口号就发送数据,不管发送成功
4.socket:是一种不同计算机,实时连接,比如说传送文件,即时通讯
Ok问到tcp,udp,http,socket区别差不多就知道这些,更多得就不知道了
说到NDK,我们先来看到jni,我们都知道java是跨平台语言编程的,它与本地交互能力较弱,因此就诞生出jni了,jni是java的特性,它主要用于java与c/c++交互。
那么我们为什么要用NDK呐?首先java与c/c++交互,c/c++编译成.so文件,在android中我们通过NDK将c/c++编译成的.so文件快速打包到apk里面,这样我们就快速,高效解决了交互问题。
图片加载分多种情况:
1.图片大小适应:直接用第三方框架加载
2.图片稍大:用bitmapfactory工厂模式对图片进行尺寸压缩后加载
3.图片超大:一般来说图片过大了,就会导致一屏显示不全,这时候我们就要用到局部加载或者说区域加载
android中为我们提供BitmapRegionDecoder这个类来解决局部加载图片问题
滑动卡顿一般由以下几个因素产生:
1.item布局加载了大量资源包括大图
2.item布局多次绘制导致与Ui刷新机制冲突,视觉上ui绘制很慢
3.嵌套问题,有时候会根据项目需求列表嵌套列表导致滑动卡顿
这个面试经常问到,我总结以下几点吧:
1.kotlin非空判断安全性
2.kotlin支持lambda表达式
3.koltin变量没有get,set方法
4.koltin中集合许多函数
5.koltin中变量申明,方法申明,接口实现,类继承等不同
6.koltin导入布局后可直接引用id
特别注意:kotlin,java可以100%互通,也就是说完全可以混合开发
在java中每个基本类型都有对应的包装类型,比如说 int 的包装类型为 Integer,double 的包装类型为 Double,基本类型和包装类型的区别:
1.包装类型可以为null,基本类型不可以
2.包装类型存储在堆中,而基本类型存储在栈中,从效率上说基本类型比包装类型好
3.包装类型可以用于泛型而基本类型不行
4.自动装箱与自动拆箱
自动装箱:把基本类型转成包装类型。自动拆箱:把包装类型转成基本类型。自动装箱是通过 Integer.valueOf()
完成的;自动拆箱是通过 Integer.intValue()
完成的。我们来看看下面几个题:
(1)Integer a= new Integer(10);
Integer b = new Integer(10);
System.out.println(a== b); // false
System.out.println(a.equals(b)); // true
分析:Integer为包装类 ==比较引用因此是false,而equals比较值所以true
(2) int a = 100;
Integer b = 100;
System.out.println(a == b);true
分析:基本类型与包装类型比较==,这时候包装类型会进行自动拆箱,int c=b.intValue()因此true
(3)Integer c = 100;
Integer d = 100;
System.out.println(c == d);
分析:两个包装类型比较==,我想大多数第一次接触的多半会认为是false,其实正确答案是true。
之前我们已经知道了,自动装箱是通过 Integer.valueOf() 完成的,那我们就来看看这个方法的源码吧。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
难不成是 IntegerCache 在作怪?你猜对了!
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
}
大致瞟一下这段代码你就全明白了。-128 到 127 之间的数会从 IntegerCache 中取,然后比较,所以第二段代码(100 在这个范围之内)的结果是 true,而第三段代码(200 不在这个范围之内,所以 new 出来了两个 Integer 对象)的结果是 false。
看完上面的分析之后,我希望大家记住一点:当需要进行自动装箱时,如果数字在 -128 至 127 之间时,会直接使用缓存中的对象,而不是重新创建一个对象。
(4)
c = 200;
d = 200;
System.out.println(c == d);
分析:通过第三题我们知道了,c=200,d=200自动装箱的时候会重新创建对象,所以这里是false
1.进程:进程是系统进行资源分配的基本单元
.有一段程序供其执行
.拥有专用的系统堆栈空间
·在内存中有对应的进程控制块
·拥有独立的用户存储空间
·进程之间不能进行自由的信息交互
2.线程:是程序执行的最小单位,是CUP调度的基本单元
.一个进程必须拥有至少一个线程
.线程是进程的一个实体,是cpu调度与分配的基本单位,它是比进程更小能独立运行的单位
.线程比进程更小,基本上不拥有系统资源,故对它的调度所用资源小,能更高效的提高系统内多个程序间并发执行的
requestLayout : 当当前布局的宽高发生改变的时候, 此时需要重新调用父view的onMeaure和onLayout, 来给子view重新排版布局
invalidate : 让页面刷新, 重新调用onDraw方法,
postInvalidate : 在子线程来让页面来进行刷新的方法
1.RequsestLayout和invalidate有什么区别
仅仅从三大方法的角度来说
requestLayout 方法触发===》onMeasure + onLayout
invalidate 方法触发 ===》onDraw
2.invalidate 和 postInvalidate有什么区别
都是视图重绘
前者在ui线程调用,后者可以再其他线程调用