系统出于体验和性能上的考虑,app在退到后台时系统并不会真正的kill掉这个进程,而是将其缓存起来。
打开的应用越多,后台缓存的进程也越多。
在系统内存不足的情况下,系统开始依据自身的一套进程回收机制来判断要kill掉哪些进程,以腾出内存来供给需要的app, 这套杀进程回收内存的机制就叫 Low Memory Killer。
内存阈值在不同的手机上不一样,一旦低于该值,Android便会杀死对应优先级的进程,例如,当可用内存小于315MB(80640),就杀死空进程。内存阈值越低,进程的优先级越高。
进程的优先级:前台进程->可见进程->服务进程->后台进程->空进程
String中"=="与equals()的区别
"=="比较的是内存中存放的位置,是在常量池中还是在堆内存中,String a = "123",a存储在常量池中,String b = new String("123"),b对象存放在堆内存中;
equals()比较的是字符序列,即字符串的内容是否相同,只要字符串的内容相同,他们的hashcode也是一样的
String使用private final char value[]来实现字符串的存储,也就是说String对象创建之后,就不能再修改此对象中存储的字符串内容,就是因为如此,才说String类型是不可变的(immutable)。 然而,String类对象确实有编辑字符串的功能,比如replace()。这些编辑功能是通过创建一个新的对象来实现的,而不是对原有对象进行修改。
单独使用""引号创建的字符串都是常量,编译期就已经确定存储到String Pool(字符串常量池)中;
使用new String("")创建的对象会存储到heap(堆)中,是运行期新创建的;
Java中的堆和栈的区别
堆和栈都是内存中的一部分,有着不同的作用,而且一个程序需要在这片区域上分配内存。众所周知,所有的Java程序都运行在JVM虚拟机内部,我们这里介绍的自然是JVM(虚拟)内存中的堆和栈。
最主要的区别就是栈内存用来存储局部变量和方法调用。 而堆内存用来存储Java中的对象。无论是成员变量,局部变量,还是类变量,它们指向的对象都存储在堆内存中。
栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存。 而堆内存中的对象对所有线程可见。堆内存中的对象可以被所有线程访问。
如果栈内存没有可用的空间存储方法调用和局部变量,JVM会抛出java.lang.StackOverFlowError。 而如果是堆内存没有可用的空间存储生成的对象,JVM会抛出java.lang.OutOfMemoryError。
栈的内存要远远小于堆内存,如果你使用递归的话,那么你的栈很快就会充满。如果递归没有及时跳出,很可能发生StackOverFlowError问题。 你可以通过-Xss选项设置栈内存的大小。-Xms选项可以设置堆的开始时的大小,-Xmx选项可以设置堆的最大值。
Java 内存区域中比较重要也是经常被提到的几部分是:程序计数器,栈(Stack),堆(Heap)和方法区(包含常量池等),它们都定义在被称作运行时数据区(Runing Data Area)的区域中。
其中程序计数器与栈(Stack)是随线程启动而生,线程结束而灭的,也就属于线程私有。而堆(Heap)和方法区是由JVM启动时创建切被所有线程共享的。
java垃圾回收
详细内容可以参考这篇文章www.cnblogs.com/czwbig/p/11…
判断哪些对象需要被回收,有以下两种方法:
引用计数法
给对象添加一引用计数器,被引用一次计数器值就加 1;当引用失效时,计数器值就减 1;计数器为 0 时,对象就是不可能再被使用的,简单高效,缺点是无法解决对象之间相互循环引用的问题。 可达性分析算法
通过一系列的称为 "GC Roots" 的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是不可用的。此算法解决了上述循环引用的问题。
在Java, 可作为GC Roots的对象包括:
- 方法区: 类静态属性的对象(静态变量);
- 方法区: 常量的对象(常量);
- 虚拟机栈(本地变量表)中的对象.
- 本地方法栈JNI(Native方法)中的对象。
GC到来时,如果对象没有直接或者间接引用到GC Roots的对象,那么就会被回收。
java快速排序
private void quickSort(int[] array, int start, int end) {
if (start > end) {
return;
}
//以第一个数为基准数
int base = array[start];
int i = start;
int j = end;
while (i < j) {
//先从右边找起,直到找到比基准数小的数
while (array[j] >= base && i < j) {
j--;
}
//再从左边找起,直到找到比基准数大的数
while (array[i] <= base && i < j) {
i++;
}
//执行到这,说明上面的循环都停止了,交换两个数
if (i < j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
//跳出了循环,说明i = j 交换基准数的位置
array[start] = array[i];
array[i] = base;
//递归执行基准数左边和右边的
quickSort(array, start, i - 1);
quickSort(array, i + 1, end);
}
Synchronized关键字
Synchronized可以修饰代码块,修饰成员方法,修饰静态方法。
Synchronized修饰成员方法时,用的锁是this,也就是调用该方法的类对象,修饰静态方法时,用的是Class对象。
java 内部类 内存泄露
在Java中内部类有普通内部类,匿名内部类与静态内部类,前两者的对象都会隐式持有外部类对象的引用,影响外部类对象的回收。
写一个列子通过javac命令编译一下.java源文件,得到几个.class字节码文件,用jd-gui字节码反编译工具打开,可以发现好几个.class文件。通过分析,可以知道:
普通内部类,匿名内部类的class文件中有构造函数,并在构造函数传入了内部类的实例作为参数
不管匿名内部类是用作定义成员变量还是方法传参:
//匿名内部类1 用作成员变量
private StaticInner si1 = new StaticInner(){
@Override
public void doAction() {
count++;
}
};
//匿名内部类2 用来方法传参
setInner(new StaticInner(){
@Override
public void doAction() {
super.doAction();
count++;
}
});
解决方案为
-
将内部类定义为static
-
用static的变量引用匿名内部类的实例
java数据结构
Java中有几种常用的数据结构,主要由Collection和map两个主要接口派生。
一、几个常用类的区别
-
ArrayList: 元素单个,效率高,多用于查询
-
Vector: 元素单个,线程安全,多用于查询
-
LinkedList:元素单个,多用于插入和删除
-
HashMap: 元素成对,元素可为空
-
HashTable: 元素成对,线程安全,元素不可为空
如果能用数组的时候(元素类型固定,数组长度固定),请尽量使用数组来代替List;如果没有频繁的删除插入操作,又不用考虑多线程问题,优先选择ArrayList;如果在多线程条件下使用,可以考虑Vector;如果需要频繁地删除插入,LinkedList就有了用武之地。
在Java集合类框架里有两个类叫做Collections和Arrays,是强大的工具。Collections类提供了丰富的静态方法帮助我们轻松完成这些在数据结构课上烦人的工作:
binarySearch:折半查找。
sort:排序,这里是一种类似于快速排序的方法,效率仍然是O(n * log n),但却是一种稳定的排序方法。
reverse:将线性表进行逆序操作,这个可是从前数据结构的经典考题哦!
rotate:以某个元素为轴心将线性表“旋转”。
swap:交换一个线性表中两个元素的位置。
...
Set是最简单的一种集合。集合中的对象不按特定的方式排序,并且没有重复对象。 Set接口主要实现了两个实现类:
HashSet: HashSet类按照哈希算法来存取集合中的对象,存取速度比较快
TreeSet :TreeSet类实现了SortedSet接口,能够对集合中的对象进行排序。
链表
可以参考www.cnblogs.com/MWCloud/p/1…
- 链表是以节点的方式来存储,是链式存储
- 每个节点包含 data 域, next 域:指向下一个节点.
- 链表的各个节点不一定是连续存储.
链表的特点是:寻址困难,插入和删除容易。
面试题:单链表的反转
/**
* 将单链表反转 思路是遍历 将当前节点指向上一个节点,最后将头节点指向改一下即可
*/
public void reverseList(HeroNode head) {
// 如果当前链表为空,或者只有一个节点,无需反转,直接返回
if (head.next == null || head.next.next == null) {
return;
}
// 定义一个辅助的指针(变量),帮助我们遍历原来的链表
HeroNode cur = head.next;
HeroNode next = null; //指向当前节点[cur]的下一个节点
HeroNode reverseHead = new HeroNode(0, "", "");
// 遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表 reverseHead的最前端
while (cur != null) {
next = cur.next; // 先暂时保存当前节点的下一个节点
cur.next = reverseHead.next; // 将cur的下一个节点指向新的链表的最前端
reverseHead.next = cur; // 将cur链接到新的链表上
cur = next; // 将cur后移
}
// 将head.next指向reverseHead.next,实现单链表的反转
head.next = reverseHead.next;
}
网络相关
默认情况下,Service是运行在主线程的,是不能执行耗时操作的。
无序广播:完全异步,逻辑上可以被任何广播接收者接收到。优点是效率较高。缺点是一个接收者不能将处理结果传递给下一个接收者,并无法终止广播 intent 的传播。
有序广播:按照被接收者的优先级顺序,在被接收者中依次传播。比如有三个广播接收者 A,B,C,优先级是 A > B > C。那这个消息先传给 A,再传给 B,最后传给 C。每个接收者有权终止广播,比如B终止广播,C就无法接收到。此外A接收到广播后可以对结果对象进行操作,当广播传给 B 时,B 可以从结果对象中取得 A 存入的数据。
ArrayMap/SparseArray
在Android上建议使用ArrayMap代替Hashmap
SparseArray在Android内部用来替代HashMap<Integer,E>这种形式,使用SparseArray更加节省内存空间的使用
避免在Android里面使用到枚举
11.横竖屏切换时Activity的生命周期 此时的生命周期跟清单文件里的配置有关系。
不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,默认首先销毁当前activity,然后重新加载。
设置Activity android:configChanges=”orientation|keyboardHidden|screenSize”时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法
网络分层
- 应用层 主要协议有http,ftp,telnet等
- 传输层 该层为两台主机上的应用程序提供端到端的通信。两个传输协议:TCP和UDP。TCP是可靠的面向连接的协议,UDP是不可靠的或者说无连接的协议
- 网络层 建立主机到主机的通信
- 数据链路层
- 物理层
TCP的三次握手与四次挥手
TCP建立连接需要三次握手,断开连接需要四次挥手
HTTp协议原理
http1.1 97年发布,最流行的版本
http2 15年新发布的版本
http协议的主要特点
- 支持c/s模式
- 简单快速
- 灵活
- 无连接 限制每次连接只处理一次请求
- 无状态 协议对于事务处理没有记忆能力
HTTP有两种报文,请求报文和响应报文
HTTP请求报文
一般由请求行,请求报头,空行,请求数据组成
//请求行的组成
Method Request-URI HTTP-Version CRLF
请求方法 URL字段 HTTP协议版本 回车和换行
//如
GET http://blog.csdn.net/itachi85 HTTP/1.1
//请求报头是键值对,由:分割,有0个或多个 如
Host:主机名
User-Agent:浏览器类型,操作系统等信息
Accept:客户端可识别的内容列表
Connection:如Keep-Alive
GET请求没有请求数据,POST请求才有
HTTP响应报文
由状态行,响应报头,空行,响应正文组成
//状态行格式
HTTP-Version Status-Code Reason-Phrase CRLF
http协议版本 状态码 文本描述 回车和换行
//如
HTTP/1.1 200 OK
//响应报头是键值对,由:分割,有0个或多个 如
Location:新地址,常用在跟换域名时
Server:服务器系统信息
//状态码有5种可能取值
200~299:请求成功
400~499:客户端错误
500~599 服务端错误
//抓包 windows中可用Fiddler 下载地址
https://www.telerik.com/download/fiddler
java设计模式
责任链模式
责任链,顾名思义,就是用来处理相关事务责任的一条执行链,执行链上有多个节点,每个节点都有机会(条件匹配)处理请求事务,如果某个节点处理完了就可以根据实际业务需求传递给下一个节点继续处理或者返回处理完毕。可以参考这篇文章
场景:现实中,请假的OA申请,请假天数如果是半天到1天,可能直接主管批准即可; 如果是1到3天的假期,需要部门经理批准; 如果是3天到30天,则需要总经理审批; 大于30天,正常不会批准。
为了实现上述场景,我们可以采用责任链设计模式。
- 员工提交请求类:LeaveRequest。
- 抽象的请假责任处理类:AbstractLeaveHandler。
- 直接主管审批处理类:DirectLeaderLeaveHandler。
- 部门经理处理类:DeptManagerLeaveHandler。
- 总经理处理类: GManagerLeaveHandler。
员工请求发起申请到抽象的责任处理类中,根据员工的请假天数,对应的处理类完成处理。 每一个责任处理类设置下面的节点。自身处理不了则传递给下一个节点处理。
okhttp处理请求也用到了责任链模式,在getResponseWithInterceptorChain()方法中,会一一调用各种拦截器,直到最后的拦截器返回Response
okhttp
okhttp的网络请求流程
这里基于okhttp3 3.10版本去解析
okhttp的基本用法如下
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url("http://www.baidu.com").build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.i(TAG, "onResponse: " + response.code());
}
});
可以看到client的newCall方法返回了Call对象,call对象执行了enqueue方法
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
newCall返回的是ReqlCall对象,那么执行的也是newCall的enqueue方法
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
可以看到调用了Dispatcher的enqueue方法,那么Dispatcher是什么呢
//最大并发请求数
private int maxRequests = 64;
//每个主机的最大请求书
private int maxRequestsPerHost = 5;
private @Nullable Runnable idleCallback;
/** Executes calls. Created lazily. */
//线程池
private @Nullable ExecutorService executorService;
/** Ready async calls in the order they'll be run. */
//将要运行的异步请求队列
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
//正在运行的异步请求队列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
//正在运行的同步请求队列
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
可以看到Dispatcher里面有三个队列,线程池等
现在再看看Dispatcher的enqueue方法做了什么
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
Dispatcher的enqueue方法只是添加请求到runningAsyncCalls队列,并用线程池执行请求。
请求是AsyncCall类型,那么请求的具体执行是什么呢
final class AsyncCall extends NamedRunnable {}
public abstract class NamedRunnable implements Runnable{
...
protected abstract void execute();
}
可以看到AsyncCall本质上是一个Runnable,定义了一个execute()方法,真正的处理请求就在AsyncCall的execute方法
@Override protected void execute() {
boolean signalledCallback = false;
try {
Response response = getResponseWithInterceptorChain();
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
eventListener.callFailed(RealCall.this, e);
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
核心代码就是getResponseWithInterceptorChain()方法,该方法返回了我们需要的Response
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);//1
}
getResponseWithInterceptorChain方法里面添加了各种的拦截器,okhttp内置了5个拦截器
-
RetryAndFollowUpInterceptor
在网络请求失败后进行重试 当服务器返回当前请求需要进行重定向时直接发起新的请求,并在条件允许情况下复用当前连 接
-
BridgeInteceptor
设置内容长度,内容编码 设置gzip压缩,并在接收到内容后进行解压。省去了应用层处理数据解压的麻烦 添加cookie 设置其他报头,如User-Agent,Host,Keep-alive等。其中Keep-Alive是实现多路复用的必要步骤
-
CacheInterceptor
当网络请求有符合要求的Cache时直接返回Cache 当服务器返回内容有改变时更新当前cache 如果当前cache失效,删除
-
ConnectInterceptor
为当前请求找到合适的连接,可能复用已有连接也可能是重新创建的连接,返回的连接由连接池负责决定。
-
CallServerInterceptor
负责向服务器发起真正的访问请求,并在接收到服务器返回后读取响应返回。
注意上面代码注释1处调用了RealInterceptorChain的proceed方法
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
...
// Call the next interceptor in the chain. 核心代码
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
...
return response;
}
省略了部分代码,核心代码如上所示。
可以看到构造了一个next的对象,类型是RealInterceptorChain,然后从拦截器列表中取出一个拦截器,并调用拦截器的intercept(next)方法,把next传进去了。上面说到okhttp内置了五个拦截器,第一个是RetryAndFollowUpInterceptor
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
int followUpCount = 0;
Response priorResponse = null;
while (true) {
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response;
boolean releaseConnection = true;
try {
//1 核心代码
response = realChain.proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
throw e.getLastConnectException();
}
releaseConnection = false;
continue;
} catch (IOException e) {
// An attempt to communicate with a server failed. The request may have been sent.
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
releaseConnection = false;
continue;
} finally {
// We're throwing an unchecked exception. Release any resources.
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
// Attach the prior response if it exists. Such responses never have a body.
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
Request followUp = followUpRequest(response, streamAllocation.route());
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
closeQuietly(response.body());
...
}
intercept方法很长,重点代码在注释1处,看到realChain调用了proceed方法,realChain是RealInterceptorChain对象,也就是上面传入的next对象,调用又回到了RealInterceptorChain的proceed方法
//此时index是1
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
//取出的拦截器是BridgeInteceptor
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
再到BridgeInteceptor的intercept方法看看
@Override public Response intercept(Chain chain) throws IOException {
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
...
Response networkResponse = chain.proceed(requestBuilder.build());
...
果然看到了proceed方法,又回到了RealInterceptorChain的proceed方法,此时取出的拦截器是CacheInterceptor,毫无意外CacheInterceptor里面也调用了proceed方法,此时传递到了下一个拦截器,如此重复,知道最后一个拦截器返回。
最后一个拦截器是CallServerInterceptor,它的intercept方法没有调用proceed,有如下代码
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
CallServerInterceptor是最后一个拦截器,返回了Response,一步步返回,最终得到了getResponseWithInterceptorChain()方法的返回值。
拦截器调用下一个拦截器,任务一步步传递下去的模式,就是责任链模式,本文上面有提到。
okhttp的复用连接池
连接池的类位于okhttp3.ConnectionPool,主要变量如下
private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));
/** The maximum number of idle connections for each address. */
private final int maxIdleConnections;
private final long keepAliveDurationNs;
private final Runnable cleanupRunnable = new Runnable() {
@Override public void run() {
while (true) {
long waitNanos = cleanup(System.nanoTime());
if (waitNanos == -1) return;
if (waitNanos > 0) {
long waitMillis = waitNanos / 1000000L;
waitNanos -= (waitMillis * 1000000L);
synchronized (ConnectionPool.this) {
try {
ConnectionPool.this.wait(waitMillis, (int) waitNanos);
} catch (InterruptedException ignored) {
}
}
}
}
}
};
private final Deque<RealConnection> connections = new ArrayDeque<>();
final RouteDatabase routeDatabase = new RouteDatabase();
boolean cleanupRunning;
有线程池,有Deque双向队列,维护了RealConnection也就是socket物理连接的包装
看看构造方法
public ConnectionPool() {
this(5, 5, TimeUnit.MINUTES);
}
public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
this.maxIdleConnections = maxIdleConnections;
this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);
// Put a floor on the keep alive duration, otherwise cleanup will spin loop.
if (keepAliveDuration <= 0) {
throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
}
}
看出默认空闲的socket最大连接数为5个,socket的keepAlive时间为5分钟
ConnectionPool的缓存功能主要由Deque<RealConnection> connections完成,提供了put,get,connectionBecameIdle,evictAll方法,分别对应放入连接,获取连接,移除连接和移除所有连接。
ConnectionPool还有自动回收连接的功能
void put(RealConnection connection) {
assert (Thread.holdsLock(this));
if (!cleanupRunning) {
cleanupRunning = true;
executor.execute(cleanupRunnable);
}
connections.add(connection);
}
看到put方法里面会执行cleanupRunnable任务,然后执行cleanup方法进行清理。
屏幕适配
今日头条的屏幕适配方案
- px: 像素,我们平常说的分辨率1080*1920的屏幕,单位就是px,即横屏上有1080个物理像素点,竖屏上有1920个物理像素点。
- dpi: (dot per inch)像素密度,用来描述单位屏幕上像素点的多少,越多越清晰。举个例子:屏幕分辨率为 1920 * 1080,屏幕尺寸为5寸(屏幕斜边长度cm/0.3937), 则 dpi = √(宽度²+ 高度²)/屏幕尺寸
- dp: 像素无关密度,安卓专用的单位。
- density: 密度,density = dpi/160
px = dpi/160*dp
px = density*dp
density = dpi/160
从dp和px的转换公式 :px = dp * density
可以看出,如果设计图宽为360dp,想要保证在所有设备计算得出的px值都正好是屏幕宽度的话,我们只能修改 density 的值。
github上面开源的一个基于今日头条适配方案的一个库 github.com/JessYanCodi…
还有另外一种流行的屏幕适配方案:smallestWidth 参考这篇文章 www.jianshu.com/p/2aded8bb6…