年底互联网寒冬来临,很不幸,本人公司由于资金链断裂,也加入年底找工作大军当中。年底工作不好找,特此勉励所有有相同经历的同学们,保持耐心,互相鼓励。
个人面试了一两个星期,以下是我个人的面试总结,仅供参考,有错恳请提出。
一.网络
1.网络协议 http、tcp、udp、socket
网络分层和协议关系如下:
物理层--
数据链路层--
网络层-- IP协议
传输层-- TCP、UDP协议
会话层--
表示层-- HTTP协议、https、ftp
http协议是基于tcp/ip协议的一种应用。而 socket封装了做tcp/ip开发的网络接口,
通过Socket,我们才能使用TCP/IP协议。http是短连接,socket是长连接。
2.tcp三次握手、4次挥手
第一次握手:建立连接。客户端发送连接请求报文段,将SYN位置为1,Sequence Number为x;然后,
客户端进入SYN_SEND状态,等待服务器的确认;
第二次握手:服务器收到SYN报文段。服务器收到客户端的SYN报文段,需要对这个SYN报文段进行确认,
设置Acknowledgment Number为x+1(Sequence Number+1);同时,自己自己还要发送SYN请求信息,
将SYN位置为1,Sequence Number为y;服务器端将上述所有信息放到一个报文段(即SYN+ACK报文段)中,
一并发送给客户端,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK报文段。然后将Acknowledgment Number设置为y+1,
向服务器发送ACK报文段,这个报文段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态,
完成TCP三次握手。
当客户端和服务器通过三次握手建立了TCP连接以后,当数据传送完毕,肯定是要断开TCP连接的啊。
那对于TCP的断开连接,这里就有了神秘的“四次分手”。
第一次分手:主机1(可以使客户端,也可以是服务器端),设置Sequence Number和Acknowledgment
Number,向主机2发送一个FIN报文段;此时,主机1进入FIN_WAIT_1状态;这表示主机1没有数据要发送给主机2了;
第二次分手:主机2收到了主机1发送的FIN报文段,向主机1回一个ACK报文段Acknowledgment
Number为Sequence Number加1;主机1进入FIN_WAIT_2状态;主机2告诉主机1,我“同意”你的关闭请求;
第三次分手:主机2向主机1发送FIN报文段,请求关闭连接,同时主机2进入LAST_ACK状态;
第四次分手:主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段,然后主机1进入TIME_WAIT状态;
主机2收到主机1的ACK报文段以后,就关闭连接;此时,主机1等待2MSL后依然没有收到回复,
则证明Server端已正常关闭,那好,主机1也可以关闭连接了。
3.https对比http
简单来说,HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全。
HTTPS和HTTP的区别主要如下:
1、https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
4、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进
行加密传输、身份认证的网络协议,比http协议安全。
4.防止抓包
因为抓包软件例如Fiddle都是需要利用wifi代理,所以判断是wifi代理,则不访问数据
if(isWifiProxy()){ //true,使用了wifi代理
//不做访问操作
}else{ //flase,正常用户,未使用wifi代理
//访问数据
}
public static boolean isWifiProxy() {
final boolean IS_ICS_OR_LATER = Build.VERSION.SDK_INT >=
Build.VERSION_CODES.ICE_CREAM_SANDWICH;
String proxyAddress;
int proxyPort;
if (IS_ICS_OR_LATER) {
proxyAddress = System.getProperty("http.proxyHost");
String portStr = System.getProperty("http.proxyPort");
proxyPort = Integer.parseInt((portStr != null ? portStr : "-1"));
} else {
proxyAddress = android.net.Proxy.getHost(context);
proxyPort = android.net.Proxy.getPort(context);
}
return (!TextUtils.isEmpty(proxyAddress)) && (proxyPort != -1);
}
5.防止多并发请求,比如说一个页面多个请求
Android本来就是要做并发请求,开线程池在里面发网络请求,
如果真要防止并发,那就弄个排队的线程池就行了,参考AsyncTask在高版本的实现,就是排队。
6.跟后台交互网络优化
1. 比如频繁调用的接口,可以考虑用长连接
2. 需要传输数据的接口可以考虑让服务器支持304状态,比如etag和last-modified。当没有变化时,不返回数据,从缓存取
3. 让服务端把小接口合并成大接口,减少网络请求的次数
7.为何选择retrofit
1.解耦:比方说通过注解来配置请求参数,通过工厂来生成CallAdapter,Converter
2.注解:通过注解方式配置请求,代码简介、使用方便(将一个http请求抽象出java接口,再通过动态代理翻译成http请求)
3.同步异步:支持同步和异步执行,只要调用enqueue/execute即可完成;
4.自由:更大自由度地支持我们自定义的业务逻辑,如自定义Converters,或者搭配rxjava使用
8.retrofit生命周期管理
https://www.jianshu.com/p/5f3e1398b3bd
9.http组成
http请求报文和响应报文都是由以下4部分组成
1.请求行(状态行)
2.请求头
通用报文包含Date、Connection、Cache-Control
请求报头通知服务器关于客户端求求的信息,典型的请求头有:
Host、User-Agent:发送请求的浏览器类型、操作系统等信息
Accept:客户端可识别的内容类型列表,用于指定客户端接收那些类型的信息
Accept-Encoding:客户端可识别的数据编码
Accept-Language:表示浏览器所支持的语言类型
Connection:允许客户端和服务器指定与请求/响应连接有关的选项,例如这是为Keep-Alive则表示保持连接。
Transfer-Encoding:告知接收端为了保证报文的可靠传输,对报文采用了什么编码方式。
用于服务器传递自身信息的响应,常见的响应报头:
Location:用于重定向接受者到一个新的位置,常用在更换域名的时候
Server:包含可服务器用来处理请求的系统信息,与User-Agent请求报头是相对应的
实体报头用来定于被传送资源的信息,请求和响应消息都可以传送一个实体,常见的实体报头为:
Content-Type:发送给接收者的实体正文的媒体类型
Content-Lenght:实体正文的长度
Content-Language:描述资源所用的自然语言,没有设置则该选项则认为实体内容将提供给所有的语言阅读
Content-Encoding:实体报头被用作媒体类型的修饰符,它的值指示了已经被应用到实体正文的附加内容的编码
Last-Modified:实体报头用于指示资源的最后修改日期和时间
Expires:实体报头给出响应过期的日期和时间
3.空行
4.消息主体
10.http1.0和http1.1区别
http1.0 服务端向客服端返回响应消息时,确定客户端收到消息后,便会关闭连接。
整个过程中,并不保存任何历史信息和状态信息,所以http也被认为是无状态的;
http1.1后将关闭客户端连接的主动权交还给客户端,增加了持久连接支持
11.为什么选择okhttp
用于替换httpurlconnection;
支持SPDY协议,允许连接同一主机所有请求分享一个socket;
使用连接池减少请求延迟;
利用响应缓存避免重复请求;
使用GZIP压缩下载内容
12.https加密过程,ssl核心
一个HTTPS请求实际上包含了两次HTTP传输,可以细分为8步。
第一次HTTP请求:
1.客户端向服务器发起HTTPS请求,连接到服务器的443端口。
2.服务器端有一个密钥对,即公钥和私钥,是用来进行非对称加密使用的,服务器端保存着私钥,不能将其泄露,公钥可以发送给任何人。
3.服务器将自己的公钥发送给客户端。
4.客户端收到服务器端的公钥之后,会对公钥进行检查,验证其合法性(CA认证),如果公钥合格,那么客户端会生成一个随机值,这个随机值就是用于进行对称加密的密钥,
我们将该密钥称之为client key,即客户端密钥,这样在概念上和服务器端的密钥容易进行区分。然后用服务器的公钥对客户端密钥进行非对称加密,这样客户端密钥就变成密文了
第二次HTTP请求:
1.客户端会发起HTTPS中的第二个HTTP请求,将加密之后的客户端密钥发送给服务器。
2.服务器接收到客户端发来的密文之后,会用自己的私钥对其进行非对称解密,解密之后的明文就是客户端密钥,然后用客户端密钥对数据进行对称加密,这样数据就变成了密文。
然后服务器将加密后的密文发送给客户端。
3.客户端收到服务器发送来的密文,用客户端密钥对其进行对称解密,得到服务器发送的数据。这样HTTPS中的第二个HTTP请求结束,整个HTTPS传输完成。
总的来说,对数据进行对称加密,对称加密所要使用的密钥通过非对称加密传输。
https://yq.aliyun.com/articles/666103
13.自登录保存用户信息怎么保证安全性
方案1.token+https:登录之后由服务端生成token,一般是由用户信息+设备信息+时间组成。token保存在本地,自登录时再由
服务器判断是有有效和过期。网络协议采用https,传输过程是密文传输,因此不担心窃取
方案2.RSA+随机数(加盐):将登录信息+时间用RSA公钥加密传给服务端,服务端私钥解密判断时间再验证登录,验证通过,
生成随机salt,以用户id为key,缓存salt值。将用户名+salt用公钥加密,返回给客户端保存。下次登录直接传加密串,验证缓存中salt值是否相等。
方案2的好处是不需要https加密保护,还可利用清楚salt缓存强制再次登录,在加密串失窃后不必修改密码
二.图片处理
1.像素格式
ALPHA_8
表示8位Alpha位图,即A=8,一个像素点占用1个字节,它没有颜色,只有透明度
ARGB_4444
表示16位ARGB位图,即A=4,R=4,G=4,B=4,一个像素点占4+4+4+4=16位,2个字节 ,不推荐,画质太差
ARGB_8888
表示32位ARGB位图,即A=8,R=8,G=8,B=8,一个像素点占8+8+8+8=32位,4个字节
RGB_565
表示16位RGB位图,即R=5,G=6,B=5,它没有透明度,一个像素点占5+6+5=16位,2个字节
2.图片压缩处理
https://www.jianshu.com/p/a52c31b45d6b
3.为什么选择Glide
1.链式操作,代码简洁
2.可以加载gif
3.默认像素格式使用的是RGB565,加载imageview所需要的精确尺寸,省内存,速度快
4.Glide控制生命周期
加载图片时取得context,然后再上面追加一个fragment,目的是为了拿到activity的生命周期,
在destroy的时候取消图片加载任务
5.bitmap定宽不定高压缩
Matrix matrix = new Matrix();
matrix.setScale(0.5f, 1f); //只设置宽或者高
bm = Bitmap.createBitmap(bit, 0, 0, bit.getWidth(),
bit.getHeight(), matrix, true);
ps:如果是定宽不定高加载,直接使用imageview的scaletype缩放显示即可
6.缓存策略&Lru算法
图片缓存策略一般是采取三级缓存,内存缓存、本地缓存和网络缓存
1).内存缓存LruCache
初始化,分配内存缓存容量
int maxMemory = (int) (Runtime.getRuntime().maxMemory()/1024);
int cacheSizw = maxMemory/8;
mMemoryCache = new LruCache<String,Bitmap>(cacheSize){
@Override
protected int sizeOf(String key,Bitmap bitmap){
return bitmap.getRowBytes()*bitmap.getHeight()/1024;
}
};
然后便是使用时的添加和获取方法了
mMemoryCache.put(key.bitmap);
mMemoryCache.get(key);
2).本地缓存 DiskLruCache
初始化可用内存和缓存位置
private static final long DISK_CACHE_SIZE = 1024*1024*50;//50M
File diskCacheDisk = getDiskCacheDir(mContext,"bitmap");
if(!diskCacheDisk.exists())
diskcacheDisk.mkdirs();
//参数分别为缓存位置,版本号,单个节点对应个数,缓存大小
mDiskLruCache = DiskLruCache.open(diskCacheDisk,1,1,DISH_CACHE_SIZE);
DiskLruCache的使用需要通过Editor
new Thread(new Runnable() {
@Override
public void run() {
try {
String imageUrl = "...";
String key = hashKeyForDisk(imageUrl);//MD5,url有特殊字符,不能直接使用
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {
OutputStream outputStream = editor.newOutputStream(0);
//将图片下载到outputStream
if (downloadUrlToStream(imageUrl, outputStream)) {
editor.commit();
} else {
editor.abort();
}
}
mDiskLruCache.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
//获取缓存
String imageUrl = "...";
String key = hashKeyForDisk(imageUrl);
DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
if (snapShot != null) {
InputStream is = snapShot.getInputStream(0);
Bitmap bitmap = BitmapFactory.decodeStream(is);
mImage.setImageBitmap(bitmap);
}
内存缓存和本地缓存都是基于LRU算法,即Least Recently Used最近最少使用算法
LRU底层实现是基于LinkedHaspMap,通过LinkedHaspMap的header的双向链表实现,header的after就是最近最少使用的,删除的都是这个节点
https://cloud.tencent.com/developer/article/1340759
三.其他
1.冲突的时候改写子view
冲突的时候一般采取外部拦截法,即改写父view。在父view的onIterceptTouchEvent需要拦截的地方
返回true即可。(一般是ACTION_MOVE里面,ACTION_DOWN不可拦截,拦了的话事件就传不到子view了)
也可采取内部拦截法,改写子view。子view没有onIterceptTouchEvent,在dispatchTouchEvent里面
配合parent.requestDisallowInterceptTouchEvent()拦截
public boolean dispatchTouchEvent(MotionEvent event){
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:{
getParent().requestallowInterceptTouchEvent(true); //传true表示父view不拦截
break;
}
case MotionEvent.ACTION_MOVE:{
if(父view需要此点击事件){
getParent().requestallowInterceptTouchEvent(false); //传false表示父view拦截
}
break;
}
}
return super.dispatchTouchEvent(event);
}
此外父view也要默认拦截除了ACTION_DOWN以外的其他事件,子view调用requestallowInterceptTouchEvent(false)才能生效
public boolean onIterceptionTouchEvent(MotionEvent event){
int action = event.getAction();
if(action == MotionEvent.ACTION_DOWN)
return false;
else
return true;
}
2.handle为什么会持有activity对象
在java中非静态内部类和匿名内部类都会持有当前类的外部引用,所以在activity中使用
handler,handler就会持有activity的隐式引用。这样activity无法被回收,会导致内存泄漏。
解决办法:将handler声明为静态内部类,并弱引用activity
static class MyHandler extends Handler {
WeakReference<Activity > mActivityReference;
MyHandler(Activity activity) {
mActivityReference= new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
final Activity activity = mActivityReference.get();
if (activity != null) {
tv_text.setText(msg.what.toString());
}
}
}
3.Application的content和activity service的content有什么不同
Application content是整个应用内有效的,activity、service的content是
组件生命周期内有效。所以单例类不可以持有activity、service的content,要使用context.getApplicationContext(),防止内存泄漏
Android与js的交互
1.webView的loadUrl("javascript.a()"//javascript定义的方法)
2.定义javascriptIntereface在本地方法给js文件调用
public class JsObject {
private final static String TAG = "TAG_JsObject";
@JavascriptInterface
public void onSumResult(int result){
Log.d(TAG, "result = " + result);
}
}
Rxbus怎么异步通知
定义RxBus类
public class RxBus {
private final Subject<Object, Object> _bus;
private volatile static RxBus instace;
private RxBus() {
//使用PublishSubject把在订阅发生的时间点之后来自原始Observable的数据发射给观察者
_bus = new SerializedSubject<>(PublishSubject.create());
}
public static RxBus getInstace() {
if (instace == null) {
synchronized (RxBus.class) {
if (instace == null)
instace = new RxBus();
}
}
return instace;
}
public void send(Object o) {
_bus.onNext(o);
}
//配合rxlifecycle使用,管理生命周期
public Observable<Object> toObserverable(LifecycleTransformer lifecycleTransformer) {
return _bus.compose(lifecycleTransformer);
}
}
发送通知
RxBus.getInstace().send(ActivityEventEntity.REFRESH.getValue());
接收通知
RxBus.getInstace().toObserverable(
bindUntilEvent(ActivityEvent.DESTROY)) //DESTROY时销毁
.subscribe(new Action1<Object>() {
@Override
public void call(Object event) {
if ((int) event == ActivityEventEntity.REFRESH.getValue()) {
...
}
}
});
系统启动流程
首先会启动引导程序bootloader,接着启动init进程;
init进程启动zygote进程,zygote首先启动systemServer进程,初始化硬件设备和服务;
systemServer会启动ActivityManagerService,WindowManagerService等,AMS的方法resumeTopActivityLocked会打开桌面launcher。
应用启动流程
launcher远程调用ActivityManagerService,要打开一个新的进程;
等确认可以打开新进程后,ActivityManagerService会请求zygote进程为应用fork出一个新进程并实例化Activityhread;
调用ActivityThread.bindApplication绑定应用的application
activity启动过程
activity启动会来到ActivityManagerService类(Binder对象),通过IPC远程调用ActivityThread的内部类ApplicationThread,
通过scheduleLaunchActivity方法将启动activity的消息交由Handler H处理,
H会调用handlerLaunchActivity-performLaunchActivity方法创建和启动Activity对象
卸载后打开一个页面
调用c层jni接口,fork一个新进程,负责监听本应用是否被卸载了。Linux中父进程死了,fork复制出来的子进程不会被杀死。
监听data/data/下应用的缓存文件夹是否还存在,不在则调用android浏览器,打开页面
系统里面用到的设计模式
mvp存在问题
1.代码增多,增加很多类
2.耦合度高,界面改动,涉及到的接口也要变化;
3.P持有V对象,如果view销毁时,P还在做耗时操作,可能会内存泄漏。
解决:在basePresenter加attach和detach方法,attach获取泛型view,detach释放view和中止网络。activity销毁时调用detach即可(可在baseActivity中实现)
https://www.jianshu.com/p/2fd2d5dac94e
mvvm
m-model,v-view,vm-viewmodel。
mvp模式缺陷:p需要持有view对象,要管理生命周期;
耦合度高,界面改动,涉及到的接口也要变化;
p有可能臃肿
而mvvm中vm不需要持有v对象,通过databinding可以做到数据驱动,改变数据即可改变ui,解耦和更新ui方便(不用切换到主线程),复用性高
binder过程
服务端通过service提供binder,客户端通过asInterface方法将binder转为代理proxy,并通过它发起远程请求,然后挂起;
binder写入参数,然后调用transact方法,服务端会由onTransact方法处理,将结果写入reply;
最终服务端返回请求结果,唤醒客户端
serializable和parcelable区别
serializable是java自带的,parcelable是android专用的。
serializable优点实现起来简单,只需要实现serializable接口即可。缺点使用反射实现序列化,开销大效率慢易触发GC
parcelable优点效率快,缺点实现复杂,需要实现parcelable接口,然后复写describeContents和writeToPracel方法,实例化静态化内部变量CREATOR。
handler机制
当调用handler.sendMessage方法时,会通过enqueueMessage方法将发送的message加入消息队列MessageQueue
Looper.loop方法会一直去轮询MessageQueue.next
调用handler.dispatchMessage发送消息,handler收到消息调用handlerMessage方法处理消息
handler postdely延时不准
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
handler的sendMessageDelayed方法一开始就计算好了执行的时间点是系统启动至今的时间+延时时间,
在MessageQueue.next方法判断消息是否出列时,是根据当前时间和msg的时间判断的
Message next() {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
...
return msg;
}
}
所以如果上一个消息有耗时任务,占用了延时任务执行的时机,就会不准确
RxJava流程
首先通过observable.create方法创建被观察者,其他操作符最后也是返回一个observable对象;
然后调用subscribe方法绑定观察者,入参observer,方法会来到subscribeActual方法,在里面会创建CreateEmitter发射器对象;
继续执行observable.subscribe(发射器),接着触发事件源发射事件。
而subscribeOn和observerOn两个切换线程的原理,切换到子线程是通过线程池新开一个runnable,在里面执行订阅或观察。
切换到主线程则是利用参数mainThread返回的handler进来postDelayed方法切换的
路由
现在开发都建议模块化开发,即是多module。好处如下:
1.可以独立模块编译,提升速度
2.方便单元测试
3.有利于团队并发开发
多module带来不好的地方就是多module要跳转的话就要互相依赖,导致耦合度高。为了解决跳转问题所以需要引入路由框架。
路由可以达到类似web的跳转方式,根据路径跳转。将router配置在common module就可以解耦
使用可以考虑阿里的ARouter框架。只要在activity前注入@Route(path = "/com/Activity1") ,
使用 ARouter.getInstance().build("/com/Activity1").navigation() 就可以进行跳转
启动优化
启动分为冷启动(首次启动),热启动(任务列表存在该进程)。优化方案:
1.在application和mainactivity的初始化不做耗时操作,可以开线程,也可以延时操作
2.用导航图作为windowBackground,替换掉冷启动系统准备时间的白屏或黑屏,优化体验
3.返回键退出处理为home退出,用热启动替换冷启动moveTaskToBack(true)
java
1.java的封装
是面向对象的重要原则。是将对象的属性和操作包装成一个独立的实体,
并隐藏对象的内部实现,向外界提供一些可访问的属性操作。
比如说实体类提供部分属性set方法和get方法
2.java四种引用及使用场景
强引用:默认调用就是强引用,永远不会被GC回收
软引用:当内存不足时,会被GC回收,用于存储一些内存敏感的缓存
SoftReference<String> softBean = new SoftReference<String>(new String[]{"a", "b", "c"});
String a = softBean.get();
弱引用:不管内存足否,只要被发现,都会被GC回收,用于存储一些内存敏感的缓存
WeakReference<String> weakBean = new WeakReference<String>(new String[]{"a", "b", "c"});
String a = weakBean .get();
虚引用:和没有任何引用一样,在任何时候都可能被垃圾回收器回收。主要用来跟踪对象被垃圾回收器回收的活动。
ReferenceQueue<String[]> referenceQueue = new ReferenceQueue<String[]>();
PhantomReference<String[]> referent = new PhantomReference<>("a", referenceQueue);
3.线程start和run区别
run是同步方法,运行在主线程
start才是真正实现了多线程
重载和重写(kotlin的重载)
1.ArrayList和LinkedList区别
ArrayList是基于动态数组的数据结构,而LinkedList是基于双向链表的数据结构
随机set和get,ArrayList较快,因为LinkedList要移动指针
add和remove字段,LinkedList较快,因为ArrayList要移动数据
2.循环arraylist删除方法
方法1:普通倒序循环
public static void remove(ArrayList<String> list) {
for (int i = list.size() - 1; i >= 0; i--) {
String s = list.get(i);
if (s.equals("bb")) {
list.remove(s);
}
}
}
之所以要采取倒序,是因为正序的话,删除了第一个“bb”,下一个元素会往左移动,这次遍历就错过了,
如果也是bb就删除不了重复元素。倒序遍历时即使发生元素删除也不影响后序元素遍历。
方法2:迭代器循环删除()
public static void remove(ArrayList<String> list) {
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if (s.equals("b")) {
it.remove();
}
}
}
多线程中不能保证两个变量修改的一致性,结果具有不确定性,所以不推荐这种方法
如果是用强循环,并用arraylist的remove方法会报ConcurrentModificationException并发修改异常
4.静态方法和实例方法用sync锁是锁住什么
synchronized加在静态方法是锁类,加在非静态方法是锁对象。
1. 如果多线程同时访问同一类的类锁(synchronized 修饰的静态方法)以及对象
锁(synchronized 修饰的非静态方法)这两个方法执行是异步的,原因:类锁和对象锁是两种不同的锁。
2. 类锁对该类的所有对象都能起作用,而对象锁不能。
5.单例最好写法
1.双重检查+volitile
public class Singleton {
private static volatile Singleton singleton; //1
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) { //2
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
双重检查避免大开销,避免两个线程同时进入2,在同步方法里面在检查一次,保证只实例化一次
volitile避免线程1刚实例化完,线程2走到1时singleton还不是最新的,又实例化一次。
volitile读写时都会到主线程同步最新的变量
2.静态内部类
public class Singleton {
private Singleton() {}
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
类的静态属性只会在第一次加载类的时候初始化,在类进行初始化时,别的线程是无法进入的。
优点:避免了线程不安全,延迟加载,效率高。
3.枚举法
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象
6.注解和反射
https://www.jianshu.com/p/d4978bbce12a
Android任务栈分配
保活
栈倒序冒泡
Service生命周期启动
a++跟++a
a++是先赋值,再运算。a=0;x=a++先将a赋予x,后再运算a=a+1,所以x=0
++a是先运算,再赋值。a=0;x=++a先是运算a=a+1,然后才是x=a
sleep和wait区别
sleep是thread的方法,没有释放锁
wait是object方法,释放了锁
hashcode是否相等,当对象不相等
hashmap底层
HashMap是基于hashing的原理,我们使用put(key, value)存储对象到HashMap中,
使用get(key)从HashMap中获取对象。当我们给put()方法传递键和值时,我们先对键调用hashCode()方法,
返回的hashCode用于找到bucket位置来储存Entry对象
克隆,复制一个对象
idhandler(空闲handler) activity什么时候开始显示的
静态方法为什么可以全局调用(不需要外部引用)
单例为什么要双重检查
线程池到达核心数,到达缓存数,参数之一策略
场景:图片上传外再调评论接口(锁)
7.摘星者
transient关键字
rxjava同步方法,取消订阅关系
Glide底层
广汽
HashMap链表什么时候变红黑树
jvm分区
classloader
为什么用binder
volicate解决什么问题
leakcanary原理
mvvm数据混乱怎么办
路由原理
怎么保证一个线程一个looper
网友评论