#TODO
目录在简书平台不可用,误点!!!
网络模块 面试主要涉及到 应用层(HTTP DNS等) 和 传输层 (TCP UDP)的东西,
TCP 连接 :传输可靠;有序;面向字节流;速度慢;较重量;全双工; 适用于文件传输、浏览器等
全双工:A 给 B 发消息的同时,B 也能给 A 发
半双工:A 给 B 发消息的同时,B 不能给 A 发
UDP 无连接 :传输不可靠;无序;面向报文;速度快;轻量; 适用于即时通讯、视频通话等
三次握手四次挥手
参考文章
HTTP 是超文本传输协议,明文传输;HTTPS 使用 SSL 协议对 HTTP 传输数据进行了加密
HTTP 默认 80 端口;HTTPS 默认 443 端口
优点:安全
缺点:费时、SSL 证书收费,加密能力还是有限的,但是比 HTTP 更加安全,也是大势所趋。
Get 参数放在 url 中;Post 参数放在 request Body 中
Get 可能不安全,因为参数放在 url 中,并且对于传送的数据长度有限制。
清理算法
进程:进程是系统进行资源分配和调度的一个独立单位 (拥有独立内存空间),一个app就是一个进程。
线程:是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一些在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
bundle : 由于Activity,Service,Receiver都是可以通过Intent来携带Bundle传输数据的,所以我们可以在一个进程中通过Intent将携带数据的Bundle发送到另一个进程的组件。(bundle只能传递三种类型,一是键值对的形式,二是键为String类型,三是值为Parcelable类型)
ContentProvider :ContentProvider是Android四大组件之一,以表格的方式来储存数据,提供给外界,即Content Provider可以跨进程访问其他应用程序中的数据
文件 :两个进程可以到同一个文件去交换数据,我们不仅可以保存文本文件,还可以将对象持久化到文件,从另一个文件恢复。要注意的是,当并发读/写时可能会出现并发的问题。
Broadcast :Broadcast可以向android系统中所有应用程序发送广播,而需要跨进程通讯的应用程序可以监听这些广播。
AIDL :AIDL通过定义服务端暴露的接口,以提供给客户端来调用,AIDL使服务器可以并行处理。
Messager :Messenger封装了AIDL之后只能串行运行,所以Messenger一般用作消息传递
Socket
Handler 和 AsyncTask (AsyncTask:异步任务,内部封装了Handler)
wait(): 当一个线程执行到wait()方法时,它就进入到一个等待池中,同时释放对象锁,使得其他线程可以访问。用户可以使用notify,notifyAll或者指定睡眠时间来唤醒当前等待池中的线程。
sleep():该函数是Thread的静态函数,作用是使调用线程进入睡眠状态。因为sleep()是Thread类的Static方法,因为它不能改变对象的机制。所以,当在一个Synchronized块中调用sleep方法时,线程虽然休眠了,但是对象的机制并没有被释放,其他线程无法访问这个对象
注意:wait方法,notify方法,notifyAll方法必须放在synchronized block中,否则会抛出异常。
join():等待目标线程执行完成之后再继续执行
yield():线程礼让。目标线程由运行状态转换为就绪状态,也就是让出执行权限,让其他线程得以优先执行,但其他线程能否优先执行是未知的。
ArrayList :基于动态数组实现,所以查找快(O1) 增删慢 (On),如果创建方法无参数,默认数组长度为10,添加元素如果需要扩容则是新创建一个 数组,进行数据的转移拷贝。删除的时候 如果删除成功,后续元素需要通过System.arraycopy 进行元素移动(相当的低效)。改查 则比较迅速。
LinkedList : 底层基于双向链表,维持了 last 和 next 两个节点,所以 查找慢 (On) 增删快(O1),链表的查找是循环的(不过查找的过程是 先判断index是靠近哪一段 然后再进行查找 可以理解为 O(n/2)),但是速度还是慢。在添加和删除中,因为添加是直接放到链表尾部 但是删除存在 先循环一遍,然后删除的情况,不过 相对于ArrayList的复制要好的很多了。
SparseArray : key 只能为整数型,内部也是两个数组,一个存key,一个存val , 添加的时候 key 不用装箱,val则需要如果是基本类型,查找key还是用的二分法查找。也就是说它的时间复杂度还是O(logN)
HashMap : jdk8后由数组、链表和红黑树。当数组中的数据出现 hash冲突的时候启动链表,当链表中的 个数超过8个 将链表转为红黑树。允许 key val 为NULL,key和 val存储数据可以为任何类型(非基本类型 会自动装箱)。HashMap并不是线程安全的。
HashTable : 其实主体和 HashMap类似,但是写入和 读取方法 添加了 synchronize 可以做到 线程安全,但是效率没有HashMap高。
ArrayMap :key val 可以为 任意类型(非基本类型,会自动装箱),中有两个数组,一个存储key 的hash值,另外一个交叉存储 key val 数据(key val key val … 形式存储),
synchronized 参考文章
可重入锁 :已经获取到锁后,再次调用同步代码块/尝试获取锁时不必重新去申请锁,可以直接执行相关代码。 ReentrantLock 和 synchronized 都是可重入锁
公平锁 : 等待时间最久的线程会优先获得锁 , synchronized lock 默认都为非公平锁
锁主要分为:悲观锁 (线程一旦得到锁,其他线程就挂起等待。用于写入频繁的 synchronized)和 乐观锁(假设没有冲突,不加锁,更新数据时判断该数据是否过期,过期的话则不进行数据更新,适用于读取操作频繁的场景,比如 AtomicInteger、AtomicLong、AtomicBoolean)
锁的状态依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。锁可以从偏向锁升级到轻量级锁,再升级的重量级锁。但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级。
volatile :只能用来修饰变量,适用修饰可能被多线程同时访问的变量,相当于轻量级的synchronized,保证有序性和 可见性(变量位于主内存中,每个线程还有自己的工作内存,变量在自己线程的工作内存中有份拷贝,线程直接操作的是这个拷贝被 volatile 修饰的变量改变后会立即同步到主内存)。
synchronized :是java 的关键字(悲观锁),自动释放锁,并且无法中断锁。保证 原子性、可见性 和 有序性。
Lock : java 中的一个接口,lock 需要手动释放,所以需要写到 try catch 块中并在 finally 中释放锁,可以手动中断锁。
双重检查单例加volatile的原因 : 将instance =newInstance(); 创建实例分为三个过程 ,1.分配内存 2.初始化 3.将instance指向分配的内存空。但是如果 在另一个线程中想要使用instance,发现instance!=null,但是实际上instance还未初始化完毕这个问题。
synchronized 原理
Synchronized可以把任何一个非null对象作为"锁",其中都有一个监视器锁:monitor。Synchronized的语义底层是通过一个monitor的对象来完成。
两种同步方式本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。两个指令的执行是JVM通过调用操作系统的互斥原语mutex来实现,被阻塞的线程会被挂起、等待重新调度,会导致“用户态和内核态”两个态之间来回切换,对性能有较大影响。
synchronized 在JDK 1.6以后的优化
自适应自旋锁 :指一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放(自旋的次数不再是固定的,它是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定),而不是进入线程挂起或睡眠状态(因为为了很短的等待时间就去挂起唤醒会 更低效)。
锁消除:但是在有些情况下,JVM检测到不存在共享数据竞争,JVM会对这些同步锁进行锁消除。
锁粗化 :就是将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁。
偏向锁、轻量级锁:轻量级锁是为了在线程交替执行同步块时提高性能,而偏向锁则是在只有一个线程执行同步块时通过标识,避免再走各种加锁/解锁流程,达到进一步提高性能。
synchronized 添加在非静态方法上为 对象锁,如果在静态方法上为类锁。
public class Singleton {
private static volatile Singleton s;
private Singleton(){};
public static Singleton getInstance() {
if(s == null) {
synchronized (Singleton.class) {
if(s == null) {
s = new Singleton();
}
}
}
return s;
}
}
public class TestClient {
private int index;
private String name;
public TestClient() {
this(new Builder());
}
public TestClient(Builder builder){
this.index = builder.index;
this.name = builder.name;
}
public static final class Builder {
private int index;
private String name;
public Builder() {
this.index = 1;
this.name = "xxx";
}
public Builder(TestClient testClient){
this.index = testClient.index;
this.name = testClient.name;
}
public Builder setIndex(int index) {
this.index = index;
return this;
}
public Builder setName(String name) {
this.name = name;
return this;
}
public TestClient build(){
return new TestClient(this);
}
}
}
线程池参考文章
进程被杀原因:1.切到后台内存不足时被杀;2.切到后台厂商省电机制杀死;3.用户主动清理
保活方式:
Hook android 使用
Hook 的选择点:静态变量和单例,因为一旦创建对象,它们不容易变化,非常容易定位。
Hook 过程:
寻找 Hook 点,原则是静态变量或者单例对象,尽量 Hook public 的对象和方法。
选择合适的代理方式,如果是接口可以用动态代理。
偷梁换柱——用代理对象替换原始对象。
多数插件化 也使用的 Hook技术
Window :抽象类 不是实际存在的,而是以 View 的形式存在,通过 PhoneWindow 实现 (PhoneWindow = DecorView = Title + ContentView)
WindowManager:外界访问 Window 的入口 管理Window 中的View , 内部通过 Binder 与 WMS IPC 进程交互
WMS:管理窗口 Surface 的布局和次序,作为系统级服务单独运行在一个进程
SurfaceFlinger:将 WMS 维护的窗口按一定次序混合后显示到屏幕上
个人源码文章
通过 SetContentView(),调用 到PhoneWindow ,后实例DecorView ,通过 LoadXmlResourceParser() 进行IO操作 解析xml文件 通过反射 创建出View,并将View绘制在 DecorView上,这里的绘制则交给了ViewRootImpl 来完成,通过performTraversals() 触发绘制流程,performMeasure 方法获取View的尺寸,performLayout 方法获取View的位置 ,然后通过 performDraw 方法遍历View 进行绘制。
一个 MotionEvent 产生后,按 Activity -> Window -> DecorView(ViewGroup) -> View 顺序传递,View 传递过程就是事件分发,因为开发过程中存在事件冲突,所以需要熟悉流程:
参考博客1
参考博客2
想要在 onCreate 中获取到View宽高的方法有:
源码分析:
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
getRunQueue().post(action);
return true;
}
View.post(runable) 通过将runable 封装为HandlerAction对象,如果attachInfo为null 则将Runnable事件 添加到等待数组中, attachInfo初始化是在 dispatchAttachedToWindow 方法,置空则是在detachedFromWindow方法中,所以在这两个方法生命周期中,调用View.post方法都是直接让 mAttachInfo.handler 执行。
ViewRootImpl.class
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
context);
final ViewRootHandler mHandler = new ViewRootHandler();
通过查找 mAttachInfo.handler 是在主线程中声明的,没有传参则 Looper 为主线程Looper,所以在View.post中可以更新UI。
但是为什么可以再View.post()中获取控件尺寸呢?
android 运行是消息驱动,通过源码 可以看到 ViewRootImpl 中 是先将 TraversalRunnable添加到 Handler 中运行的 之后 才是 View.post()
ViewRootImpl.class
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
// 该方法之后才有 view.post()
performTraversals();
...
}
}
因此,这个时候Handler正在执行着TraversalRunnable这个Runnable,而我们post的Runnable要等待TraversalRunnable执行完才会去执行,而TraversalRunnable这里面又会进行measure,layout和draw流程,所以等到执行我们的Runnable时,此时的View就已经被measure过了,所以获取到的宽高就是measure过后的宽高。
提升动画 可以打开 硬件加速,使GPU 承担一部分CPU的工作。
作用:线程之间的消息通信
流程:主线程默认实现了Looper (调用loop.prepare方法 向sThreadLocal中set一个新的looper对象, looper构造方法中又创建了MsgQueue) 手动创建Handler ,调用 sendMessage 或者 post (runable) 发送Message 到 msgQueue ,如果没有Msg 这添加到表头,有数据则判断when时间 循环next 放到合适的 msg的next 后。Looper.loop不断轮训Msg,将msg取出 并分发到Handler 或者 post提交的 Runable 中处理,并重置Msg 状态位。回到主线程中 重写 Handler 的 handlerMessage 回调的msg 进行主线程绘制逻辑。
问题:
js调用android :
// myObj 为在js中使用的对象名称 JavaScriptInterfaces 是我们自定义的一个类
webView.addJavascriptInterface(new JavaScriptInterfaces(), "myObj");
myObj.xx() //js方法
android 调用js
无参数
mWebView.loadUrl("javascript:wave()");
有参数
webView.evaluateJavascript(String.format("javascript:callH5Re('测试数据')"), new ValueCallback() {
@Override
public void onReceiveValue(String value) {
Log.e("Test", "onReceiveValue: "+value );
} });
主要分为 启动优化,布局优化 ,打包优化 等
看过布局绘制源码流程后,可以知道 setContextView中 在ViewRootImpl 中使用 pull 的方法(这里可以扩展xml读取方式 SAX :逐行解析、dom:将整个文件加载到内存 然后解析,不推荐、pull:类似于 SAX 进行了android平台的优化,更加轻量级 方便)迭代读取 xml标签,然后对view 进行 measure,layout 和draw 的时候都存在耗时。通常优化方式有:
Analyze APK 后可以发现代码 和 资源其实是 app包的主要内存
参考博客
通过 registerActivityLifecycleCallbacks 监听Activity或者Fragment 销毁时候的生命周期(如果不想那个对象被监控则通过 AndroidExcludedRefs 枚举,避免被检测)
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference);
}
然后通过弱引用和引用队列监控对象是否被回收(弱引用和引用队列ReferenceQueue联合使用时,如果弱引用持有的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。即 KeyedWeakReference持有的Activity对象如果被垃圾回收,该对象就会加入到引用队列queue)
void waitForIdle(final Retryable retryable, final int failedAttempts) {
// This needs to be called from the main thread.
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override public boolean queueIdle() {
postToBackgroundWithDelay(retryable, failedAttempts);
return false;
}
});
}
IdleHandler,就是当主线程空闲的时候,如果设置了这个东西,就会执行它的queueIdle()方法,所以这个方法就是在onDestory以后,一旦主线程空闲了,就会执行一个延时五秒的子线程任务,任务:检测到未被回收则主动 gc ,然后继续监控,如果还是没有回收掉,就证明是内存泄漏了。 通过抓取 dump文件,在使用 第三方 HAHA 库 分析文件,获取到到达泄露点最近的线路,通过 启动另一个进程的 DisplayLeakService 发送通知 进行消息的展示。
参考博客
☆平头哥 博客链接
同步和异步 网络请求使用方法
// 同步get请求
OkHttpClient okHttpClient=new OkHttpClient();
final Request request=new Request.Builder().url("xxx").get().build();
final Call call = okHttpClient.newCall(request);
try {
Response response = call.execute();
} catch (IOException e) {
}
//异步get请求
OkHttpClient okHttpClient=new OkHttpClient();
final Request request=new Request.Builder().url("xxx").get().build();
final Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
// 异步post 请求
OkHttpClient okHttpClient1 = new OkHttpClient();
RequestBody requestBody = new FormBody.Builder()
.add("xxx", "xxx").build();
Request request1 = new Request.Builder().url("xxx").post(requestBody).build();
okHttpClient1.newCall(request1).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
同步请求流程:
通过OkHttpClient new生成call实例 Realcall
Dispatcher.executed() 中 通过添加realcall到runningSyncCalls队列中
通过 getResponseWithInterceptorChain() 对request层层拦截,生成Response
通过Dispatcher.finished(),把call实例从队列中移除,返回最终的response
异步请求流程:
生成一个AsyncCall(responseCallback)实例(实现了Runnable)
AsyncCall通过调用Dispatcher.enqueue(),并判断maxRequests (最大请求数)maxRequestsPerHost(最大host请求数)是否满足条件,如果满足就把AsyncCall添加到runningAsyncCalls中,并放入线程池中执行;如果条件不满足,就添加到等待就绪的异步队列,当那些满足的条件的执行时 ,在Dispatcher.finifshed(this)中的promoteCalls();方法中 对等待就绪的异步队列进行遍历,生成对应的AsyncCall实例,并添加到runningAsyncCalls中,最后放入到线程池中执行,一直到所有请求都结束。
责任链
源码跟进 execute() 进入到 getResponseWithInterceptorChain() 方法
Response getResponseWithInterceptorChain() throws IOException {
//责任链 模式
List 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);
}
chain.proceed() 方法核心代码。每个拦截器 intercept()方法中的chain,都在上一个 chain实例的 chain.proceed()中被初始化,并传递了拦截器List与 index,调用interceptor.intercept(next),直接最后一个 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);
@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);
while (true) {
...
// 循环中 再次调用了 chain 对象中的 proceed 方法,达到递归循环。
response = realChain.proceed(request, streamAllocation, null, null);
releaseConnection = false;
...
}
}
拦截器
OkHttp 流程
参考文章
butterKnife 使用的是 APT 技术 也就是编译时注解,不同于运行时注解(在运行过程中通过反射动态地获取相关类,方法,参数等信息,效率低),编译时注解 则是在代码编译过程中对注解进行处理,通过注解获取相关类,方法,参数等信息,然后在项目中生成代码,运行时调用,其实和直接手写代码一样,没有性能问题,只有编辑时效率问题。
在编写完demo之后,需要先build一下项目,之后可以在build/generated/source/apt/debug/包名/下面找到 对应的xx_ViewBinding类,查看bk 帮我们做的事情,
xx_ViewBinding.java
@UiThread
public ViewActivity_ViewBinding(ViewActivity target, View source) {
this.target = target;
target.view = Utils.findRequiredView(source, R.id.view, "field 'view'");
}
Utils.java
public static View findRequiredView(View source, @IdRes int id, String who) {
View view = source.findViewById(id);
if (view != null) {
return view;
}
String name = getResourceEntryName(source, id);
throw new IllegalStateException("Required view ...."}
通过上述上述代码 可以看到 注解也是帮我们完成了 findviewbyid 的工作。
butterknife 实现流程
Rxjava源码
切换到子线程用的 线程池 ,切换到主线程则用的Handler
public static void bubble(int[] arr){
int temp;
//根据角标进行比较,
for(int i = 0; i i; j--) {
if (arr[j] < arr[j - 1]) {
//从后往前进行比较,小数往前,一轮之后最小数就在最前面了
temp = arr[j - 1];
arr[j - 1] = arr[j];
arr[j] = temp;
}
}
}
}
public static int myBinarySearch(int[] arr,int value) {
int low=0;
int high=arr.length-1;
while(low<=high) {
int mid=(low+high)/2;
if(value==arr[mid]) {
return mid;
}
if(value>arr[mid]) {
low=mid+1;
}
if(value