美文网首页
一大波面试题

一大波面试题

作者: SlideException | 来源:发表于2020-02-25 13:45 被阅读0次

    rxjava是如何实现线程切换的 glide对比其他图片加载框架的优势 热修复原理 kotlin协程 mvp mvc mvvm

    object?.let 可以统一做判空处理,内部可以用it代替操作的对象,返回值为函数最后一行或指定return表达式,如果最后一行为空就返回一个Unit类型的默认值

    with(parm){} 不能统一做判空处理,方法内传入参数或对象,如果最后一个参数是个函数,可以移到括号外部,内部可以直接调用该对象的属性或方法,返回值为最后一行或指定return表达式。

    object?.run 可以统一做判空处理,可以说是let和with的结合,只接受一个lambda表达式,返回值为最后一行或指定return表达式。

    object?.apply{ object }? 可以统一做判空处理,返回值是传入对象的本身。多层判空的优化。可以函数链式调用

    object.also{} 可以统一做判空处理,返回传入对象的本身。可用于扩展函数链式调用


    协程是一个轻量级的线程。GlobalScope.launch(Dispatchers.Default){ }

    协程是一个轻量级的线程。GlobalScope.launch(Dispatchers.Default/Main/IO/Unconfined在调用的线程直接执行){ } 线程是由系统调度的,阻塞和切换都会消耗CPU资源,阻塞时相当于一种资源的浪费,无法人为做控制。协程的挂起不会阻塞线程,几乎是无代价的,协程是由开发者控制的,一个线程可以创建任意个协程。协程代码程序流是顺序执行的,不需要一堆回调函数,也可以手动执行和结束,是可控的

    协程体是一个suspend关键字修饰的一个无参,无返回值的函数类型,被suspend修饰的函数称为挂起函数,与之对应的关键字是resume(恢复)

    suspend 方法只能在协程里面调用,不能再协程外调用
    suspend方法的本质是异步返回(不是异步回调)

    runBlocking{ } 连接阻塞与非阻塞的世界。
    runBlocking{}都可以在非协程作用域下创建一个协程作用域,在runBlocking作用域里面使用delay()会阻塞他的调用线程,直到他内部都执行完毕。runBlocking为协程的作用域,如果要在非协程作用域调用协程,可以用GlobalScope.launch{}

    runBlocking{}和GlobalScope.launch{}的区别?
    GlobalScope.launch会启动一个top-level的协程,他的生命周期将只受到整个应用程序生命周期的影响。不会阻塞当前线程。
    runBlocking{}协程作用域如果销毁了,里面的协程也随之失效,就好比变量的作用域。
    runBlocking {}是创建一个新的协程同时阻塞当前线程,直到协程结束。

     withContext {}和async {}
    withContext {}不会创建新的协程,在指定协程上运行挂起代码块,并挂起该协程直至代码块运行完成。
    CoroutineScope.async {}可以实现与 launch builder 一样的效果,在后台创建一个新协程,唯一的区别是它有返回值,因为CoroutineScope.async {}返回的是 Deferred 类型

    runBlocking{
        val job = launch{
            }
        val job1  =  async{
                10
            }
        job1.await()
    }
    launch和async都可以在协程作用域下启动协程。

    launch启动一个协程后,会返回一个job对象,这个job对象不含有任何数据。它只是表示启动的协程本身,我们可以通过job对协程进行控制.
    async依然返回一个job对象,但是这个对象可以带上返回值,使用await()来获取他的返回值。


    okhttp源码知识点

    同步基本流程:

    1.生成OkHttpClient,可以通过new OkHttpClient.Builder(),也可以直接new OkHttpClient()。

    2.生成Request,new Request.Builder().url("").build()。

    3.okHttpClient.newCall(request).execute();//同步 enqueue//异步

    newCall(request) 生成一个Call,一个Call只能被执行一次,RealCall实现了Call接口,对同步异步方法进行了实现,

    同步生成的是RealCall对象,异步生成的是AsyncCall对象,AsyncCall是runnable的子类

    如果可以执行,则对当前请求添加监听操作,然后将Call对象放入调度器Dispatcher中,最后由拦截器链中的各个拦截

    器来对该请求进行处理,最终返回Response。

    Dispatcher调度器。

    默认最大支持64个请求,默认单个Host最大支持5个请求。

    Dispatcher使用了一个Deque保存同步任务,使用了两个Deque保存异步任务,两个中一个是准备执行的请求,

    另一个是正在执行的请求。

    为啥异步的是俩?

    因为默认最大64个请求,如果超过的话会先放到准备的Deque里,当有空闲线程时,再将ready的移到running里

    拦截器链

    各大拦截器:

    retryAndFollowUpInterceptor、BridgeInterceptor、CacheInterceptor、ConnectInterceptor、networkInterceptor、

    CallServerInterceptor

    将这些拦截器传给了RealInterceptorChain(拦截器链)

    retryAndFollowUpInterceptor:主要负责请求的重定向操作,用于网络请求中,请求失败后的重试机制,如果是

    路由或者连接异常,则尝试恢复,否则根据responseCode会对request进行再处理得到新的request。如果返回码是

    200就结束了。

    BridgeInterceptor:桥拦截器。主要负责为请求添加请求头,为响应添加响应头。

    CacheInterceptor:缓存拦截器。服务器收到请求时,会在200 OK中回送该资源的Last-Modified和ETag头(前提

    服务器支持缓存的情况下才有这俩)。客户端将该资源保存在cache中,并记录这两个属性。当客户端发送相同请求

    时,现根据Data+Cache Control来判断缓存是否过期,如果过期了,会在请求中携带If-Modified-Since和If-None-

    Match两个头。分别对应Last-Modified和ETag。

    ConnectInterceptor:核心连接池,首先排除链接不可用的情况,如果链接可用就结束了。如果链接不可用会第一次连接池查找,如果没找到会进行二次连接池查找,会判断是否找到可用链接,如果没找到会生成Connection对象,连接Server(TCP+TLS handshake),再将新的连接放入连接池,然后判断是否是Http2连接,然后确认http2的多路复用特性,

    connectInterceptor中的几个关键点:ThreadPoolExecutor、队列Deque、路由记录表。

    networkInterceptor:在非 WebSocket 请求时会添加该拦截器 addNetworkInterceptor,能够操作中间过程的响应,如重定向和重试,当网络短路而返回缓存响应时不被调用,只观察在网络上传输的数据,携带请求来访问链接。

    CallServerInterceptor:返回最终Response请求。


    事件的传递中OnTouchListener的优先级是高于OnClickListener的!并且OnClickListener的调用与OnTouchListener的返回值有关,返回为false时候会调用,返回true的时候不调用!


    RequestOptions options = new RequestOptions()

                .placeholder(R.drawable.ic_launcher_background)

                .error(R.mipmap.ic_launcher)

                .diskCacheStrategy(DiskCacheStrategy.NONE)

          .override(200, 100);

    Glide.with(this)

                .load(imgUrl)

                .apply(options)

                .into(mIv2);

    Glide缓存分为两块 内存缓存和硬盘缓存

    内存缓存:防止应用重复将图片数据读取到内存当中。

    硬盘数据:防止应用重复从网络或其他地方重复下载和读取数据。

    内存缓存:

    Glide内存缓存的实现 使用LruCache算法与弱引用结合。

    如果loadFromCache()方法和loadFromActiveResources()方法都没有获取到缓存才会继续向下执行,从而开启线程

    加载图片。

    loadFromCache使用的是lru算法 loadFromActiveResources使用的弱引用的HashMap。

    首先会将缓存图片从activeResources中移除,然后再将它put到LruResourceCache中。这样也就实现了正在使用中

    的图片使用弱引用来进行缓存,不在使用中的图片使用LruCache进行缓存。

    硬盘缓存:

    DiskCacheStrategy.None 不缓存任何内容。

    DiskCacheStrategy.SOURCE 只缓存原始图片。

    DiskCacheStrategy.Result 只缓存转换过后的图片(默认)。

    DiskCacheStrategy.ALL 既缓存原始图片,也缓存转换过后的图片。

    glide默认不会将原始图片展示出来,而是会对图片进行压缩和转换。

    和内存缓存类似,硬盘缓存也是使用LruCache算法,谷歌还提供了一个专门的工具类DiskLruCache。Glide使用的是

    自己编写的DiskLruCache工具类。

    根据url作为key进行缓存的策略,可能会遇到url后面拼接一个可变的token,就导致了key的变化,导致图片不变

    却缓存了很多份,可以自定义类继承GlideUrl 。


    butterknife是一个视图注入框架,主要通过APT技术,在编译阶段处理注解,生成一个新的class类

    它没有用到反射技术,性能无损耗。

    原理

    ButterKnife.bind(this);

    根据activity获取顶层视图DecorView,然后调用bind方法,拿到集成Unbinder子类构造器,

    即生成xxx_ViewBinding类构造器,创建对象,这个类在编译时自动生成,加载类后没拿到构造器,

    保存到一个LinkedHashMap,防止每次Class.forName加载。


    内存泄露优化注意事项:

    1、静态单利类里的context用getApplicationContext代替

    2、注册取消注册

    3、Handler匿名内部类持有外部activity的引用 改为静态内部类

    4、webview导致内存泄露 onDestroy中置为null

    5、dialog、popupwindow 在activity前dismiss

    6、CountDownTimer cancel

    Cursor关闭

    少用枚举 枚举比静态变量多消耗两倍内存


    传统线程的缺陷:

    ·在任务众多的情况下,系统要为每一个任务创建一个线程,而任务执行完毕后会销

    毁每一个线程,造成频繁的创建与销毁。

    ·多个线程频繁的创建会占用大量的资源,并且在资源竞争的时候容易出现问题,同

    时这么多的线程缺乏一个统一的管理,容易造成界面卡顿。

    ·多个线程频繁的销毁,会频繁调用GC机制,会使性能降低又非常耗时。

    线程池优点:

    ·重用线程池中的线程,线程在执行完后不会立刻销毁,而会等待另外的任务,这样

    不会频繁的创建、销毁和调用GC

    ·有效控制线程池最大并发数,避免大量线程抢占资源出现的问题

    ·对多个线程进行统一的管理,可提供定时执行以及指定间隔循环执行的功能。

    重用

    1、继承Thread类 复写run方法 

    2、实现Runnable接口 复写run方法

    3、实现Callable接口 复写 复写call方法 该接口有泛型  与其他两个相比有返回值

    ,返回值为泛型,

    FutureTask<Integer> demo03 = new FutureTask<>(new Dome3

    ());//Demo3实现Callable类并复写call方法

    调用为new Thread

    (demo03,"Thread_Name").start();

    一般返回值调用get()方法放在代码最后,因为get

    方法有可能造成阻塞。

    4、AsyncTask类 底层封装了线程池和Handler 复写doInBackground方法、

    onProgressUpdate方法、onPostExecute方法、onPreExecute方法

    取消方法 cancel

    (boolean)

    5、HandlerThread new的时候加String 要调用start方法 与Handler结合使用 取消的

    时候要quit()

    6、继承IntentService类 内部采用了HandlerThread来执行任务,任务执行完毕会自

    动退出。复写onHandleIntent()方法 、onCreate、onStartCommand、onDestroy

    7、线程池

    线程池解决了两个问题:

    1.提升性能:通常在执行大量异步任务时,减少了每个任务的调用开销,并且提供了

    一种限制和管理资源的方法,使性能得到提升。

    2.统计信息:每个ThreadPoolExecutor保持一些基本的统计信息,例如完整的任务数

    量。

    Exectuors.newCachedThreadPool();//无界线程池,自动线程回收。

    Executors.newFixedThreadPool(int size);//固定大小的线程池。只有核心线程,无非核心线程无超时时长,并且阻塞队列无界。

    Executors.newSingleThreadExecutor();//单一后台线程。一个任务一个任务执行的场景

    new ThreadPoolExecutor(

              int corePoolSize,//2 线程池核心线程数(平时保留的线程数)

                    int maximumPoolSize,//5 线程池最大线程数(当workQueue都放

    不下时,启动新线程,最大线程数)

      long keepAliveTime,//超出corePoolSize数量的线程的保留时间。

                    TimeUnit unit,//keepAliveTime单位

            BlockingQueue<Runnable> workQueue,//阻塞队列,存放来不及

    执行的线程  new LinkedBlockingDeque<>(3)

      ThreadFactory threadFactory,//线程工厂

    Executors.defaultThreadFactory()

                RejectedExecutionHandler

    handler)//饱和策略  new ThreadPoolExecutor.AbortPolicy()

    workQueue:阻塞队列

    ,存放来不及执行的线程。

    ·ArrayBlockingQueue  构造函数一定要传大小

    ·LinkedBlockingQueue 构造函数不传大小会默认为Integer.MAX_VALUE 当大量请求

    任务时容易耗尽内存

    ·SynnchronousQueue 同步队列 一个没有存储空间的阻塞队列 ,将任务同步交付给

    工作线程

    ·优先队列

    handler:拒绝策略

    ·AbortPolicy(默认):直接抛弃

    ·CallerRunsPolicy:用调用者的线程执行任务

    ·DiscardOldestPolicy:抛弃队列中最久的任务

    ·DiscardPolicy:抛弃当前任务

    构造方法详解(最多参数为例):

    1.corePoolSize 线程池的核心线程数。

    2.maximumPoolSize 线程池最大线程数。

    可通过setCorePoolSize和setMaximumPoolSize方法进行修改。

    当在execute(Runnable)提交新任务时,如果当前少于corePoolSize,即使有工作线程

    处于空闲状态,也会创建一个新线程来处理该请求;如果多于corePoolSize但小于

    maximumPoolSize的线程正在运行,则如果队列没有满,则进入等待队列,如果队列已

    满才会创建新线程。如果maximumPoolSize已满,则执行拒绝策略。

    可以通过设置coorPoolSize和maximumPoolSize相同来创建一个固定大小的线程池。

    可以通过设置maximumPoolSize大小为Integer.MAX_VALUE,创建一个允许任意数量的并

    发任务

    核心线程预启动

    默认情况下,只有当新任务到达时,才开始创建和启动核心线程。但是可以通过

    threadPoolExecutor.prestartCoreThread()//创建一个空闲任务线程等待任务的到达

    threadPoolExecutor.prestartAllCoreThreads()//创建核心线程池数量的空闲任务线

    程等待任务的到达

    3.keepAliveTime 线程存活时间

    如果当前线程池拥有超过corePoolSize大小的线程,那么多余的线程将在超过

    keepAliveTime时被终止,可以调用allowCoreThreadTimeOut(true) 将此策略用于核

    心线程。

    4.TimeUnit 单位

    5.BlockingQueue<Runnable> 阻塞队列 主要用来存储已经被提交但尚未执行的任务

    ,是用exectue方法来提交的

    三种队列:

    直接握手队列,SynchronousQueue//他将任务交给线程而不需要保留,如果没有线程

    立刻运行他,那么排队任务尝试失败会构建新的线程。当任务持续以平均提交速度大

    于平均处理速度时,会导致线程数量无限增长的问题。

    无界队列,当所有corePoolSize线程繁忙时,使用无界队列(没有预定义容量的

    LinkedBlockingQueue)将导致新任务在队列中等待,从而导致maximux的值没有任何

    作用,当任务持续以平均提交速度大于平均处理速度,会导致队列无限增长的问题。

    有界队列, 一个有界队列(ArrayBlockingQueue)和有限的maximum配置有助于防止

    资源耗尽,但是难以控制。使用大队列和较小的maximumPoolSizes可以最大限度地减

    少CPU使用率,操作系统资源和上下文切换开销,但会导致人为的低吞吐量。如果任务

    经常被阻塞(比如I/O限制),那么系统可以调度比我们允许的更多的线程。

    使用小队

    列通常需要较大的maximumPoolSizes,这会使CPU更繁忙,但可能会遇到不可接受的调

    度开销,这也会降低吞吐量。

    有就新建,大于了核心就先用闲着的,大于了核心而且没有闲着的就创建,直到超出

    maximum就拒绝。

    6.ThreadFactory 为线程池提供创建新线程的功能,一般使用默认即可

    7.RejectedExecutionHandler饱和策略。

    AbortPolicy(中止策略)默认策略,抛出运行时异常RejectedExecutionException。

    CallerRunsPolicy,简单的反馈控制机制,可以减慢提交任务的速度

    DiscardPolicy 直接丢弃新提交的任务

    DiscardOldestPolicy,如果执行器没有关闭,队列头的任务将会被抛弃,然后执行器

    重新尝试执行任务(如果失败则重复这一过程)

    execute方法与submit方法的区别 submit方法内部还是调用了execute方法,返回的是

    一个Future对象

    ThreadPoolExecutor 执行任务时大致遵循如下流程:

    1.如果线程池中的线程数未达到核心线程数,则会立马启用一个核心线程去执行。

    2.如果线程池中的线程数已经达到核心线程数,且任务队列workQueue未满,则将新线

    程放入workQueue中等待执行。

    3.如果线程池中的线程数已经达到核心线程数但未超过线程池规定最大值,且

    workQueue已满,则开启一个非核心线程来执行任务。

    4.如果线程池中的线程数已经超过线程池规定最大值,则拒绝执行该任务,采取饱和

    策略,并抛出RejectedExecutionException异常。


    性能优化

    内存 渲染 耗电方面

    内存泄露是指程序在申请内存后,无法释放已申请的内存空间,

    一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。

    内存溢出指程序申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,

    但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,即所谓的内存溢出。

    造成内存泄漏:

    1、非静态内部类持有外部类的引用  -》使用静态内部类 不持有外部引用

    2、单例的context 随着activity的销毁 还持有activity的引用 -》 使用context.getApplicationContext

    3、静态变量指向的引用永远不会被回收 -》 尽量少使用 不使用时重置为null

    4、资源对象未及时关闭,数据库,IO流,文件流 -》读写时通常使用了缓冲,缓冲一直被占用而得不到释放

    5、未取消注册或回调。-》取消注册广播

    6、动画导致。动画是个耗时操作,启动了动画在页面或视图销毁时没有调用cancel,这个动画虽然看不见了

    但会一直不断的播下去,

    7、webview造成内存泄露。activity销毁后webview持有的引用得不到释放。

    有时候获取一张图片只是为了得到图片的一些信息,比如宽高,不需要显示在界面上,这时候

    就不必吧图片加载到内存 可以用BitmapFactory.Options() 的 inJustDecodeBounds属性设置为true

    应用瘦身:

    只是用一套图片 xxhdpi 1080p

    资源图片转成webp格式

    布局优化

    层级少的情况下 LineraLayout>RelativeLayout 原因 llout的测量在不设置weidgt的情况下只测量一次,而rlout要测量两次

    使用约束布局、使用<include>标签 、使用<ViewStub>延迟view的加载、减少层级使用<merge>替换父级布局

    注意使用wrap_content 会增加测量成本 、删除无用属性。

    渲染优化

    移除window或xml中非必要的background


    ==比较对象 ===比较地址值

    ?可null !!断言不为null

    .. [ ]

    until [ )

    step 2 跳2

    open class  :

    委托代理by

    单例 object

    lateinit var

    const val

    companion object 伴生对象

    internal 修饰的同一个module下可见

    as强转

    类名::class.java

    inner 内部类

    object是Any

    多层嵌套可以给循环起名字

    Outter@for(;;){

      Inner@for(;;){

        break@Inner

    }

    }


    LayoutParams 的作用是子控件告诉父控件自己要如何布局


    安卓中View和ViewGroup在点击的时候有两个方法,onTouch和onTouchEvent

    onTouch是设置是了onTouchListener之后的回调方法,如果设置了onTouchListener就会回调onTouch方法,在outouch方法返回true时,onTouchEvent不会再被调用,如果没有设置onTouchListener或者onTouch返回false时,onTouchEvent会被调用,onClickListener是在onTouchEvent中被调用的 优先级最低

    onTouch>onTouchEvent>onClickListener


    Hanlder消息机制

    每个线程中只会有一个MessageQueue对象,MessageQueue是在Looper的构造函数中创建的,一个MessageQueue对应一个Looper。每个线程中也只会有一个looper对象

    子线程中直接调用new Hander()会导致崩溃,1.可以调用Looper.prepare()再调用Looper.loop() 2.可以Handler构造参数中传入Looper.getMainLooper()

    异步处理消息流程:首先需要在主线程中创建一个Handler对象,并重写handlerMessage方法,然后当子线程中需要进行UI操作时,就创建一个Message对象,并通过Handler将这条消息发送出去。之后这条消息会被添加到MessageQueue队列中等待被处理,而Looper则会一直尝试从MessageQueue中取出待处理的消息,最后返回给Handler的handlerMessage中

    主线程为什么不会被阻塞:https://www.zhihu.com/question/34652589

    new Message和Message.obtain的区别
    Message.obtain和Handler().obtainMessage()是一样的,Handler().obtainMessage()内部也调用了Message.obtain(),与直接new的区别是,省去了创建对象,内部直接从消息池里面拿,省去了创建对象申请内存的开销。

    sendMessage和postMessage的区别
    主要区别在于是否等待其他消息的处理,postMessage只是把消息放入队列,不管其他程序是否处理都会返回然后继续执行,sendMessage必须等待其他程序处理消息后才返回,继续执行,返回值表示其他程序处理后的返回值,postMessage返回值表示postMessage函数执行是否正确,

    quit和quitSafely区别
    当我们调用Looper的quit方法时,实际上执行了MessageQueue中的removeAllMessagesLocked方法,该方法的作用是把MessageQueue消息池中所有的消息全部清空,无论是延迟消息(延迟消息是指通过sendMessageDelayed或通过postDelayed等方法发送的需要延迟执行的消息)还是非延迟消息。

    当我们调用Looper的quitSafely方法时,实际上执行了MessageQueue中的removeAllFutureMessagesLocked方法,通过名字就可以看出,该方法只会清空MessageQueue消息池中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让Handler去处理,quitSafely相比于quit方法安全之处在于清空消息之前会派发所有的非延迟消息。

    无论是调用了quit方法还是quitSafely方法只会,Looper就不再接收新的消息。即在调用了Looper的quit或quitSafely方法之后,消息循环就终结了,这时候再通过Handler调用sendMessage或post等方法发送消息时均返回false,表示消息没有成功放入消息队列MessageQueue中,因为消息队列已经退出了。

    需要注意的是Looper的quit方法从API Level 1就存在了,但是Looper的quitSafely方法从API Level 18才添加进来。


    LruCache算法原理

    Least Recently Used 最近最少使用 LRU算法就是当缓存空间满了的时候,将最近最少使用的数据从缓存空间中删除以增加可用的缓存空间来缓存新数据。这个缓存算法内部有一个缓存列表,每当一个缓存数据被访问的时候,这个数据就会被提到列表尾部,每次这样的话,列表头部的数据就是最近最不经常使用的了,当缓存空间不足时,就会删除列表头部的缓存数据。

    LinkedHashMap

    public LruCache(int maxSize) {

            if (maxSize <= 0) {

                throw new IllegalArgumentException("maxSize <= 0");

            }

            this.maxSize = maxSize;

            this.map = new LinkedHashMap<K, V>(0, 0.75f, true);

        }

      · initialCapacity 用于初始化该 LinkedHashMap 的大小。

      ·loadFactor(负载因子)这个LinkedHashMap的父类 HashMap 里的构造参数,涉及到扩容问题,比如 HashMap 的最大容量是100,那么这里设置0.75f的话,到75的时候就会扩容。

      ·accessOrder,这个参数是排序模式,true表示在访问的时候进行排序( LruCache 核心工作原理就在此),false表示在插入的时才排序。

    总结:

      ·LruCache中维护了一个集合LinkedHashMap,该LinkedHashMap是以访问顺序排序的。

      ·当调用put()方法时,就会在结合中添加元素,并调用trimToSize()判断缓存是否已满,如果满了就用LinkedHashMap的迭代器删除队首元素,即近期最少访问的元素。

      ·当调用get()方法访问缓存对象时,就会调用LinkedHashMap的get()方法获得对应集合元素,同时会更新该元素到队尾。


    Sharepreferences apply和commit的区别

    1.apply没有返回值, commit返回boolean

    2.apply是将修改的数据原子提交到内存,而后异步真正提交到硬件磁盘, commit是同步提交到硬件磁盘,因此,在多个并发的提交commit时,她们会等待正在处理的commit保存到磁盘后再操作,降低了效率

    3.apply不会提示任何失败

    apply效率高一些,如果没有必要确认是否提交成功建议用apply


    kotlin给空值赋值,如果一个对象User的name为空 此时把这个name赋值给另一个对象 要怎么写

    用?:的形式赋值空值的默认值 

    val loginBean = LoginBean("", "")

    loginBean.code =mLoginBean?.code ?:"11"

    kotlin两种延迟初始化方式:1.by lazy 2.lateinit var 
    lateinit var只能用来修饰类属性,不能用来修饰局部变量,并且只能用来修饰对象,不能用来修饰基本类型。
    by lazy  可以用于类属性或者局部变量
    val name by lazy{ "张三" } //只能用val修饰, 打印name 为张三

    自定义高阶函数:

    fun cus(clac:(Int,Int)->Int){
        val result = calc(2,3)    
        Log.d("dww",result.toString())
    }
    调用: cus{ a,b->a+b}//5    
                cus{ a,e->a*e}//6


    自定义view

    三个流程

    onMeasure  测量,系统会根据xml布局文件和代码对控件属性的设置,来获取或者计算出每一个view和viewgroup的尺寸,并将这些尺寸保存下来

    measure的过程根据view的类型分两种情况

    1,单一的view:通过measure过程就完成了其测量的过程。
    过程:measure()->onMeasure()->setMeasureDimension()->getDeafaultSize()->完成测量
    2,ViewGroup,除了完成自身的测量过程外,还会遍历调用子元素的measure方法,各个子元素再递归执行该流程。
    过程:View.measure()->viewgroup.onMeasure()->viewgroup.measureChild->vg.getChildMeasureSpec->
    单一view的measure过程->合并得到viewgroup测量值->view.setMeasureDimension()->完成测量

    onLayout   布局,根据测量出的结果以及对应的参数,来确定每一个控件应该显示的位置。

    单一view的onlayout过程:
    layout->setOpticalFrame()->onlayout(空实现)->layout结束

    viewgroup的onlayout过程:
    vg.layout()->调用super.layout->view.layout()->onlayout()->遍历子view->计算当前view的位置->确定子view在自身的位置->child.layout->单一view的layout过程->vg完成layout

    onDraw    绘制,确定好位置后将这些控件绘制到屏幕上。
    单一view:绘制view本身
    过程:draw()->绘制background->绘制自己->绘制子view->绘制装饰(foreground,滚动条等)->结束
    viewgroup:除了绘制自身view外,还需要绘制所有子view
    过程:view.draw()->view.drawbackground()->view.ondraw()->vg.dispathcDraw()->单一view的draw过程->
    view.ondrawForeground()->结束

    onMeasure里面有一个MeasureSpec ,MeasureSpec概括了从父布局传递给子view布局的要求,每一个MeasureSpec代表了宽度或者高度要求,它由size和mode组成。重写该方法时,必须调用setMeasureDimension(w,h)来存储view测量出的宽高,

    widthMeasureSpec:父布局加入的水平空间要求
    heightMeasureSpec:父布局加入的垂直空间要求。

    MeasureSpec三种Mode 

    UNSPECIFIED(未指明的;未详细说明的):    未指定尺寸模式,父布局没有对子view强加任何限制,他可以是任意想要的尺寸,

    EXACTLY(恰好地;精确地):    精确值模式,父布局决定了子view的精确尺寸,子view无论想要设置多大的值都将限定在那个边界内(也就是宽高为具体值,如50dp,或者父布局设置成了match_parent)     

    AT_MOST(至多):    最大值模式,子view可以一直大到指定的值,(相当于父布局宽高wrap_content)

    onLayout

    每一个父布局都会对它的子view进行布局来放置她们,

    。。。没写完

    总结:

    自定义view在onDraw里需要处理padding的影响,widthMeasureSpec和heightMeasureSpec是包含padding大小的

    子view的margin属性是由viewGroup处理的,viewGroup在onMeasure和onLayout时一定要考虑viewGroup自己的padding是子view的margin

    自定义view invalidate和postInvalidate区别
    invalidate和postInvalidate都是进行UI刷新的,invalidate方法运用在UI线程,postInvalidate方法运用在非UI线程,用于将线程切换到UI线程,postInvalidate最后还是调用的invalidate方法。
    postInvalidate方法调用了ViewRootImpl中的dispatchInvalidateDelayed方法向ViewRootImpl中的ViewRootHandler发送了一个消息,最后调用的还是view的invalidate方法。


    kotlin下的单例

    1.饿汉式  object SingletonDemo{}     object进行对象声明与我们的饿汉式的单例代码是相同的

    2.懒汉式只判断一层null 线程不安全的

    class SingleDemo private constructor(){
        companion object{
                private var instance:SingleDemo? = null
                get(){
                    if(field==null){
                    field = SingleDemo()
                    }
            return field
             }
             fun get():SingleDemo{
                return instance!!
            }   
        }
    }

    3.安全的懒汉式  同步锁在方法上的那种

    与2只差一个@Synchronized

    class SingleDemo private constructor(){
            companion object{
            private var instance:SingleDemo? = null
                get(){
                if(field==null){
                    field = SingleDemo()
                    }
                return field
                }
                @Synchronized
                fun get():SingleDemo{
                    return instance!!
                    }   
                }
        }

    4.懒汉式双重检查式 安全

    class SingleDoubleCheck private constructor(){
        companion object{
            val instance : SingleDoubleCheck by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED){
                SingleDoubleCheck()
                    }       
            }
    }

    5.静态内部类

    class StaticClazz private constructor(){
        companion object{
            val instance = ClazzHolder.holder
        }
        private object ClazzHolder{
            val holder = StaticClazz()
        }
    }


    ArrayList扩容原理:

    1,把原来的数组复制到另一个内存空间更大的数组中。
    2,把新元素添加到扩容以后的数组中。
    每次扩容是原来的1.5倍,


    Array和ArrayList的区别
    存储方面:
    数组可以存储基本类型也可以存储对象,只能存同种类型的元素,
    ArrayList只能存储对象类型,可以存object

    空间大小方面:
    数组的大小是固定的,空间不够时也不能再次申请
    ArrayList空间是动态增长的,如果空间不够,会创建一个空间比原来大1.5倍的新数组,然后将所有元素复制到新数组中,接着抛弃旧数组,每次添加新元素的时候会去检查内部数组的空间是否足够。

    方法上:
    ArrayList方法更多样化,addAll(),removeAll(),返回迭代器iterator()等。
    ArrayList初始化容量是10,一次扩容自身大小的1.5倍,内部数组,扩容原理为创建一个新数组并将旧数组copy到新数组并舍弃旧数组。

    1.ArrayList是可以动态增长和缩减的索引序列,基于数组实现的List类。ArrayList是有序集合
    2.ArrayList封装了一个动态再分配的ogject[]数组,每一个类对象都有一个capacity属性,表示他所封装的object[]数组的长度,当向ArrayList中添加元素时,该值会自动增加。
    3.ArrayList线程不安全,多条线程访问同一ArrayList集合时,需手动保证该集合的同步性。


    HashMap默认初始容量16,负载因子0.75,设定threshold,threshold=capacity(16) *loadFactor (0.75) = 12
    当HashMap的size到了threshold时就会进行扩容。
     HashMap要求容量必须是2的幂。 threshold = tableSizeFor(initialCapacity); tableSizeFor()主要功能返回一个比给定整数大且最接近2的幂的整数,如给10则为2的4次方16

    简单来说HashMap由数组+链表组成,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的,如果定位到的数组位置不含链表(当前Entry的next指向null)那么对于查找,添加等操作很快,仅需一次寻址即可,如果定位到的数组包含链表,对于添加操作时间复杂度为O(n),首先遍历链表,存在即覆盖,否则新增。对于查找操作来讲,仍需遍历链表,然后通过key对象的equals方法逐一对比,所以,性能考虑,HashMap中的链表出现越少性能才越好。

    HashMap 1.7和1.8的区别
    是基于哈希表实现Map接口的双列集合,数据结构是链表散列,也就是数据+链表,Key是唯一的,value可以重复,允许存null键null值,元素无序。线程不安全。
    查询慢 增删快
    1.7使用的是头插法,而1.8使用的是尾插法,因为1.7是单链表进行的纵向延伸,当采用头插法时会容易出现逆序且环形链表死循环的问题,但是在1.8后加入了红黑树,使用尾插法,避免了这个问题。
    扩容后数据存储位置的计算方式也不一样,


    activity启动流程:

    1.点击桌面app的图标,Launcher进程采用Binder_IPC向system_server进程发起startActivity请求
    2.system_server接到进程请求后,向zygote进程发起创建进程的请求。
    3.Zygote进程fork出新的子进程,即app进程,
    4.app进程通过Binder_IPC向system_server进程发起attachApplication请求。
    5.system_server进程在收到请求后,进行一系列准备工作,再通过binder ipc向app进程发送scheduleLaunchActivity请求。
    6.app进程的binder线程(ApplicationThread)在收到请求后,通过handler向主线程发送LAUNCHER_ACTIVITY消息
    7主线程收到message后,通过反射机制创建目标activity,并回调activity.oncreate()方法等


    synchronized和volatile的区别

    多线程只有保证可见性和原子性、有序性才能保证安全

    可见性:线程都是基于cpu运算的,cpu有缓冲区,所以每次在计算的时候都会把数据加载缓冲区计算,计算好的数据最终会被写到主存中,但是有时间差,如果没有synchronized或者volatile修饰,那么下一个线程读取的时候可能是修改前的数据。加上synchronized或者volatile,在该线程释放的时候会把数据写回到主存。

    原子性:就好比你在思考一件事情的时候,不愿意被其他事情打扰一样,线程的原子性也是如此,在执行一段逻辑的时候也不希望被其他线程干预,多个线程同时请求同一资源或代码块,这时候对资源或者代码块上加上synchronized,表示此时只能有一个线程能拿到锁,其他线程只能等待他释放才能进行下一轮争夺。

    有序性:又叫做禁止指令重复,处理器为了提高程序运行效率,可能会对输入代码进行优化,所以有可能会调整语句的执行顺序,但是他会保证最终结果的正确性,这种正确性只保证单线程里没有问题、

    synchronized修饰范围:可以变量,可以方法,上述三个特性都有,原子性,可见性有序性。

    volatile是一种轻量级的同步机制,只能修饰变量。特性:只有可见性和有序性,不具有原子性。


    ConcurrentHashMap
    JDK1.7版本中concurrenthashmap采用了数据+Segment(分段锁)的方式实现
    分段锁称为Segment,它是类似HashMap的结构,内部拥有一个Entry数组,数组中的每一个元素又是一个链表,同时又是一个ReentrantLock。
    内部结构:ConcurrentHashMap使用分段锁技术,将数据分成一段一段的存储,然后给每一段数据分配一把锁,当一个线程占用锁访问其中一个数据的时候,其他数据也能被其他线程访问。能够实现真正的并发访问。
    JDK1.8版本concurrenthashmap采用数组+链表+红黑树的实现方式来设计。内部大量采用CAS(compare and swap 比较替换)操作。
    CAS操作包括三个操作数---内存位置V、预期原值A和新值B,如果内存地址里面的值和A的值是一样的,那么就将内存里面的值更新成B,CAS是通过无限循环来获取数据的,如果在第一轮循环中,a线程获取地址里面的值被b线程修改了,那么a线程需要自旋,到下次循环才有可能执行。1.8中彻底放弃了Segment(分段锁)而采用Node。JDK1.8中的concurrenthashmap在链表的节点数量大于8的时候会将链表转换成红黑树进一步提高其查找性能。

    Node:保存Key,value及key的hash值的数据结构。

    查询时间复杂度:从原来的遍历链表O(n),变成遍历红黑树O(logN)。


    LinkedHashMap 继承自HashMap,结构为HashMap+双向链表,构造方法中有一个accessOrder 默认为false(按插入顺序存储),这跟存储顺序有关,存储数据是有序的,分为两种:插入顺序和访问顺序。

    LinkedHashMap构造函数,主要就是调用了HashMap构造函数初始化了一个Entry[]数组,然后调用自身init初始化了一个只有头结点的双向链表。

    线程不安全的。


    onNewIntent方法回调时机

    在ActA的启动模式设置为singleTask时,ActA->ActB->ActC->ActA 则会调用ActA的onNewIntent方法。

    activity第一次启动的时候不会回调onNewIntent方法,如果以后再想启动这个activity的时候那么会onNewIntent-》onrestart-》onstart-》onresume,如果由于系统内存不足已经把activity释放掉了,那么再次调用的时候会重新启动activity即执行onCreate onstart onresume

    当调用到onNewIntent的时候,需要在onNewIntent中使用setIntent赋值给activity的intent,否则后续getIntent得到的数据都是老的intent数据。

    Activity四种启动模式

    1.standard 默认启动模式,每次启动,无论栈中是否有这个activity的实例,系统都会创建一个新的activity实例。

    2.singleTop 当一个singleTop模式的activity已经位于任务栈的栈顶,再去启动它时,不会再创建新的实例,如果不位于栈顶,就会创建新的实例。

    3.singleTask,SingleTask模式的activity在同一个Task内只有一个实例,如果Activity已经位于栈顶,系统不会再创建新的activity实例,但是如果activity已经存在但是不位于栈顶,系统就会把该activity移到栈顶,并把它上面的activity出栈。

    4.singleInstance  该模式也是单例的,但是和singleTask不同,singleTask只是任务栈内单例,系统里是可以有多个singleTask实例的,而singleinstance的activity在整个系统里只有一个实例,启动一个singleinstance的activity,系统会创建一个新的任务栈,并且这个任务栈里只有他一个activity

    如果把一个activity设置为singleinstance模式,启动会慢一些,切换效果不好,影响用户体验,它往往位于多个应用之间。


    横竖屏切换activity生命周期。

    oncreate->onstart->onresume->onpause->onSaveinstancestate->onstop->ondestroy->oncreate->onstart->onrestoreinstancestate->onresume

    pause后保存(onSaveinstancestate),start后恢复(onrestoreinstancestate)。


    AIDL

    IPC:Inter Process Communication 也就是进程通信或者跨进程通信的意思,进程一般指一个执行单元,它拥有独立的地址空间,也就是一个应用或者一个程序,线程是CPU调度的最小单元,是进程中一个执行部分或者说是执行体,两者之间是包含与被包含的关系,因为进程间资源不能共享,所以每个系统都有自己的IPC机制,Android自己的IPC机制就是Binder。AIDL其实就是通过Binder机制来实现的。


    地图的json数据在cpu上 要传给gpu再通过opengl渲染 我们只是告诉opengl要用什么顺序进行渲染 opengl读取gpu上的数据通过我们创建的buffer 这个数据怎么传到gpu 通过program program包装好gles的规则传给opengl

    贴图

    一张图片有四个顶点 一般是在第一象限 所以把他想象是在第一象限 按照00 01 11 10的顺序 这四个左边称为纹理坐标 然后把这个纹理坐标放在gl上面一一对应起来 一般来说只需要贴一个背景 四个纹理坐标对应4个gl坐标 如果是贴坐标比较多的大楼 需要手动拆分每4个一组

    我们所做的事是把数据按什么样的顺序传给opengl


    内存优化 LeakCanary框架原理

    原理:监控应用中所有activity生命周期方法中的onDestroy方法,在onDestroy中把activity转成弱引用保存到引用队列,然后框架会不断遍历分析引用队列,检测到内存地址是否可达,并输出内存泄漏的最短路径。

    给每个观察的引用生成一个随机key保存到集合中,并把引用转换成弱引用保存到引用队列,将能回收的引用从队列中删除,没有被回收的会生成一个heap文件,把heap文件读取到内存缓存HprofBuffer中,然后通过解析HprofParser产生Snapshot快照,。。。https://www.jianshu.com/p/4deb3b6e9515


    Activity 5秒   Service20秒  BroadcastReceiver 10秒 超过主线程就会ANR

    BroadcastReceiver Service在主线程 不能进行耗时操作,可以通过IntentService进行


    view和surfaceview的区别
    view在主线程更新UI,surfaceview可以在子线程更新UI
    为什么surfaceview可以在子线程更新UI
    surfaceView和view都是通过lockCanvas和unlockCanvasAndPost方法进行绘画的。View的刷新有一个checkThread(在ViewRootImpl.java中)的判断,如果不是在UI线程中会抛出异常。而surfaceView中不会进行判断,这样他就可以在其他线程更新UI
    View的绘图效率不高,主要用于动画变化较少的程序,SurfaceView 绘图效率较高,用于界面更新频繁的程序
    SurfaceView使用双缓冲机制,播放视频时画面更流畅。

    什么是双缓冲机制?
    在运用时可以理解为:SurfaceView在更新视图时用到了两张 Canvas,一张 frontCanvas 和一张 backCanvas ,每次实际显示的是 frontCanvas ,backCanvas 存储的是上一次更改前的视图。当你在播放这一帧的时候,它已经提前帮你加载好后面一帧了,所以播放起视频很流畅。
    当使用lockCanvas()获取画布时,得到的实际上是backCanvas 而不是正在显示的 frontCanvas ,之后你在获取到的 backCanvas 上绘制新视图,再 unlockCanvasAndPost(canvas)此视图,那么上传的这张 canvas 将替换原来的 frontCanvas 作为新的frontCanvas ,原来的 frontCanvas 将切换到后台作为 backCanvas 。例如,如果你已经先后两次绘制了视图A和B,那么你再调用 lockCanvas()获取视图,获得的将是A而不是正在显示的B,之后你将重绘的 A 视图上传,那么 A 将取代 B 作为新的 frontCanvas 显示在SurfaceView 上,原来的B则转换为backCanvas。

    相关文章

      网友评论

          本文标题:一大波面试题

          本文链接:https://www.haomeiwen.com/subject/wdzczctx.html