面试技术点

一、对热修复、插件化、模块化、组件化有一定研究。

1、模块化

  将共享部分或业务模块抽取出来形成独立module。

2、组件化

  基于模块化,核心思想是角色的转换,在打包时是library,分离独立的业务组件如微信朋友圈。

3、热修复和插件化种类、特点

BootClassLoader:用于加载Android Framework层class文件。

PathClassLoader:用于加载已经安装到系统中的apk中的class文件。

DexClassLoader:用于加载指定目录中的class文件。

BaseDexClassLoader:是PathClassLoader和DexClassLoader的父类。

4、插件化

  插件一般是指经过处理的APK,so和dex等文件,插件可以被宿主进行加载,有的插件也可以作为APK独立运行。

4.1、插件化实现原理简单概括三步:

(1) 通过dexclassloader加载。

(2) 代理模式添加生命周期。

(3) hook思想跳过清单验证。

4.2、Hook实现方式有两种:

  1. Hook IActivityManager和Hook Instrumentation。
  2. 主要方案就是先用一个在AndroidManifest.xml中注册的Activity来进行占坑,用来通过AMS的校验,接着在合适的时机用插件Activity替换占坑的Activity。

4.3、插件化框架

目前主流的插件化方案有滴滴VirtualApk、360RePlugin框架。如果加载的插件不需要和宿主有任何耦合,也无须和宿主进行通信,比如加载第三方App,那么推荐使用RePlugin,其他情况推荐使用VirtualApk.

5、热修复

  热修复与插件化都利用classloader实现加载新功能。热修复是为了修复bug,所以要将新的同名类替代同名的bug类,要抢先加载bug类之前加载新的类。插件化只是增加新的功能或资源文件,所以不涉及抢先加载旧类的使命。

热修复实现的方式:

5.1、资源修复方案:

  1. 很多热修复框架的资源修复参考了Instant Run的资源修复的原理。
  2. 创建新的AssetManager,通过反射调用addAssetPath方法加载外部的资源,这样新创建的AssetManager就含有了外部资源。
  3. 将AssetManager类型的字段的引用全部替换为新创建的AssetManager。

5.2、底层替换方案:

(1) 当我们要反射Key的show方法,会调用getDeclaredMethod中invoke方法。

  1. 最终会在native层将在虚拟机中对应一个ArtMethod指针,ArtMethod结构体中包含了Java方法的所有信息,然后替换整个ArtMethod结构体。
  2. 使用这种方案的有AndFix、Dexposed、阿里百川、Sophix。

二、掌握音视频,IM,直播等技术,并有成功上线应用。

  1. 音视频使用阿里云的视频点播。
  2. 即时通讯使用的是容联云、环信。
  3. 直播有用过腾讯的直播。
  1. 熟练使用MVC、MVP、MVVM框架。
  1. MVC原理
  1. 模型层(Model):数据库、网络、复杂业务计算等。
  2. 视图层(View):处理界面的显示结果,比如XML页面布局。
  3. 控制层(Controller):控制M层和V层通信以此来达到视图显示和业务逻辑分离,起到桥梁的作用,来;C层由Activity承担。

MVC缺点:

  1. C层它的首要职责是加载应用的布局和初始化用户界面。
  2. 并接受和处理来自用户的操作请求,作出响应。
  3. 随着界面和逻辑的复杂度不断提升,Activity类的职责不断增加,以致变得庞大臃肿。
  1. MVP
  1. Model:负责存储、检索、操纵数据。
  2. View:负责绘制UI元素、与用户进行交互(在Android中体现为Activity)。
  3. Presenter:作为View与Model交互的中间纽带的作用;View通过接口与Presenter进行交互。

MVP优点:

  1. 模型与视图完全分离,修改视图不影响模型。
  2. 更高效地使用模型,因为交互都发生在Presenter内部。
  3. Presenter可以复用于多个视图。

MVP缺点:每个view有个presenter ,类多了。

3、MVVM

  1. Model:提供数据源给ViewModel,包含实体类,网络请求和本地存储等。
  2. View:Activity、Fragment、XML布局、自定义View等。
  3. ViewModel:将Model层提供的数据根据View层的需求进行处理,通过DataBinding绑定到相应的UI上。

MVVM缺点:

由于数据和视图的双向绑定,导致出现问题时不太好定位来源,有可能数据问题,或者业务逻辑中对视图属性的修改导致。官方的架构组件ViewModel、LiveData、DataBinding去实现MVVM。

  1. MVC、MVP、MVVM区别
  1. 在MVC中是允许Model和View进行交互的。
  2. 在MVP中View通信是通过Presenter;Presenter与View之间的交互是通过接口。
  3. 在MVVM中用户直接交互的是View。
  1. 关于MVC,MVP,MVVM如何选择的探讨
  1. 对于偏向展示型的app,绝大多数业务逻辑都在后端,app主要功能就是展示数据,交互等,建议使用mvvm。
  2. 对于工具类或者需要写很多业务逻辑app,使用mvp或者mvvm都可。
  1. 熟悉IPC进程间通讯,binder机制,了解AIDL的使用。
  1. IPC进程间通讯

定义:就是进程间通信,是指两个进程之间进行数据交换的过程。

  1. Activity可以跨进程调用其他应用程序的Activity,比如打电话、发信息。
  2. ContentProvider可以跨进程访问其他应用程序中的数据(以Cursor对象形式返回),可以对数据进行增、删、改操作。
  3. Broadcast可以向android系统中所有应用程序发送广播, 而需要跨进程通讯的应用程序可以监听这些广播。
  4. Service和ContentProvider类似,也可以访问其他应用程序中的数据。但不同的是,ContentProvider返回的是Cursor对象,而Service返回的是Java对象,这种可以跨进程通讯的服务叫AIDL服务。
  5. 面试技术点_第1张图片

多进程产生的问题有哪些呢:

  1. 四大组件数据共享失败。
  2. 静态成员和单例模式、线程同步失效:独立的虚拟机造成。
  3. SP的可靠性下降:因为Sp不支持两个进程并发读写。
  4. Application会多次创建:Android系统在创建新的进程时会分配独立的虚拟机,所以这个过程其实就是启动一个应用的过程,自然也会创建新的Application。

2、Binder机制

  1. 在Android系统的Binder机制中,是由Client,Service,ServiceManager,Binder驱动程序组成的,其中Client,service,ServiceManager运行在用户空间,Binder运行在内核空间的。
  2. Binder是核心组件,负责把这几个组件组合在一起;而Client和Service通信是在Binder驱动程序和ServiceManager提供辅助管理的基础上实现。

3、AIDL理解

定义:如果在一个进程中要调用另一个进程中对象的方法,可使用AIDL生成可序列化的参数和一个服务端对象的代理类,通过它客户端可以实现间接调用服务端对象的方法。

AIDL实现步骤:

  1. IBinder:跨进程通信的Base接口,它声明了跨进程通信需要实现的一系列抽象方法,实现了这个接口就说明可以进行跨进程通信,Client和Server都要实现此接口。
  2. IInterface:这也是一个Base接口,用来表示Server提供了哪些能力,是Client和Server通信的协议。
  3. Binder:提供Binder服务的本地对象的基类,它实现了IBinder接口,所有本地对象都要继承这个类。
  4. BinderProxy:在Binder.java这个文件中还定义了一个BinderProxy类,这个类表示Binder代理对象它同样实现了IBinder接口,不过它的很多实现都交由native层处理。Client中拿到的实际上是这个代理对象。
  5. Stub:这个类在编译aidl文件后自动生成,它继承自Binder,表示它是一个Binder本地对象;它是一个抽象类,实现了IInterface接口,表明它的子类需要实现Server将要提供的具体能力(即aidl文件中声明的方法)。
  6. Proxy:它实现了IInterface接口,说明它是Binder通信过程的一部分;它实现了aidl中声明的方法,但最终还是交由其中的mRemote成员来处理,说明它是一个代理对象,mRemote成员实际上就是BinderProxy。

(7) aidl文件只是用来定义C/S交互的接口,Android在编译时会自动生成相应的Java类,生成的类中包含了Stub和Proxy静态内部类,用来封装数据转换的过程。

总结:

  1. 实现IBinder、IInterface;
  2. 本地对象继承Binder;在Binder这个文件中还定义了一个BinderProxy类,Client中拿到的实际上是这个代理对象;
  3. Stub:编译aidl文件后自动生成。
  4. Proxy:它实现了IInterface接口。
  5. aidl文件只是用来定义C/S交互的接口。

五、熟练使用二维码扫描、第三方登录、分享、推送、支付等技术。

二维码:用zxing框架。

微信、QQ登录:安卓调起微信返回key,通过请求后台将获取的数据返回前端。

微信、支付宝支付:安卓前端生成订单请求后台,返回加签后的数据,然后调用微信、支付宝sdk,为了安全,订单的加签是在后台做的。

推送平台:个推、极光、oppo、友盟、华为、小米、百度、信鸽等。

出现问题原因:在TCP长连接建立成功的基础上,网络状况有关,比如网络慢、丢包等等。最主要推送时,移动端未在线。

如何挺高到达率:

1、利用系统广播

监听系统事件广播来唤醒应用,常用的广播有:

  • 开机,ACTION_BOOT_COMPLETED 
  • 亮屏,ACTION_SCREEN_ON 
  • 灭屏,ACTION_SCREEN_OFF 
  • 插拔有线耳机,ACTION_HEADSET_PLUG 
  • 电量充足,ACTION_BATTERY_OK

2、启动前台service、Jobscheduler

3、接入多家推送

六、熟练使用异步、多线程编程。

1、Android异步线程实现方式有哪些?

runOnUiThread、Handler、AsyncTask、Rxjava

2、Handler原理

1、Handler整个流程中,主要有四个对象,Handler、Message,MessageQueue,Looper。handler对象在主线程中创建。

2、Handler通过sendMessage()发送消息Message到消息队列MessageQueue。

3、Looper通过loop()不断提取触发条件的Message,并将Message交给对应的target handler来处理。

4、target handler调用自身的handleMessage()方法来处理Message。

Handler后传篇一: 为什么Looper中的Loop()方法不能导致主线程卡死?

  1. 对于线程执行完成后,线程生命周期便该终止了,线程退出;而对于主线程,我们不希望一段时间后自己退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出,例如,binder线程也是采用死循环的方法,通过循环方式与不同Binder驱动进行读写操作。当然并非简单地死循环,无消息时会休眠。而在handler发来Message时候,唤醒主线程。
  2. 耗时操作本身并不会导致主线程卡死, 导致主线程卡死的真正原因是耗时操作之后的触屏操作, 没有在规定的时间内被分发。
  3. Looper 中的 loop()方法, 他的作用就是从消息队列MessageQueue 中不断地取消息, 然后将事件分发出去。

3、多线程编程

https://blog.csdn.net/yangjunjin/article/details/105025256

  1. 熟练掌握(Activity、Service、Broadcast Receiver、ContentProvider)四大组件的使用。

1、Activity

Activity生命周期:onCreate()、onStart()、 onResume()、onPause()、 onStop() 、onDetroy()

注:比如说有两个activity,分别叫A,B,当在A里面激活B组件的时候,

  1. A 会调用 onPause()方法,然后 B 调用 onCreate() ,onStart(), onResume()。
  2. 这个时候 B 覆盖了窗体, A 会调用 onStop()方法.(按返回键需要重新调用A的onRestart、onStart、onResume)
  3. 如果 B 是个透明的,或者 是对话框的样式, 就不会调用 A 的 onStop()方法。(按返回键需要重新调用A的onResume)

Activity启动方式:

standard 模式

这是默认模式,每次激活Activity时都会创建Activity实例,并放入任务栈中。

singleTop 模式

栈顶正好存在该Activity的实例,就重用该实例( 会调用实例的 onNewIntent() ),否则就会创建新的实例并放入栈顶,即使栈中已经存在该Activity的实例,只要不在栈顶,都会创建新的实例

singleTask 模式

如果在栈中已经有该Activity的实例,就重用该实例(会调用实例的 onNewIntent() )。重用时,会让该实例回到栈顶,因此在它上面的实例将会被移出栈。如果栈中不存在该实例,将会创建新的实例放入栈中。

singleInstance 模式

在一个新栈中创建该Activity的实例,并让多个应用共享该栈中的该Activity实例。一旦该模式的Activity实例已经存在于某个栈中,任何应用再激活该Activity时都会重用该栈中的实例( 会调用实例的 onNewIntent() )。其效果相当于多个应用共享一个应用,不管谁激活该 Activity 都会进入同一个应用中。使用场景如闹铃提醒,将闹铃提醒与闹铃设置分离。singleInstance不要用于中间页面,如果用于中间页面,跳转会有问题,比如:A -> B (singleInstance) -> C,完全退出后,在此启动,首先打开的是B。

  1. Service

startService启动方式:

(1)生命周期:onCreate()--> onStartConmon()--> onDestroy()

(2)多次在调用startService(),onCreate()方法也只会被调用一次,而onStartConmon()会被多次调用。

(3)当我们调用stopService()的时候,onDestroy()就会被调用,从而销毁服务。

bindService启动方式。

(1)生命周期走法:onCreate()-->onBind()-->unBind()-->onDestroy() 

(2)这种方式进行启动service好处是更加便利activity中操作service,拿到该服务的对象就可以操作服务的方法,比如写的音频播放。

  1. BroadcastReceiver

Broadcast注册方式与区别(通过binder向AMS注册)

Broadcast广播,注册方式主要有两种.

第一种是静态注册:这种广播需要在Androidmanifest.xml中进行注册。不受页面生命周期的影响,即使退出了页面,也可以收到广播这种广播一般用于想开机自启动等,由于这种注册的方式的广播是常驻型广播,所以会占用CPU的资源。

第二种是动态注册:而动态注册的话,受到生命周期的影响,退出页面后,就不会收到广播,我们通常运用在更新UI方面。最后需要解绑,否会会内存泄露。

第三种是本地广播:使用这个机制发出的广播只能够在应用程序的内部进行传递,为了数据的安全,只能被动态注册。本地广播效率更高,因为它是用handler实现的。

  1. ContentProvider

(1)在 Android 中如果想将自己应用的数据(一般多为数据库中的数据)提供传给第三发应用,那么我们只能通过 ContentProvider 来实现了。

(2)使用的时候首先自定义 一个类继承 ContentProvider,然后覆写 query、insert、update、delete 等 方法。把自己的数据通过 uri 的形式共享出去。

八、熟练使用ListView、Recyclerview的优化方法。

ListView优化:

  1. convertView的使用,主要优化加载布局问题。当它为空的时候,创建内部类ViewHolder并给它设置一个tag,不为空的时候直接取出来。这样控件就不需要再次的查找id。

Recyclerview优化:

  1. 布局优化:减少层次结构、减少过渡绘制、item等高。
  2. ViewGroup性能:RelativeLayout 对子View做了两次measure,才能确定宽高。
  3. LinearLayout进行一次,如果给我设置权重(weight),则会进行两次测量。
  4. 取消默认动画:mRecyclerView.setItemAnimator(null);
  5. RecycledViewPool:可以给RecyclerView设置一个ViewHolder的对象池,可以节省你创建ViewHolder的开销。
  1. OkHttp的WebSocket实现账号的单点登录功能

1、客户端向服务定时发送心跳包,检测

十、熟练使用json、xml解析方法:如:fastjson,Pull、Gson解析等。

1、SAX解析方式(SAXParserFactory)

优点:解析速度快,占用内存少。

2、Pull 方式解析(XmlPullParserFactory)

优点:PULL解析器小巧轻便,解析速度快,简单易用

3、Dom解析方式(DocumentBuilderFactory 文档对象工厂)

优点:占用内存少,适合处理比较大的XML文档。

十二、SharedPreference、SQLite、greedao、realm区别等。

1、Realm库在进行插入、更新、删除等操作都具有较快的速度(尤其是在插入数据的速度上),底层由JNI实现是比较关键的因素。

2、Android中原生的SQlite数据库在插入数据的表现上跟greenDao差不多,但是删除和更新操作速度最快。

3、其中greenDao和sqlite生成的数据库大小差不多,但是realm的数据库却要大好多。

  1. 熟练使用属性动画,补间动画、帧动画实现动画特效。

帧动画:指定每帧的图片和播放时间,有序的进行播放,比如音乐播放器的背景。

补间动画:主要有Alpha、Scale、Translate、Rotate四种效果。注意:只是在视图层实现了动画效果,并没有真正改变View的属性,比如一个button移动到一个新的位置,但是点击事件还是在原来的位置。

属性动画:在Android3.0的时候才支持,通过不断的改变View的属性,不断的重绘而形成动画效果。相比于视图动画,View的属性是真正改变了。

十六、精通android的ui系统控件,并熟练掌握自定义view,事件分发和处理。

自定义View

类型有:组合,继承原有控件、继承View三种

继承View步骤:onMeasure onDraw

继承ViewGoup步骤:onMeasure onLayout

注:继承View和继承ViewGoup的区别,ViewGoup需要重写onLayout方法,之前写过一个流式布局,需要测量子控件的宽高。

刷新界面的三个方法: 

Layout:对控件进行重新定位执行onLayout()这个方法。

RequestLayout:控件会重新执行 onMesure() onLayout() .

Invalidate:该方法使用会,会执行onDraw()

SpecMode有三种:1. EXACTLY:确定大小,如果SpecMode是Exactly,那么SpecSize是多少,测量结果就是多少。比如子View在layout中设置的是指定大小,那么在测量时从父布局中传到measure方法的SpecMode就是Exactly。或者说子布局设置的是match_parent,而父布局此时已经能够确定自己的大小,那么模式也是Exactly。2. AT_MOST:子布局可以自行确定自己的大小,但是不能超过SpecSize的大小。典型的就是父布局已经确定了自己的大小,而子布局设置的参数是wrap_content,3. UNSPECIFIED:父布局对子view不做限制,要多大给多大。用于特殊的测量场合。

事件的分发

  1. Touch事件分发中只有两个主角:ViewGroup和View。ViewGroup包含onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent三个相关事件。View包含dispatchTouchEvent、onTouchEvent两个相关事件。

2.触摸事件由Action_Down、Action_Move、Aciton_UP组成,其中一次完整的触摸事件中,Down和Up都只有一个,Move有若干个,可以为0个。

3.当Acitivty接收到Touch事件时,将遍历子View进行Down事件的分发。ViewGroup的遍历可以看成是递归的。分发的目的是为了找到真正要处理本次完整触摸事件的View,这个View会在onTouchuEvent结果返回true。

4.当某个子View返回true时,会中止Down事件的分发,同时在该ViewGroup中记录该子View。接下去的Move和Up事件将由该子View直接进行处理。

  1. 当ViewGroup中所有子View都不捕获Down事件时,将触发ViewGroup自身的onTouch事件。触发Acitivity的onTouchEvent方法。

Android 事件分发机制

https://blog.csdn.net/yangjunjin/article/details/100623095

十七、熟练掌握Android原生与H5的交互。

 WebSettings settings = wv.getSettings();

 settings.setJavaScriptEnabled(true);//让webView支持JS

 wv.loadUrl("http://www.baidu.com/"); //加载百度网页

        

Android调用js:

  • 通过WebView的loadUrl();这种方式可以传参数给js
  • 通过WebView的evaluateJavascript();这种方式可以双向拿数据(Android 4.4 后才可使用)

Js调用android:

  • 通过WebView的addJavascriptInterface()进行对象映射
  • 通过 WebViewClient 的shouldOverrideUrlLoading ()方法回调拦截 url
  • 通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS对话框alert()、confirm()、prompt() 消息

     

漏洞产生原因是:当JS拿到Android这个对象后,就可以调用这个Android对象中所有的方法,包括系统类(java.lang.Runtime 类),从而进行任意代码执行。

解决方式:

(1)Google 在Android 4.2 版本中规定对被调用的函数以 @JavascriptInterface进行注解从而避免漏洞攻击。

(2)在Android 4.2版本之前采用拦截prompt()进行漏洞修复。

2. 通过 WebViewClient 的shouldOverrideUrlLoading ()方法回调拦截 url 。这种方式的优点:不存在方式1的漏洞;缺点:JS获取Android方法的返回值复杂。(ios主要用的是这个方式)

JS回调java中的代码线程在子线程

webView性能优化:

1、将WebView放在一个独立的进程中,与应用程序主进程隔离开,即使这个进程挂了,也不会影响app主进程的使用。

2、通过new的方法去添加webview,而不是在xml里添加,且Context使用全局的context,避免造成当前Activity的引用滥用。销毁的时候调用destroy(),移除,置空、清除缓存。

十八、熟练掌握android内存泄漏检测,代码优化,内存优化,bug日志收集处理等等。

启动速度优化

准则:能异步都异步,不能异步尽量延迟,让应用先启动,再操作。

1、减少Application的onCreate中所做的事,能异步就异步,能延迟就延迟,比如第三方、工具的初始化

2、闪屏页尽量不做耗时操作

3、闪屏页和主布局尽量减少嵌套。

4、在主页可以先显示缓存数据,再请求网络刷新数据

内存优化

  1. 避免在控件ondraw方法或者循环体内中创建对象。
  2. XML布局减少不必要的嵌套;
  3. 对Cursor、broadcast、Sensor、File使用结束,注销关闭;
  4. webview对象退出界面,记得销毁、置空。
  5. 对象无需引用,则置空为null,方便检测被垃圾内存。
  6. 防止单例类持有不用对象的引用,可尝试传入ApplicationContext。
  7. 字符串拼接使用StringBuffer、StringBuilder
  8. 少用static修饰变量,被修饰变量不会被GC回收的。
  9. 非静态内部类导致内存泄露,比如Activity中创建的Handler,可以使用弱引用去拿到外部的对象引用。

绘制优化

卡顿的原因:

·  1、首先,系统会将Xml文件通过IO的方式加载到内存当中,而IO的过程可能会导致卡顿。

·  2、其次,布局加载的过程是一个反射的过程可能会导致卡顿。

·  4、最后,不合理的布局嵌套,进行布局遍历的过程就会比较耗时。

解决方案:

  1. 使用了异步Inflate的方式,即AsyncLayoutInflater;它是在子线程中对布局进行加载。
  2. 使用ConstraintLayout去减少布局的嵌套层级。
  3. 使用include 、merge、ViewStub标签。

App瘦身

  1. 图片使用顺序:VD->webp->png->jpg。
  1. 删除无用资源。
  1. 使用插件压缩png图片,再转webp。
  1. 优化lib,配置armeabli、armeabli-v7a、x86;
  2. 代码混淆,资源混淆。
  3. 只支持中文配置。
  4. 统一风格,自定义drawable,和选择器。

数据库优化Sqlite

  1. 显示使用事务,提交多条数据,创建事务和提交只做一次。
  2. 建立索引(这个我觉得没必要说了,所有数据库查询时索引都会有帮助)
  • 优点:加快了检索、查询的速度。
  • 缺点:增大数据库大小,对增删改的性能存在一定影响。
  1. 查询数据优化(少用cursor.getColumnIndex)
  2. 调整ContentValues的容量
  3. 语句的拼接使用StringBuilder代替String
  4. 耗时异步化(大数据量操作异步线程处理)
  5. 及时关闭Cursor

总结:数据小,android 提供的方法和执行语句,差别不大;大数据,执行语句速度快。

十九、socket、http、https、TCP、udp、ip、

Socket的使用类型主要有两种:

1、流套接字(streamsocket) :基于TCP协议,采用流的方式,提供可靠的字节流服务。

2、数据报套接字(datagramsocket):基于UDP协议,采用数据报文提供数据打包发送的服务。

3、Socket是对TCP和UDP编程的接口。而TCP和UDP协议属于传输层。Socket是对TCP/IP协议的封装,Socket本身并不是协议,通过Socket,我们才能使用TCP/IP协议。

HTTPS和HTTP的区别:

  • HTTP 数据明文传输,HTTPS 数据加密传输
  • HTTP 不需要证书,HTTPS 需要证书
  • HTTP 默认80端口,HTTPS 默认 443 端口
  • HTTPS 比 HTTP 安全,因为比 HTTP 多了 SSL 层

Http的请求过程 1、域名解析 2、发起TCP的3次握手 3、建立TCP连接后发起Http请求 4、服务器端响应Http请求,并回传数据 5、客户端获取数据解析展示

TCP/IP协议:

三次握手:

为什么TCP建立连接需要三次握手?

答:防止服务器接收到早已失效的连接请求报文从而一直等待客户端请求。

1、第一次握手:客户端向服务端发送syn包,进入syn_send等待状态。

2、第二次握手:服务器收到syn包并确认,同时也发送一个syn包,进入syn_send等待状态。

3、第三次握手:客户端收到服务器发来的包,并向服务器发送确认包ack,此包发送完毕,客户端和服务器进入连接状态,完成三次握手。

面试技术点_第2张图片

四次挥手:

为什么TCP释放连接需要四次挥手?

答:为了保证双方都能通知对方“需要释放连接”,即在释放连接后都无法接收或发送消息给对方

  1. 第一次挥手:A发送释放信息到B;(发出去之后,A->B发送数据这条路径就断了)
  2. 第二次挥手:B收到A的释放信息之后,回复确认释放的信息:同意释放连接请求

(3)第三次挥手:B发送请求释放连接信息给A

(4)第四次挥手:A收到B发送的信息后,向B发送确认释放信息:同意释放连接请求

TCP、UDP区别:

TCP:可连接,可靠的,速度慢、效率低。

UDP:无连接,不可靠,速度快、效率高。

Socket和Http的区别:

  1. Socket属于传输层,因为 TCP / IP协议属于传输层,解决的问题是:数据如何在网络中传输。
  2. HTTP协议 属于 应用层,解决的是如何包装数据
  3. http连接:http连接就是所谓的短连接,即客户端向服务器端发送一次请求,服务器端响应后连接即会断掉;
  4. socket连接:socket连接就是所谓的长连接,理论上客户端和服务器端一旦建立起连接将不会主动断掉。

二十、设计模式

单例模式、观察者模式、工厂模式、适配器设计模式、装饰模式、策略模式

一、单例模式:

定义:确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

二、观察者模式:

定义:对象间的一种一对多的依赖关系,当被观察者状态发生改变时,所有观察者都得到通知(EventBus、broadcast)

三、建造者模式(Builder)

定义:将各种产品集中起来进行管理,用来创建复合对象(如自定义对话框)

三、工厂模式:

定义:为创建对象提供过渡接口,以便将创建对象的过程屏蔽起来,提高灵活性。

四、适配器设计模式:将接口转换成客户端需要的接口,目的是消除接口兼容性。

二十一、算法(冒泡、插入、选择)

冒泡排序:

面试技术点_第3张图片

选择排序:

//选择排序

    public void selectSort(int[] array) {

        int min;

        int tmp = 0;

        for (int i = 0; i < array.length; i++) {

            min = array[i];

            for (int j = i; j < array.length; j++) {

                if (array[j] < min) {

                    min = array[j];//最小值

                    tmp = array[i];

                    array[i] = min;

                    array[j] = tmp;

                }

            }

        }

    }

插入排序:

     //直接插入排序

    private void InsertSort(int[] a) {

   

        for (int i = 1; i < a.length; i++) {

            //待插入的 新元素temp

            int temp = a[i];

            int j;

            for (j = i - 1; j >= 0; j--) {  

                //将大于temp的往后移动一位

                if (a[j] > temp) {

                    a[j + 1] = a[j];

                }

            }

            //循环结束后,temp是最小的值,由于循环最后会j--,所以放到j+1位置。

            a[j + 1] = temp;

        }

    }

二十一、android中NDK、JIN

  1. NDK提供了一系列的工具,帮助开发者迅速的开发C/C++的动态库,并能自动将so和java应用打成apk包。
  2. JNI即是Java本地接口,JNI是Java调用Native 语言的一种特性。
  3. 通过JNI可以使得Java与C/C++机型交互。

二十二、进程保活

1、开启一个像素的Activity:系统一般是不会杀死前台进程的。所以要使得进程常驻,我们只需要在锁屏的时候在本进程开启一个Activity,为了欺骗用户,让这个Activity的大小是1像素,并且透明无切换动画,在开屏幕的时候,把这个Activity关闭掉,所以这个就需要监听系统锁屏广播.

2、JobSheduler是作为进程死后复活的一种手段,native进程方式最大缺点是费电, Native 进程费电的原因是感知主进程是否存活有两种实现方式,在 Native 进程中通过死循环或定时器,轮训判断主进程是否存活,当主进程不存活时进行拉活。

二十三、缓存机制

内存缓存:这里的内存主要指的存储器缓存(LruCache)

磁盘缓存:外部存储器,手机的话指的就是SD卡。(DisLruCache)

LruCache的实现原理:维护一个缓存对象列表(LinkedHashMap是由数组+双向链表),其中对象列表是按照访问顺序实现的,即一直没访问的对象,将放在队尾,即将被淘汰。而最近访问的对象将放在队头,最后被淘汰。

二十四、内存回收机制

垃圾回收原理概述:是一种自动的存储管理机制,是Java语言特性;把内存释放工作的压力都转让到系统,以消耗系统性能为代价。

优点:

1.让作为程序员专注于逻辑实现。

2.保护程序的完整性,是Java语言的安全策略。

缺点:

1.系统层面负责垃圾回收,明显会加大系统资源的开销,影响性能。

    2.存在不完备性,并不能百分百保证回收所有的垃圾内存。

回收工作原理:

一、可回收对象的判定 

我们的GC需要把某个对象回收掉,肯定是需要判断它到底是不是垃圾,是不是需要被回收,因此,就需要对每一个对象进行可回收判定。

1. 引用计数算法

首先给每个对象添加计数器,有引用+1,引用失效-1,当等于0,判定对象不使用

二、通过算法回收垃圾内存

1.标记清除算法:标记阶段就是标记被回收的对象,清除阶段就是回收被标记的对象所占用的空间。

2.复制算法:将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。

3.标记整理算法:该算法标记阶段和标记清除算法一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。

二十五、Android系统框架

应用程序层:电子邮件、短信、日历、地图、浏览器和联系人管理等。

应用程序框架层:应用框架层为开发人员提供了可以开发应用程序所需要的API。(活动管理器、位置管理器、包管理器、通知管理器、资源管理器、 电话管理器)

系统运行库层:

1、C/C++程序库:如SQLite库提供数据的管理等。

2、Android运行时库:提供一些核心库,Dalvik虚拟机。

Linux内核层:显示、音频、照相等驱动。

二十六、存储机制

1、使用SharedPreferences存储数据

核心原理:保存基于XML文件存储的key-value键值对数据,通常用来存储一些简单的配置信息。

2、文件存储数据

核心原理: Context提供了两个方法来打开数据文件里的文件IO流 FileInputStream、FileOutputStream。

3、SQLite数据库存储数据

SQLite是轻量级嵌入式数据库引擎,它支持 SQL 语言,并且只利用很少的内存就有很好的性能。

4、使用ContentProvider存储数据

ContentProvider是一种内容共享型组件,实际上它是通过Binder向其它应用提供数据。

5、网络存储数据

二十六、调试工具

Stetho、PostMan、flidder、青花瓷

二十七、适配

(一)屏幕适配:

刘海屏适配

郭霖:https://blog.csdn.net/guolin_blog/article/details/103112795

  1. 有在头部的左边、中、右边和顶部底部都有的屏幕
  2. Android 9.0系统中提供了3种layoutInDisplayCutoutMode属性来允许应用自主决定该如何对刘海屏设备进行适配。
  3. LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT:这是一种默认的属性,在不进行明确指定的情况下,系统会自动使用这种属性。这种属性允许应用程序的内容在竖屏模式下自动延伸到刘海区域,而在横屏模式下则不会延伸到刘海区域。
  4. LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES:这种属性表示,不管手机处于横屏还是竖屏模式,都会允许应用程序的内容延伸到刘海区域。
  5. LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER:这种属性表示,永远不允许应用程序的内容延伸到刘海区域。
  6. Android在9.0系统中提供了一套专门用于获取安全显示区域的API

----------------------------补充------------------------------------

3、Activity的启动过程(不要回答生命周期)

app启动的过程有两种情况,第一种是从桌面launcher上点击相应的应用图标,第二种是在activity中通过调用startActivity来启动一个新的activity。

  • 当我们点击图标的时候,系统就会调用startActivitySately(),启动的activity的相关信息都会保存在intent中,比如action等。
  • 同时系统也会启动一个PackaManagerService的包管理服务,这个管理服务会对AndroidManifest.xml文件进行解析,获得相关组件的信息,比如四大组件等。
  • 通过Instrumentation类中的execStartActivity方法来启动activity
  • Instrumentation这个类主要作用就是监控程序和系统之间的交互。而在这个execStartActivity()方法中会获取ActivityManagerService的代理对象,通过这个代理对象进行启动activity。
  • 启动就会调用一个checkStartActivityResult()方法,如果说没有在配置清单中配置有这个组件,就会在这个方法中抛出异常了。

   

6、java虚拟机和Dalvik虚拟机的区别 

Java虚拟机:

1、java虚拟机基于栈,使用指令来载入和操作栈上的数据,运行的是字节码

Dalvik虚拟机:

1、Dalvik虚拟机基于寄存器。

2、Dalvik运行的是自定义的.dex字节码格式。(java类被编译成.class文件后,会通过一个dx工具将所有的.class文件转换成一个.dex文件)

  

8、讲解一下Context 

  • Context是一个抽象基类。有两个子类,ContextWrapper是上下文功能的封装类,而ContextImpl则是上下文功能的实现类。
  • 而ContextWrapper又有三个直接的子类, ContextThemeWrapper、Service和Application。其中,ContextThemeWrapper是一个带主题的封装类,而它有一个直接子类就是Activity,所以Activity和Service以及Application的Context是不一样的,只有Activity需要主题,Service不需要主题。
  • Context一共有三种类型,分别是Application、Activity和Service。这三个类虽然分别各种承担着不同的作用,但它们都属于Context的一种,而它们具体Context的功能则是由ContextImpl类去实现的,因此在绝大多数场景下,Activity、Service和Application这三种类型的Context都是可以通用的。不过有几种场景比较特殊,比如启动Activity,还有弹出Dialog。出于安全原因的考虑,Android是不允许Activity或Dialog凭空出现的,一个Activity的启动必须要建立在另一个Activity的基础之上,也就是以此形成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),因此在这种场景下,我们只能使用Activity类型的Context,否则将会出错。

  • Context数量 = Activity数量 + Service数量 + 1 (1为Application)

9、理解Activity,View,Window三者关系

Activity控制单元,Window承载模型,View显示视图,LayoutInflater像剪刀,Xml配置像窗花图纸。

1:Activity构造的时候会初始化一个Window,准确的说是PhoneWindow。

2:这个PhoneWindow有一个“ViewRoot”,这个“ViewRoot”是一个View或者说ViewGroup,是最初始的根视图。

3:“ViewRoot”通过addView方法来一个个的添加View。比如TextView,Button等

4:这些View的事件监听,是由WindowManagerService来接受消息,并且回调Activity函数。比如onClickListener,onKeyDown等。

  

11、View的绘制流程

自定义控件:

1、组合控件。这种自定义控件不需要我们自己绘制,而是使用原生控件组合成的新控件。如标题栏。

2、继承原有的控件。这种自定义控件在原生控件提供的方法外,可以自己添加一些方法。如制作圆角,圆形图片。

3、完全自定义控件:这个View上所展现的内容全部都是我们自己绘制出来的。比如说制作水波纹进度条。

View的绘制流程:OnMeasure()——>OnLayout()——>OnDraw()

第一步:OnMeasure():测量视图大小。从顶层父View到子View递归调用measure方法,measure方法又回调OnMeasure。

第二步:OnLayout():确定View位置,进行页面布局。从顶层父View向子View的递归调用view.layout方法的过程,即父View根据上一步measure子View所得到的布局大小和布局参数,将子View放在合适的位置上。

第三步:OnDraw():绘制视图。ViewRoot创建一个Canvas对象,然后调用OnDraw()。六个步骤:①、绘制视图的背景;②、保存画布的图层(Layer);③、绘制View的内容;④、绘制View子视图,如果没有就不用;

⑤、还原图层(Layer);⑥、绘制滚动条。

13、保存Activity状态

onSaveInstanceState(Bundle)会在activity转入后台状态之前被调用,也就是onStop()方法之前,onPause方法之后被调用;

14、Android中的几种动画

帧动画:指通过指定每一帧的图片和播放时间,有序的进行播放而形成动画效果,比如想听的律动条。

补间动画:主要有Alpha、Scale、Translate、Rotate四种效果。注意:只是在视图层实现了动画效果,并没有真正改变View的属性。

属性动画:在Android3.0的时候才支持,通过不断的改变View的属性,不断的重绘而形成动画效果。

      

19、热修复的原理

  • Java虚拟机 —— JVM 是加载类的class文件的,而Android虚拟机——Dalvik/ART VM 是加载类的dex文件。
  • 加载类的时候都需要ClassLoader,ClassLoader有一个子类BaseDexClassLoader,而BaseDexClassLoader下有一个

数组——DexPathList,是用来存放dex文件。

  • 当BaseDexClassLoader通过调用findClass方法时,实际上就是遍历数组,

找到相应的dex文件,找到,则直接将它return。而热修复的解决方法就是将新的dex添加到该集合中,并且是在旧的dex的前面,所以就会优先被取出来并且return返回。

20、Android内存泄露及管理

1、内存溢出(OOM)和内存泄露(对象无法被回收)的区别。 

2、引起内存泄露的原因

3、内存泄露检测工具 ------>LeakCanary

内存溢出OOM:申请内存时,没有足够的内存空间供其使用

内存泄露 memory leak:申请内存后,无法释放已申请的内存空间

内存泄露原因:

一、Handler 引起的内存泄漏。

解决:将Handler声明为静态内部类,就不会持有外部类的引用,其生命周期就和外部类无关,如果Handler里面需要context的话,可以通过弱引用方式引用外部类。

二、单例模式引起的内存泄漏。

解决:Context是ApplicationContext,由于ApplicationContext的生命周期是和app一致的,不会导致内存泄漏。

三、非静态内部类创建静态实例引起的内存泄漏。

解决:把内部类修改为静态的就可以避免内存泄漏了

四、非静态匿名内部类引起的内存泄漏。

解决:将匿名内部类设置为静态的。

五、注册/反注册未成对使用引起的内存泄漏。

注册广播接受器、EventBus等,记得解绑。

六、资源对象没有关闭引起的内存泄漏。

在这些资源不使用的时候,记得调用相应的类似close()、destroy()、recycler()、release()等方法释放。

七、集合对象没有及时清理引起的内存泄漏。

通常会把一些对象装入到集合中,当不使用的时候一定要记得及时清理集合,让相关对象不再被引用。

21、Fragment与Fragment、Activity通信的方式

1.在activity通过对象调用方法

2.使用接口回调

3.使用广播

4.Fragment直接调用Activity中的public方法

22、Android UI适配

字体使用sp,使用dp,多使用match_parent,wrap_content,weight

图片资源,不同图片的的分辨率,放在相应的文件夹下可使用百分比代替。  

   

27、ANR

ANR全名Application Not Responding, 也就是"应用无响应". 当操作在一段时间内系统无法处理时, 系统层面会弹出上图那样的ANR对话框.

产生原因:

1、主线程被IO操作阻塞(从4.0之后网络IO不允许在主线程中)。

2、主线程中存在耗时的计算。

3、主线程中错误的操作,比如Thread.wait或者Thread.sleep等。

4、在规定的时间内没有响应

解决方式:

1、使用AsyncTask/Thread处理耗时IO操作。

2、使用Thread或者HandlerThread时,调用Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)设置优先级,否则仍然会降低程序响应,因为默认Thread的优先级和主线程相同。

3、使用Handler处理工作线程结果,而不是使用Thread.wait()或者Thread.sleep()来阻塞主线程。

4、Activity的onCreate和onResume回调中尽量避免耗时的代码。 BroadcastReceiver中onReceive代码也要尽量减少耗时,建议使用IntentService处理。

5、应用程序应该避免在BroadcastReceiver里做耗时的操作或计算。

6、service是运行在主线程的,所以在service中做耗时操作,必须要放在子线程中。

28、设计模式

单例模式:分为恶汉式和懒汉式

恶汉式:

public class Singleton{ 

    private static Singleton instance = new Singleton(); 

    public static Singleton getInstance() { 

        return instance ; 

    } 

}

懒汉式:

public class Singleton02{ 

    private static Singleton02 instance; 

    public static Singleton02 getInstance() { 

        if (instance == null){ 

            synchronized (Singleton02.class){ 

                if (instance == null){ 

                instance = new Singleton02(); 

                } 

            } 

        } 

        return instance; 

    } 

}

31、手写算法(选择冒泡必须要会)

32、JNI 

(1)安装和下载Cygwin,下载 Android NDK

(2)在ndk项目中JNI接口的设计

(3)使用C/C++实现本地方法

(4)JNI生成动态链接库.so文件

(5)将动态链接库复制到java工程,在java工程中调用,运行java工程即可

33、RecyclerView和ListView的区别

RecyclerView可以完成ListView,GridView的效果,还可以完成瀑布流的效果。同时还可以设置列表的滚动方向(垂直或者水平);

RecyclerView中view的复用不需要开发者自己写代码,系统已经帮封装完成了。

RecyclerView可以进行局部刷新。

RecyclerView提供了API来实现item的动画效果。

在性能上:

如果需要频繁的刷新数据,需要添加动画,则RecyclerView有较大的优势。

如果只是作为列表展示,则两者区别并不是很大。

  

42、OKhttp, Retrofit对比

OKhttp:Android开发中是可以直接使用现成的api进行网络请求的。就是使用HttpClient,HttpUrlConnection进行操作。okhttp针对Java和Android程序,封装的一个高性能的http请求库,支持同步,异步,而且okhttp又封装了线程池,封装了数据转换,封装了参数的使用,错误处理等。API使用起来更加的方便。但是我们在项目中使用的时候仍然需要自己在做一层封装,这样才能使用的更加的顺手。

Retrofit:Retrofit是Square公司出品的默认基于OkHttp封装的一套RESTful网络请求框架,RESTful是目前流行的一套api设计的风格, 并不是标准。Retrofit的封装可以说是很强大,里面涉及到一堆的设计模式,可以通过注解直接配置请求,可以使用不同的http客户端,虽然默认是用http ,可以使用不同Json Converter 来序列化数据,同时提供对RxJava的支持,使用Retrofit + OkHttp + RxJava + Dagger2 可以说是目前比较潮的一套框架,但是需要有比较高的门槛。

Java

1、线程中sleep和wait的区别

(1)这两个方法来自不同的类,sleep是来自Thread,wait是来自Object;

(2)sleep方法没有释放锁,而wait方法释放了锁。

(3)wait,notify,notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用。

  

3、关键字final和static是怎么使用的。

final:

1、final变量即为常量,只能赋值一次。

2、final方法不能被子类重写。

3、final类不能被继承。

static:

1、static变量:对于静态变量在内存中只有一个拷贝(节省内存),JVM只为静态分配一次内存,在加载类的过程中完成静态变量的内存分配,可用类名直接访问(方便),当然也可以通过对象来访问(但是这是不推荐的)。

2、static代码块:static代码块是类加载时,初始化自动执行的。

3、static方法:static方法可以直接通过类名调用,任何的实例也都可以调用,因此static方法中不能用this和super关键字,不能直接访问所属类的实例变量和实例方法(就是不带static的成员变量和成员成员方法),只能访问所属类的静态成员变量和成员方法。

4、String,StringBuffer,StringBuilder区别

1、三者在执行速度上:StringBuilder > StringBuffer > String (由于String是常量,不可改变,拼接时会重新创建新的对象)。

2、StringBuffer是线程安全的,StringBuilder是线程不安全的。(由于StringBuffer有缓冲区)

5、Java中重载和重写的区别:

1、重载:一个类中可以有多个相同方法名,但是参数类型和个数都不一样。

2、重写:子类继承父类,则子类可以通过实现父类中的方法,从而新的方法把父类旧的方法覆盖。

6、Http https区别

此处延伸:https的实现原理

1、https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。

2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。

3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。

4、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

https实现原理:

(1)https访问服务器,要求与服务器建立SSL连接。

(2)服务器收到客户端请求后,将网站的证书信息(证书中包含公钥)传送给客户端。

(3)客户端与服务器开始协商SSL连接的安全等级(信息加密的等级)。

(4)客户端通过会话密钥,利用网站的公钥将密钥加密,并传送给网站。

(5)服务器通过私钥解密,实现服务器与客户端之间的通信。

7、Http位于TCP/IP模型中的第几层?为什么说Http是可靠的数据传输协议?

tcp/ip的五层模型:

  • http位于模型中的应用层,IP网络层,tcp传输层
  • 从下到上:物理层->数据链路层->网络层->传输层->应用层
  • 由于tcp/ip是面向连接的可靠协议,而http是在传输层基于tcp/ip协议的,所以说http是可靠的数据传输协议。

8、HTTP链接的特点

HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为“一次连接”。

9、TCP和UDP的区别

tcp是面向连接的,由于tcp连接需要三次握手,保证连接的可靠性。

udp 不是面向连接的,是不可靠的,开销更小,传输速率更高,所以实时行更好。

10、Socket建立网络连接的步骤

建立Socket连接至少需要一对套接字,其中一个运行与客户端--ClientSocket,一个运行于服务端--ServiceSocket

1、服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。

2、客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。注意:客户端的套接字必须描述他要连接的服务器的套接字,

指出服务器套接字的地址和端口号,然后就像服务器端套接字提出连接请求。

3、连接确认:当服务器端套接字监听到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述

发给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务端套接字则继续处于监听状态,继续接收其他客户端套接字的连接请求。

断点下载:

在本地下载过程中要使用数据库实时存储到底存储到文件的哪个位置了,这样点击开始继续传递时,才能通过HTTP的GET请求中的setRequestProperty("Range","bytes=startIndex-endIndex");方法可以告诉服务器,数据从哪里开始,到哪里结束。同时在本地的文件写入时,RandomAccessFile的seek()方法也支持在文件中的任意位置进行写入操作。同时通过广播或事件总线机制将子线程的进度告诉Activity的进度条。关于断线续传的HTTP状态码是206,即HttpStatus.SC_PARTIAL_CONTENT。

//断点续传关键: 请求信息设置range字段

connection.setRequestProperty(

"Range", "bytes=" + start + "-" + threadInfo.getEnd());

RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rwd");

randomAccessFile.seek(start);

//注意下载时候这里的responseCode是206

if (HttpURLConnection.HTTP_PARTIAL == connection.getResponseCode())

补充知识点:

1、心跳机制实现:

长连接的实现:心跳机制,应用层协议大多都有HeartBeat机制,通常是客户端每隔一小段时间向服务器发送一个数据包,通知服务器自己仍然在线。并传输一些可能必要的数据。使用心跳包的典型协议是IM,比如QQ/MSN/飞信等协议。

在TCP的机制里面,本身是存在有心跳包的机制的,也就是TCP的选项:SO_KEEPALIVE。 系统默认是设置的2小时的心跳频率。但是它检查不到机器断电、网线拔出、防火墙这些断线。通过使用TCP的KeepAlive机制(修改那个time参数),可以让连接每隔一小段时间就产生一些ack包,以降低被踢掉的风险,当然,这样的代价是额外的网络和CPU负担。

2、Cookie与Session的作用和原理

Session是在服务端保存的一个数据结构,用来跟踪用户的状态,这个数据可以保存在集群、数据库、文件中。

Cookie是客户端保存用户信息的一种机制,用来记录用户的一些信息,也是实现Session的一种方式。

1、数据结构

Arraylist和LinkedList的区别

Arraylist:

优点:ArrayList基于动态数组的数据结构,是连着存储内,查询操作效率高。

缺点:ArraList是线程异步的,是不安全的,插入和删除操作效率比较低。

LinkedList:

优点:基于链表的数据结构,地址是任意的,对新增和删除操作比较占优势。LikedList 适用于要头尾操作或插入指定位置的场景。

缺点:因为LinkedList要移动指针,所以查询操作性能比较低。

HashMap和HashTable、HashSet的主要区别是什么

相同点:HashMap和HashTable二者都实现了Map 接口,底层实现都是数组 + 链表结构实现。

区别:

l HashTable 是线程安全,HashMap是非线程安全;

l HashMap 没有排序,允许一个null 键和多个null 值,而HashTable 不允许;

l HashSet:仅存储对象,调用add()方法添加元素。

ArrayMap跟SparseArray

ArrayMap、SparseArray的数据结构内部是使用两个数组进行数据存储,一个数组记录key的hash值,另外一个数组记录Value值,会对key使用二分法进行从小到大排序,在添加、删除、查找数据的时候都是先使用二分查找法得到相应的index,然后通过index来进行添加、查找、删除等操作。

区别:

1、如果key的类型已经确定为int类型,那么使用SparseArray,因为它避免了自动装箱的过程,如果key为long类型,它还提供了一个LongSparseArray来确保key为long类型时的使用

2、如果key类型为其它的类型,则使用ArrayMap。

浅拷贝和深拷贝

浅拷贝:被复制对象的所有值属性都含有与原来对象的相同,对象引用仍然指向原来的对象。

深拷贝:在浅拷贝的基础上,所有引用其他对象的变量也进行了clone,并指向被复制过的新对象。

java中==和equals和hashCode的区别

“= =”基本数据类型,也称原始数据类型byte,short,char,int,long,float,double,boolean   他们之间的比较,应用双等号(==),比较的是他们的值。

public class testDay {

    public static void main(String[] args) {

        String s1 = new String("11");

        String s2 = new String("11");

        System.out.println(s1 == s2);

        System.out.println(s1.equals(s2));

    }

}

结果是:false   true   

Equals:方法是用来判断其他的对象是否相等。但是String 、Math、Integer、Double等这些封装类在使用equals()方法时,已经覆盖了object类的equals()方法。

HashCode:  一个对象一般有key和value,可以根据key来计算它的hashCode值,再根据其hashCode值存储在不同的存储区域中。

总结:

       1.hashCode是为了提高在散列结构存储中查找的效率,在线性表中没有作用。

       2.equals重写的时候hashCode也跟着重写

       3.两对象equals如果相等那么hashCode也一定相等,反之不一定。

Java的四种引用及使用场景?

  • 强引用(FinalReference):在内存不足时不会被回收。
  • 软引用(SoftReference):在内存不足时会被回收。
  • 弱引用(WeakReferenc):只要GC回收器发现了它,就会将之回收。
  • 虚引用(PhantomReference):在回收之前,会被放入ReferenceQueue,JVM不会自动将该referent字段值设置成null。其它引用被JVM回收之后才会被放入ReferenceQueue中。用于实现一个对象被回收之前做一些清理工作。

怎么安全停止一个线程任务?原理是什么?线程池里有类似机制吗?

终止线程

1、使用violate boolean变量退出标志,使线程正常退出,也就是当run方法完成后线程终止。(推荐)

2、使用interrupt()方法中断线程,但是线程不一定会终止。

3、使用stop方法强行终止线程。不安全主要是:调用之后,创建子线程的线程就会抛出错误,并且会释放子线程持有的锁。

终止线程池

ExecutorService线程池就提供了shutdown和shutdownNow这样的生命周期方法来关闭线程池自身以及它拥有的所有线程。

1、shutdown关闭线程池,线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。

2、shutdownNow关闭线程池并中断任务。

直接在Activity中创建一个thread跟在service中创建一个thread之间的区别?

在Activity中被创建:该Thread的就是为这个Activity服务的,完成这个特定的Activity交代的任务,主动通知该Activity一些消息和事件,Activity销毁后,该Thread也没有存活的意义了。

在Service中被创建:这是保证最长生命周期的Thread的唯一方式,只要整个Service不退出,Thread就可以一直在后台执行,一般在Service的onCreate()中创建,在onDestroy()中销毁。所以,在Service中创建的Thread,适合长期执行一些独立于APP的后台任务,比较常见的就是:在Service中保持与服务器端的长连接。

请解释安卓为啥要加签名机制。

1、发送者的身份认证 由于开发商可能通过使用相同的 Package Name 来混淆替换已经安装的程序,以此保证签名不同的包不被替换。

2、保证信息传输的完整性 签名对于包中的每个文件进行处理,以此确保包中内容不被替换。

3、防止交易中的抵赖发生, Market 对软件的要求。

为什么bindService可以跟Activity生命周期联动?

1、bindService 方法执行时,LoadedApk 会记录 ServiceConnection 信息。

2、Activity 执行 finish 方法时,会通过 LoadedApk 检查 Activity 是否存在未注销/解绑的 BroadcastReceiver 和 ServiceConnection,如果有,那么会通知 AMS 注销/解绑对应的 BroadcastReceiver 和 Service,并打印异常信息,告诉用户应该主动执行注销/解绑的操作。

如何通过Gradle配置多渠道包?

android {  

    productFlavors {

        xiaomi {}

        baidu {}

        wandoujia {}

        _360 {}        // 或“"360"{}”,数字需下划线开头或加上双引号

    }

}

SharedPrefrences的apply和commit有什么区别?

这两个方法的区别在于:

1、apply没有返回值而commit返回boolean表明修改是否提交成功。

2、apply是将修改数据原子提交到内存,commit是同步的提交到硬件磁盘。

3、apply方法不会提示任何失败的提示。

4、如果对提交的结果不关心的话,建议使用apply,当然需要确保提交成功且有 后续操作的话,还是需要用commit的。

5、Android系统启动流程是什么?(提示:init进程 -> Zygote进程 –> SystemServer进程 –> 各种系统服务 –> 应用进程)

Android系统启动的核心流程如下:

  • 1、启动电源以及系统启动:当电源按下时引导芯片从预定义的地方(固化在ROM)开始执行,加载引导程序BootLoader到RAM,然后执行。
  • 2、引导程序BootLoader:BootLoader是在Android系统开始运行前的一个小程序,主要用于把系统OS拉起来并运行。
  • 3、Linux内核启动:当内核启动时,设置缓存、被保护存储器、计划列表、加载驱动。当其完成系统设置时,会先在系统文件中寻找init.rc文件,并启动init进程。
  • 4、init进程启动:初始化和启动属性服务,并且启动Zygote进程。
  • 5、Zygote进程启动:创建JVM并为其注册JNI方法,创建服务器端Socket,启动SystemServer进程。
  • 6、SystemServer进程启动:启动Binder线程池和SystemServiceManager,并且启动各种系统服务。
  • 7、Launcher启动:被SystemServer进程启动的AMS会启动Launcher,Launcher启动后会将已安装应用的快捷图标显示到系统桌面上。

如何提高推送的到达率?

原因:我们知道,推送的技术原理主要是保持网络的长连接,在TCP长连接建立成功的基础上,推送不能如期到达的原因主要和网络状况有关,比如不在线、网络慢、丢包。

解决方法:

1、利用broadCast+serivce,监听系统的广播,接收到后开启前台服务(startForegound(id,new Notification()))。

  1. 缓存推送消息:app接收到的推送后发送回执消息给服务,服务端保存没有推送成功的消息,等app上线后推送,推送成功后删除此消息。
  2. JobSchedule进行拉活。

APK打包流程:classes.dex,我们在项目中所编写的 java 文件,经过编译之后会生成一个 .class 文件,而这些所有的 .class 文件呢,它最终会经过 dx 工具编译生成一个 classes.dex。

加载一张超大图(BitmapRegionDecoder)

  1. 设置资源

 BitmapRegionDecoder bitmapRegionDecoder =

  BitmapRegionDecoder.newInstance(inputStream, false);

  1. 显示指定的区域

bitmapRegionDecoder.decodeRegion(rect, options);

  1. 自定义手势拖拽

socket 实现即时通讯

-----------------------------------面试问题-----------------------------------------

  1. java中如何调用私有方法(可以通过反射)

Class c = Class.forName("A");

zhidaoMethod m = c.getDeclaredMethod("method"); m.setAccessible(true);//再设置方法可见.

m.invoke(c.newInstance(), null);

2、什么时候触发GC

  1. 什么时候触发Young GC----针对年轻代
  • 当Eden区满了的时候,会触发Young GC

(2) 什么时候触发 Full GC----针对整个堆

·  在发生Young GC的时候,虚拟机会检测之前每次晋升到老年代的平均大小是否大于年老代的剩余空间,如果大于,则直接进行Full GC;如果小于,但设置了空间分配担保(Handle PromotionFailure),那么也会执行Full GC。

·  永久代空间不足、System.gc()、堆中分配很大的对象。

3、RxJava线程切换方式/背压模式

  1. 通过Handler mMainHandler.post(runnable)切换线程;
  2. 造成背压模式:被观察者大量发送事件,远远大于观察者处理事件的速度的时候,会造成内存溢出。这时候背压模式就产生了。
  3. 背压方式Flowable:

BackpressureStrategy.ERROR会抛出异常。BackpressureStrategy.BUFFER等待下游处理。BackpressureStrategy.DROP丢弃多余事件。BackpressureStrategy.LATEST只会存储128个事件。

  1. SingleInstanceStandardActivity>SingleInstanceActivity>StandardActivity...返回,SingleInstanceActivity这个是不会入MainActivity这个栈。

5、view绘制流程

经历三个最主要的阶段,即onMeasure()、onLayout()和onDraw()。

  1. onMeasure

View系统的绘制流程会从ViewRoot的performTraversals()方法中开始,在其内部调用View的measure()方法。measure()方法接收两个参数,widthMeasureSpec和heightMeasureSpec,这两个值分别用于确定视图的宽度和高度的规格和大小。如果是ViewGroup就循环调用measureChildren()方法来去测量子视图的大小。

MeasureSpec的值由specSize和specMode共同组成的,其中specSize记录的是大小,specMode记录的是规格。specMode一共有三种类型,如下所示:

EXACTLY:表示父视图希望子视图的大小应该是由specSize的值来决定的。

AT_MOST:最多只能是specSize中指定的大小。

UNSPECIFIED:设置成任意的大小。

  1. onLayout

measure过程结束后,视图的大小就已经测量好了,并调用View的layout()方法是用于给视图进行布局的,也就是确定视图的位置。layout()方法接收四个参数,分别代表着左、上、右、下的坐标,当然这个坐标是相对于当前视图的父视图而言的。

getMeasureWidth()方法在measure()过程结束后就可以获取到了,而getWidth()方法要在layout()过程结束后才能获取到。另外,getMeasureWidth()方法中的值是通过setMeasuredDimension()方法来进行设置的,而getWidth()方法中的值则是通过视图右边的坐标减去左边的坐标计算出来的。

  1.  onDraw(绘制视图)

ViewRoot中的代码会继续执行并创建出一个Canvas对象,然后调用View的draw()方法来执行具体的绘制工作。draw()方法内部的绘制过程总共可以分为六步:

  1. 绘制视图的背景;
  2. 保存画布的图层(Layer);
  3. 绘制View的内容;
  4. 绘制View子视图,如果没有就不用;
  5. 还原图层(Layer);
  6. 绘制滚动条。

6、各个版本API兼容

6.0运行时权限处理 

7.0 适配APK安装 

8.0 通知栏Notification

9.0 刘海屏适配

10.0 作用域存储

11.0 强制启用Scoped Storage

-----------------------------------Okhttp原理-------------------------------------

OkHttp内部的请求流程:

  1. 使用OkHttp会在请求的时候初始化一个Call的实例;
  2. 然后执行它的execute(同步)方法或enqueue(异步)方法
  3. 内部最后都会执行到拦截器链getResponseWithInterceptorChain()方法,依次经过用户自定义普通拦截器、重试拦截器、桥接拦截器、缓存拦截器、连接拦截器和用户自定义网络拦截器以及访问服务器拦截器等拦截处理过程,来获取到一个响应并交给用户。

2、拦截器

RetryAndFollowupInterceptor(重定向拦截器) 初始化、重试、处理异常。

BridgeInterceptor (桥接适配拦截器)主要的作用是补充http请求头。

CacheInterceptor (缓存拦截器)处理缓存的功能。

ConnectInterceptor(连接拦截器)主要作用是建立可用的连接。

CallServerInterceptor(访问服务器拦截器)主要的作用将http请求写入网络IO流当中,从中读取服务端返回给客户端的数据。

3、OkHttp使用到设计模式

责任链模式:每一个拦截器对应一个功能,创建一个接收者对象链,对发送者和接收者实现功能的解耦。

4、ConnectInterceptor之连接池

  • 判断当前的连接是否可用,如果无法使用,从连接池中获取一个连接;
  • 连接池中没有可用连接,创建新连接,并进行握手,然后将其放到连接池中。
  • 连接复用的一个好处就是省去了进行 TCP 和 TLS 握手的一个过程。因为建立连接也是需要消耗时间,连接被复用之后可以提升网络访问的效率。

-----------------------------------Retrofit原理-------------------------------------

Retrofit总结:

实际上是使用Retrofit接口层封装请求参数,之后由OkHttp完成后续的请求操作,在服务器返回数据之后,OkHttp将原始的结果交给Retrofit,根据用户的需求对结果进行解析。

Retrofit网络通信8个步骤:

1、创建Retrofit实例(构建者模式)

2、为接口中的方法添加注解

3、动态代理 生成 网络请求对象并发送网络请求(Call)

4、得到数据进行转换 解析数据

5、通过 回调执行器 切换主线程处理数据结果

baseUrl工作原理:将baseUrl地址转换成HttpUrl,转换成适合OkHttp的url。

GsonConverterFactory工作原理:创建一个对象,关联Gson并添加到数据转换器工厂中。

OkHttpCall:是对Call的一个封装,包含了请求的参数ServiceMethod

ServiceMethod工作原理:

  • 根据返回值类型和方法中的注解来从网络请求适配器、数据转换器工厂分别获取到网络适配器和数据转换器。
  • 获取到解析的参数,通过动态代理,把定义好的接口中方法、注解、内容转换成OkHttpCall对象。

RxJavaCallAdapterFactory实现原理:实现Factory>注册CallAdapter>Factory.get>adapt方法。

使用到的设计模式:

1、构建者模式(比如Retrofit创建,将复杂对象复杂度分离)

2、工厂模式(比如CallAdapter.Factory这个类,通过集成它实现不同的CallAdapter,侧重生成不同的对象)

3、外观模式(比如Retrofit是外观类,对外提供接口,屏蔽了其他类的变化)

4、策略模式(比如CallAdapter侧重方法的实现)

5、适配器模式(比如RxJavaCallAdapterFactory将一个接口转化成客户端希望的接口)

6、动态代理模式(比如Retrofit.create,通过动态代理将接口的注解和参数转化成Retrofit中的OkHttpCall进行网络请求)

7、观察者模式(比如OkHttpCall被观察者,数据的回调Callback观察者)

-----------------------------------Glide原理-------------------------------------

  1. Glide框架介绍、with方法详解

Glide#width方法总结:

  1. 生命周期的绑定:with传入的context决定了Glide的生命周期,如果传入的是activity,那么当activity被销毁后,Glide就也会被销毁。如果传入的ApplicationContext,那么它的生命周期就跟APP相同。
  2. RequestManager:用来管理Glide加载图片的请求类。RequestManagerRetriever:用来生产、管理和回收RequestManager的类。
  3. 当前获取RequestManager如果这个传入的是App上下文,直接将Glide和Application的声明周期绑定到一起;非App上下文请情况:创建一个无界面的RequestManagerFragment与RequestManager绑定到一起。
  1. load()详解

load初始化的操作,通过DrawableTypeRequest对象获取图片请求的request。

  1. into详解
  • 构建网络请求对象Request并执行,获取图片资源并解码,返回图片资源并在主线程显示图片。
  1. 三级缓存原理

1、Glide三级缓存分类:活动缓存、内存缓存、磁盘缓存

2、三级缓存的范围:

活动缓存:在某个Activity范围,页面退出该缓存就不存在

内存缓存:某个App范围,应用完全退出就不存在

磁盘缓存:整个系统,只要不删除数据,就一直存在

3、三级缓存的作用:

活动缓存:分担内存缓存的负担,

内存缓存:加快数据读取

磁盘缓存:进行永久性保持

4、活动缓存说明

活动缓存并不是我们熟悉的内存缓存,是Glide自己定义的一种缓存策略。

本质上就是HasMap,用了一次就缓存,以后需要就直接拿,不需要就清除这个缓存。

该策略的存在也是为了及时释放内存,不需要等等整个应用退出再释放内存,减轻应用内存负担。

活动缓存比内存缓存小,如果活动缓存满了,会自动写到内存缓存。

系统会对内存缓存进行自动管理,只要不是快速存放大内存文件,并且不一直占有内存对象,都不会内存溢出。

5、内存缓存说明(LruCache)

内存缓存是系统自身会管理的,但是可以继承LruCache,做进一步管理。LruCache算法:主要算法原理就是把最近使用的对象的强引用存储在LinkedHashMap上,并且把最近最少使用的对象在缓存池达到预设值之前从内存中移除。

6、磁盘缓存说明(DisLruCache)

磁盘缓存本质是本地文件缓存,但是通过普通的文件写入读取效率不高。

Glide中使用了DiskLruCache框架进行数据保存和读取。

效率高的主要原因是:磁盘缓存对图片文件进行了加密和压缩处理。

  1. Glide 三级缓存的使用顺序

活动缓存->内存缓存->磁盘缓存->网络获取

  1. 面试简答题

Q4:Glide 缓存原理,如何设计一个大图加载框架。

如何实现一个图片加载框架

https://www.jianshu.com/p/3df395d8a6bc

·  封装参数:从指定来源,到输出结果,中间可能经历很多流程,所以第一件事就是封装参数,这些参数会贯穿整个过程;

·  解析路径:图片的来源有多种,格式也不尽相同,需要规范化;

·  读取缓存:为了减少计算,通常都会做缓存;同样的请求,从缓存中取图片(Bitmap)即可;

·  查找文件/下载文件:如果是本地的文件,直接解码即可;如果是网络图片,需要先下载;

·  解码:这一步是整个过程中最复杂的步骤之一,有不少细节;

·  变换:解码出Bitmap之后,可能还需要做一些变换处理(圆角,滤镜等);

·  缓存:得到最终bitmap之后,可以缓存起来,以便下次请求时直接取结果;

·  显示:显示结果,可能需要做些动画(淡入动画,crossFade等)。

磁盘缓存原理:

一、磁盘缓存读取

  • 先去调用DecodeJob的decodeResultFromCache()方法来获取缓存,如果获取不到,会再调用decodeSourceFromCache()方法获取缓存,这两个方法的区别其实就是DiskCacheStrategy.RESULT和DiskCacheStrategy.SOURCE这两个参数的区别。
  • 调用了loadFromCache()方法从缓存当中读取数据,如果是decodeResultFromCache()方法就直接将数据解码并返回,如果是decodeSourceFromCache()方法,还要调用一下方法先将数据转换一下再解码并返回。传入的key也不同,resultKey、resultKey的getOriginalKey()。

二、磁盘缓存写入

l 原始图片行先判断是否允许缓存原始图片,缓存Key是用的resultKey.getOriginalKey(),添加到diskCacheProvider。

l 转换好的图片使用resultKey。

Q4:Glide VS Picasso

  • Google的推荐,更多应该归功于 GIF 的支持。
  • Picasso的默认质量是 ARGB_8888 Glide的默认质量则为 RGB_565(内存占用少)
  • Picasso直接缓存整张图片,Glide就不一样了,它会为每个不同尺寸的Imageview缓存一张图片,也就是说不管你的这张图片有没有加载过,只要imageview的尺寸不一样,那么Glide就会重新加载一次。

Q5:Glide VS fresco

  • 两个都支持 GIF。
  • Fresco最大只支持图片文件大小为 2M 。
  • Fresco使用方式上,fresco 推荐的是用他提供的 SimpleDraweeView . 这个方式意味着我们的迁移成本会非常的高,要改布局文件,其次还必须给定大小(或者比例)
  • fresco 更多是native实现。
  • Glide 各种 BitmapTransformation,比如圆形,圆角等,更让人喜欢。

-----------------------------------EventBus原理-------------------------------------

一、EventBus使用流程概念

1、Android事件发布/订阅框架

2、事件传递既可用于Android四大组件间通信

3、EventBus的优点是代码简洁,使用简单,事件发布、订阅充分解耦

4、首先有一个Publisher发射器,将Event通过Post发送到EventBus总线中,根据Event事件类型匹配相应的订阅者Subscriber。

二、源码解析

EventBus.getDefault()

1、使用了双重校验并加锁的单例模式来创建EventBus实例,里面的成员变量通过EventBusBuilder 构建者模式构建。

2、EventBus三个不同类型的事件发送器

  • mainThreadPoster:主线程事件发送器,通过它的mainThreadPoster.enqueue(subscription, event)方法可以将订阅信息和对应的事件进行入队,然后通过 handler 去发送一个消息,在 handler 的 handleMessage 中去执行方法。
  • backgroundPoster:后台事件发送器,通过它的enqueue() 将方法加入到后台的一个队列,最后通过线程池去执行,注意,它在 Executor的execute()方法 上添加了 synchronized关键字 并设立 了控制标记flag,保证任一时间只且仅能有一个任务会被线程池执行。
  • asyncPoster:实现逻辑类似于backgroundPoster,不同于backgroundPoster的保证任一时间只且仅能有一个任务会被线程池执行的特性,asyncPoster则是异步运行的,可以同时接收多个任务。

EventBus.getDefault().register(this)

1、根据当前注册类获取订阅方法列表(subscriberMethods) 。

2、使用了增强for循环令subsciber对象 对 subscriberMethods 中每个 SubscriberMethod 进行订阅。

3、在EventBus的 register() 方法的最后会调用 subscribe 方法。

注册中subscribe

1、首选判断是否有注册过该事件

2、然后再按照优先级加入subscriptionsByEventType的value的List中

3、然后再添加到typesBySubscriber的value的List中

4、分发事件checkPostStickyEventToSubscription

EventBus.getDefault().post()

1、在post有一个类型为ThreadLocal 的PostingThreadState,包含了一个 eventQueue 和其他一些标志位,把event保存到eventQueue;

2、最后调用postToSubscription,根据threadMode类型调用对应的发送器:

POSTING、MAIN、MAIN_ORDERED、BACKGROUND、ASYNC

EventBus.getDefault().unregister(this)

1、移除了该subscriber 的所有订阅信息。

2、移除了注册对象和其对应的所有 Event 事件链表。

EventBus.getDefault.postSticky()

1、先将该事件放入 stickyEvents 中。

2、会判断当前事件是否是 sticky 事件,如果是,则从 stickyEvents 中拿出该事件并执行 postToSubscription() 方法。

-----------------------------------RxJava原理-------------------------------------

一、RxJava原理

RxJava是基于Java虚拟机上的响应式扩展库,它通过使用可观察的序列将异步和基于事件的程序组合起来。与此同时,它扩展了观察者模式来支持数据/事件序列,并且添加了操作符,这些操作符允许你声明性地组合序列,同时抽象出要关注的问题:比如低级线程、同步、线程安全和并发数据结构等。

Observable:被观察者,一旦数据发生变化,会通过某种方式通知观察者;

Observer:观察者,监听被观察者发射的数据并作出响应;

subscribe:订阅,观察者订阅被观察者的数据变化;

Scheduler:调度器,用于解决多线程问题;

二、Observable#create() 创建被观察者过程

将自定义的ObservableOnSubscribe对象重新包装成了一个ObservableCreate对象。

三、Observable.subscribe() 订阅流程

Observable(被观察者)和Observer(观察者)建立连接(订阅)之后,会创建出一个发射器CreateEmitter,发射器会把被观察者中产生的事件发送到观察者中去,观察者对发射器中发出的事件做出响应处理。

四、线程切换

subscribeOn:指定被观察者的调度器;

observeOn:指定观察者的调度器;

五、调度器 Scheduler

Scheduler 是 RxJava 以一种极其简单的方式解决多线程问题的机制。不设置调度器的话,RxJava遵循哪个线程产生就在哪个线程消费的原则。

  • AndroidSchedulers.mainThread():是 RxAndroid 库提供的在 Android 平台使用的调度器,用于切换到 UI 线程;
  • Schedulers.io():用于IO密集型任务,例如读写SD卡文件,查询数据库,访问网络等,具有线程缓存机制,在此调度器接收到任务后,先检查线程缓存池中,是否有空闲的线程,如果有,则复用,如果没有则创建新的线程,并加入到线程池中,如果每次都没有空闲线程使用,可以无上限的创建新线程。
  • Schedulers.newThread():为每一个任务创建一个新线程,不具有线程缓存机制。虽然使用 Schedulers.io() 的地方,都可以使用 Schedulers.newThread(),但是因为可以重用线程,Schedulers.io() 比 Schedulers.newThread() 的效率更高。
  • Schedulers.computation():用于CPU密集型计算任务,即不会被I/O等操作限制性能的耗时操作,例如xml、json文件的解析,图片的压缩取样等。
  • Schedulers.trampoline():在当前线程中立刻执行,如当前线程中有任务在执行则将其暂停,等插入进来的任务执行完成之后,再继续未完成的任务。
  • Schedulers.single():拥有一个线程单例,所有的任务都在这一线程中执行,当此线程中有任务执行时,其他任务将会按照先进先出的顺序依次执行。
  • Schedulers.from(Executor executor):指定一个线程调度器,由此调度器来控制任务的执行。

六、操作符

 创建操作符:

  • Create:创建Observable. 
  • From:将数组、Iterable、Future、Callable、Completable、Maybe、Optional、Publisher、Runnable、Single、Stream等转换为Observable,对于Iterable和数组,产生的Observable会发射Iterable或数组的每一项数据。
  • Interval:创建一个按固定时间间隔发射整数序列的 Observable。
  • Just:创建一个发射指定值的Observable,接受1~10个参数,返回一个按参数列表顺序发射这些数据的Observable。
  • Range:创建一个发射一个范围内的有序整数序列的 Observable。

转换操作符

  • Buffer:定期收集 Observable 的数据放进一个数据包裹,然后发射这些数据包裹,而不是一次发射一个值。
  • Map:对原始Observable发射的每一项数据应用一个你选择的函数,然后返回一个发射这些结果的Observable。
  • FlatMap:将一个发射数据的Observable变换为多个Observables,然后将它们发射的数据合并后放进一个单独的Observable。
  • GroupBy:将原始 Observable 拆分为一些 Observables 集合,它们中的每一个发射原始 Observable 数据序列的一个子序列。

过滤操作符

  • Distinct:过滤掉重复的数据项,只允许还没有发射过的数据项通过。
  • ElementAt:只发射指定索引位置的数据项。
  • Filter:指定函数过滤数据项。
  • First:只发射第一个数据项。 
  • Last:只发射最后一个数据项。
  • Skip:跳过前面n个数据项。
  • Take:只发射前面n个数据项。

组合操作符

  • Zip:使用指定函数按顺序结合两个或多个 Observables 发射的数据项,然后发射这个函数返回的结果。它按照严格的顺序应用这个函数。它只发射与发射数据项最少的那个Observable一样多的数据。
  • Merge:将多个 Observables 的输出项合并为一个 Observable。
  • StartWith:在数据序列的开发插入一条指定的项。   

错误处理操作符

  • catch:拦截原始Observable的onError通知,将它替换为其它的数据项或数据序列,让产生的Observable能够正常终止或者根本不终止。
  • retry:如果原始 Observable 遇到错误,重新订阅它期望它能正常终止。它不会将原始Observable的onError通知传递给观察者,它会订阅这个Observable,再给它一次机会无错误地完成它的数据序列。Retry总是传递onNext通知给观察者,由于重新订阅,可能会造成数据项重复。

RxJava实例应用:有三个接口A、B、C,B、C请求接口返回数据插入A中?

Zip:把多个请求合并,把数据合并后再发射

Merge:不是顺序发射,需要通过类型去判断到底是哪个数据

Concat:顺序发射,需要通过类型去判断到底是哪个数据

1、多个接口依次请求

observable

              .subscribeOn(Schedulers.io())

//设定第一次请求结果放在io线程并同时执行第二次请求

                .observeOn(Schedulers.io())                   

.flatMap(newFunc1>() {

                   @Override

                   Public Observable call(String s) {

              //拿到第一次请求结果,从string中取到access_token和 openid

               //执行第二次请求

                            return api.getUseInfo(access_token, openid);

                    }

                    })

//设定第三次请求结果放在io线程并同时执行次自己的登录请求

           .observeOn(Schedulers.io())                    

.flatMap(newFunc1>() {

                            @Override

                            Public Observable call(String s) {

                     //拿到微信返回的用户信息去执行自己服务端的登录请求

                      return api.MyLogin(s);

                            }

                    })

             .observeOn(AndroidSchedulers.mainThread())

             .subscribe(newBaseObserver() {

          @Override

               public void onNext(String s) {

           super.onNext(s);

                            //自己的登录请求结果

                    }

                    @Override

                    public void onCompleted() {

                                super.onCompleted();

                    }

   });

Merge

面试技术点_第4张图片

Zip

面试技术点_第5张图片

1.merge应用场景:复杂页面一次性要调用多个接口使用2.flatMap应用场景:一个接口调用时依赖另一个接口的返回值,如一个接口调用成功后才调用第二个接口或者第二个接口需要第一个接口的返回值作为参数调用3.zip应用场景:两个接口的数据相互结合才能显示UI

----------------------------------Kotlin基础------------------------------------

1、声明变量

var count: Int = 5;

val count: Int = 5;(只读变量)

2、声明常量

const val MAX_COUNT: Int = 5;

3、range:表示某个范围

  1. 声明一个方法

private fun countPerson(health: Int, isBlessed : Int){

}

5、标准库函数

apply:相当于配置函数,给对象设置属性,返回对象

let: return isVip?.let{"是的"}?:"不是"; 只有isVip不为空才执行,let函数才会执行。

run:和apply差不多,但是不返回接收者

with:和run功能行为一样,但是需要传一个参数

also:和let功能相似,但是返回对象

takeIf:进行判断,true返回对象,false则返回null;

takeUnless:只有false才返回对象

6、函数式编程

变换

map:遍历接收者集合,让变换器函数作用于集合里的各个元素。会把上个集合返回给下一个。

flatmap:将多个集合合并返回

过滤

filter:it.filter{ it.contains("red")} 返回包含 red 字段

合并

zip:把两个集合合并后返回包含键值对的新集合

7、kotlin页面跳转

val intent = Intent(this@MainActivity, LinearLayoutActivity::class.java)

startActivity(intent)

8、kotlin协程简介

async:创建协程使用,某个时间完成

launch:创建协程,立即执行指定任务

await:挂起的函数线程还能用来执行其他协程

--------------------------面试问到的问题-----------------------------

1、JNI内存泄露检测

  • 使用LeakTracer,通过重写libc中的malloc、free、new、delete函数和操作符,记录内存申请和释放操作来判断是否内存泄露。
  • 调用该库leak-tracer-helpers文件中的工具对输出的日志进行分析,打印出内存泄露的代码位置以及栈信息。

2、SQLite查询列表中数量最多的(比如有iphone10,iphone11,iphone12)

3、线程池

4、横竖屏切换生命周期

  1. 基本数据类型大小

Byte 字节, 8位;

Boolean ,1个字节

Short , 2个字节,16位;

char ,2个字节,16位;

Int , 4个字节,32位;

float, 4个字节,32位;

Long ,8个字节,64位;

double,8个字节,64位;

  1. OkHttp缓存的处理
  2. 多线程编程
  3. 断点续传
  4. 屏幕适配原理(比如头条)
  5. Glide大量数据,如何保证性能
  • 加大可同时运行的线程数(默认是4),选择无限制的线程数:builder.setSourceExcutor(GlideExecutor.newUnlimitedSourceExecutor)
  1. IM怎么做
  2. 事件分发
  3. 自定义控件步骤
  4. 子线程为什么不能更新UI
  • UI控件非线程安全,在多线程中并发访问可能会导致UI控件处于不可预期的状态
  1. APK打包流程
  2. == equals hascode 区别

你可能感兴趣的:(2020面试秘籍,面试,android)