美文网首页Andoid 八股文面试
程序员自我修养之Android篇

程序员自我修养之Android篇

作者: 巴菲伟 | 来源:发表于2020-12-25 20:09 被阅读0次

    Activity和Fragment的区别

    一、Activity的启动流程

    启动流程:L:Lock(L代表是锁)   I:Install(代表是安装)

        1.Activity.java  :  startActivity()->startActivityForResult()

         2.Instrumentation.java  :  startActivity()

         3.ActivityManagerProxy.java :  startActivity()

         4.通过IBinder传递到APM.java :  startActivity()->startActivityAsUser()

         5.ActivityStarter.java : startActivityMayWait()->startActivityLocked()

         6.APM.java :  startProcessLock  :  判断是否已经开启该进程,如果开启就不再创建

         7.Process.java :  Start("android.app.ActivityThread") : 这里就是开启一个ActvityThread入口

          8.Process.java :  startZygote() :  真正的孵化出一个进程

          9.ActivityThread.java : main() -> attach()----->通过调用IActivityManager来和AMS进行通行,完整Activity的生命周期

          10.主线程中调用Loop,那么为什么没有造成阻塞呢

              ActivityThread的Main方法里面有个Loop的消息队列,ActivityThread 有个 getHandler 方法,得到这个 handler 就可以发送消息,然后 loop 里就分发消息,然后就发给 handler,然后就执行到 H(Handler )里的对应代码。所以说ActivityThread的main方法主要就是做Activity相关动作的消息循环一旦退出消息循环,那么你的程序也就可以退出了,所以这些代码就不会卡死。

        1.ActivityThread :  该类是应用程序对应的进程的主线程类,即UI线程

     2.ActiityRecord :  activity的记录对象

     3.ActivityStack 、ActivityStackSupervisor : 管理activity的启动,生命周期以及activity的堆栈

      4.TaskRecord  :  应用activity记录任务栈

         5.ProcessRecord  :  该类用于记录每个进程的全部信息。主要信息包括该进程包含的activity、provider、service等信息、进程文件信息、进程状态信息

    二、本地广播和全局广播的区别

        1、接受情况:全局广播任何程序都可以接收到,本地广播只能本应用可以接收到

        2、安全性:全局广播不安全,本地广播安全

        3、注册情况:全局广播可以静态和动态注册,本地广播只能动态注册

    三、BindSerce和Service的区别

        1、生命周期不同

        2、使用startService()方法启用服务,调用者与服务之间没有关连,属于静态绑定,服务会一直存在。使用bindService()方法启用服务,调用者与服务绑定在了一起,属于动态绑定调用者一旦退出,服务也就终止。

    四、view显示的过程

         Window及其实现类是PhoneWindow, 页面都是依附在窗口之上的,DecorView即是窗口最顶层的视图

    1.MainActivity.java :  setContentView

    2.AppCompatDelegateImpl.java:  setContentView,这里面会有LayoutInfalter去解析xml文件成view,然后会调用mWindow.setContentView方法

     3.PhoneWindow.java :  setContentView里面去创建DecorView,然后解析view添加到DecorView当中。WindowManager继承ViewManager并实现里面的addView、deleteView、updateView三个方法

    4.ActivityThread.java : handleResumeActivity 方法里面,有个WindowManager.addView(decorView) , 这个时候就开始view真正的绘制流程,WindowManager是继承ViewManager的类,ViewManager里面有对view的增删更新三个方法

    5.WindowManagerImpl.java :  WindowManagerImpl是继承WindowManger的类,去调用addView的方法

    6.WindowManagerGlobal.java :  去调用addView的方法,这里面会创建ViewRootImpl的对象,然后去root.setView你要显示的view,在这里之前会有个子线程是否可以更新UI问题,调用完setView这个方法就开始执行view的绘制、测量等操作,在这里之前会有一个是否为自线程更新UI的判断

    7.屏幕刷新机制:

        在viewRootImpl哪里通过单例模式创建Choreographer(拷牙骨缝儿)的对象,Choreographer作用是可以接受系统的 VSYNC 信号,以及统一管理应用的输入、动画和绘制等任务的执行时机,16.6ms是在Choreographer的构造方法里面创建的。

    requestLayout() ——>  scheduleTraversals() ———>  Choreographer会执行posSyncBarrier(),通过同步屏障消息机制,把消息插入到handler最前面,通过执行postCalllBack等待vsync的信号到来

    8.vsync的监听以及处理:

        在FrameDisplayEventReceiver里面的onReceiver,通过JNI的方式去注册Vsync的监听者,通过有onVsync去接受并对Vsync信号的处理, doFrame执行每一帧情况,超过16.6ms就用上一帧,没有超过就及时刷新,然后去执行callBack的函数,然后执行performTraversals——>再去执行viewRootImpl的绘制流程

    两篇屏幕刷新机制:

    https://cloud.tencent.com/developer/article/1685247

    https://www.jianshu.com/p/86d00bbdaf60

    如何优先去执行view的测绘呢? 有同步屏障消息和异步消息去执行

    ViewRootImpl的作用

        1.它是关联Window和View相关联的桥梁

        2.它执行view 的onMeasure、onLayout、onDraw三大流程

        3.它可以接受屏幕输入事件和分发手势

        在子线程view.requestLayout和view.postInvalidate更新Ui就不会生效了,因为ViewRootImpl的setView方法时候会有线程的检查

    onMeasure里面的参数使用:

    1、MeasureSpec.UNSPECIFIED ->未指定尺寸

    2、MeasureSpec.EXACTLA ->精确尺寸,控件的宽高指定大小或者为FILL_PARENT

    3、MeasureSpec.AT_MOST ->最大尺寸,控件的宽高为WRAP_CONTENT,控件大小一般随着控件的子空间或内容进行变化,此时控件尺寸只要不超过父控件允许的最大尺寸

    五、自定义view的流程    

        1.LayoutParams是什么,与MeasureSpec有什么关系 

            LayoutParams代表布局中的宽和高

            MeasureSpec里面的三个参数

            EXACTLY :   确切的控件大小

            AT_MOST :  不会超过某个数值,比如父布局大小

            UNSPECIFIED :  控件大小不确定

        2.如何把LayoutParams转换为MeasureSpec的具体参数值

          getChildMeasureSpec() : 这个需要自己看看里面算法

        面试考点:getMeasuredWidth和getWidth的区别

    六、view的事件分发流程

           rootViewImpl->DecorView->Activity->Window->DecorView->ViewGroup

          dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent

    View事件的总结: 

    dispatchTouchEvent的总结: 

    ViewGrop:  

    在down改为true :  ViewGrop   down、move、up三个事件都可以收到,从Activity传递给ViewGrop的,view什么事件都不能接收到  

     在move改为true:   ViewGrop可以接收到down事件,view也可以接收到down事件,其余什么事件都接受不到,由Activity dispatchTouchEvent和onTouchEvent自己处理   

    在up改为true :ViewGroup可以接收到down事件,view也可以接收到down事件,其余什么都接受不到,由Activity dispatchTouchEvent和onTouchEvent自己处理 

     onInterceptTouchEvent的总结: 

    ViewGroup: 只能接受down的事件,其余什么都收不到

    在down改为true :  ViewGrop可以接收到down事件,view什么事件都接受不到,由Activity dispatchTouchEvent和onTouchEvent自己处理     

    在up改为true :  ViewGrop可以接收到down事件,view也可以接收到down事件,由Activity dispatchTouchEvent和onTouchEvent自己处理     

    在move改为true :  ViewGrop可以接收到down事件,view也可以接收到down事件,由Activity dispatchTouchEvent和onTouchEvent自己处理 

     onTouchEvent的总结: 

    ViewGrop:  

    在down改为true :  ViewGrop   down、move、up三个事件都可以收到,从Activity传递给ViewGrop的,view可以收到down事件

    在up改为true :  ViewGrop可以接收到down事件,view也可以接收到down事件,由Activity dispatchTouchEvent和onTouchEvent自己处理   

    在move改为true :  ViewGrop可以接收到down事件,view也可以接收到down事件,由Activity dispatchTouchEvent和onTouchEvent自己处理 

     dispatchTouchEvent的总结: 

    View:          

    在down改为true :  ViewGrop   down、move、up三个事件都可以收到,view这个三个事件也都可以收到   

    在move改为true: ViewGrop可以接收到down事件,view也可以接收到down事件,其余什么事件都接受不到,由Activity dispatchTouchEvent和onTouchEvent自己处理   

    在up改为true: ViewGroup可以接收到down事件,view也可以接收到down事件,其余什么都接受不到,由Activity dispatchTouchEvent和onTouchEvent自己处理 

     onTouchEvent的总结: 

    View:          

    在down改为true :  ViewGrop   down、move、up三个事件都可以收到,view这个三个事件也都可以收到   

    在up改为true :  ViewGrop可以接收到down事件,view也可以接收到down事件,由Activity dispatchTouchEvent和onTouchEvent自己处理   

    在move改为true :  ViewGrop可以接收到down事件,view也可以接收到down事件,由Activity dispatchTouchEvent和onTouchEvent自己处理 

    滑动冲突解决的办法

        1.外部拦截法:在父布局这里加onInterceptTouchEvent方法处理

        2.内部拦截法:在子布局的dispatchTouchEvent里面的调用getParent().requestDisallowInterceptTouchEvent(true)        

    七、多进程通信的方式

                1.intent/bundle

                2.ContentProvider

                3. 广播

                4.AIDL

                5.FileObserver :  使用startWatching和stopWatching两个方法进行加锁,通过监听它的onEvent事件来做处理,原理是底层inotify原理机制,采用是对文件变化做监听

                6.IPC通信

                7.socket : 两次拷贝、C/S架构(复用性差)、不安全

                8.binder :  一次拷贝、C/S架构(复用性强)、安全

                9.管道  :  两次拷贝、C/S架构(复用性差)、不安全

                10. 共享内存 :  两次拷贝、复用性差、不安全

    八、Binder的通信使用和原理

    1. aidl如何生成java的文件,客户端和服务端建立联系

         主要是三个文件:Stub(服务端接受数据)、Proxy(封装客户端处理数据是调用stub.asInterface())、继承IInterface接口

       sub的实现在继承service的onBind里面去实现的


    2. 客户端于服务数据连通  

     service端的使用: 继承service然后再onBind里面把stub给new出去

            client端的使用:通过bindService去serviceConnection里面去调用Stub.asInterface方法来获得远端Service的对象

        总结: sub     -----> 是给服务端创建的

                    proxy  -----> 是给客户端创建的

                    IIterface ------>  公共的抽象接口,给sub和procy来使用                

       3.通信实现过程:

            1.首先会用Pacel这个数据结构定义data、reply两个对象,data是用来存客户端准备发给服务端的数据,reply是服务端返回给客户端的数据,用writeToParcel方法,把你们数据转换为Parcel数据

            2.IBinder.transact(变量定义int a= 1,。。。。。)去发送,这个时候客户端等待服务端给返回数据

            3.Stub 的onTrancation()方法,通过定义的a来确定去执行

            4.Binder对象的获取 : Stub.asInterface去拿到Proxy的对象

    https://www.bilibili.com/video/BV13A411J7i4?p=7 讲解的地址

       4.Binder的原理

        它是基于C/S架构实现,binder的驱动、Client端、Service 端、ServiceManager构成的

        ServiceManager里面常见的四种方法:

        addService、getService、listService、checkService

        addService案例举例:

        1. 先获得ServiceManager的对象

            sp < IServiceManager > sm = defaultServiceManager();

        2.调用ServiceManager的addService方法

        3.IPCThreadState.cpp(又可以理解为ProcessState)  :  

            transact() ---> 通过Parcel传递数据,同时传递ADD_SERVICE_TRANSACTION这个参数

            writeTransactionData() ---> 把Parcel数据转为binder_transaction_data

            waitForResponse() ---->传递一个BC_transaction变量,这个时候就等待驱动层返回给数据,再返回给上层

        4.binder.c 

           binder_open():在驱动底层开辟空间,主要是为用户态传递数据,这里也是数据拷贝一次

           binder_ioctl() ---> binder_transaction() 

           把binder_transation_data数据转化为binder_transaction,这里收到传入BC_transaction参数传递到下面,驱动层等待创建好被唤起

           binder_thread_write()

           binder_transaction的数据转换为binder_write_read数据

           把binder_write_read存在binder_transaction_data结构中,它里面有个binder_node节点数据结构-->binder_proc(它是存进程的)-->binder_ref(它是存线程的),里面有个红黑树,专门存放线程id和名字作为一一对应的,这样就能从节点--->进程---->再找到对应的线程,数据存储好之后会调IPCThreadState.cpp的waitForResponse方法,并且返回给BR_TRANSACTION_COMPLETE参数,告诉它存储完毕

        IPCThreadState.cpp  ------>BpBinder端,Binder.cpp  -------->BnBinder端,他们两个都是基于IBinder去写的

    九、进程保活的实现

            1.Android的进程优先级:前台进程、可见进程、服务进程、后台进程、空进程(主要为了做缓存、缩短下次启动时间)

            2.进程保活方案

         a.利用系统广播拉活

      缺点:当只有在特定场景下才可以拉起保活,下次被杀死后,无法得到控制

         b.利用系统Service机制拉活 :  onStartCommand方法,返回START_STICKY

         缺点:在短时间内被多次杀死就不会被拉活、被root或者其他工具被stop也是无法拉活的

      c.通过native进程拉活(通过AMS的进程杀死)

      d.进程相互唤醒(打开一个app同时唤醒另一个app)

           e.提升Service进程优先级,比如改为前台进程 startForeground(1,notification)

      f.JobScheduler用来检测你自己的服务是否被杀掉,如果被杀掉了,你重启自己的服务               缺点:需要有自启动权限

            g.采用SyncAdapters机制,等待系统去更新同步信息,就是在账号和同步添加账户

    十、Handler,Looper,MessageQueue的工作原理

            主线程的Looper对象的生命周期 = 该应用程序的生命周期,它是导致于内存泄漏的原因

           Message消息的种类:

            同步(Barrier)屏障消息、异步消息(在handler初始化可以设置)、普通消息

            同步屏障消息作用:一般该消息都为null,只是起到flag的作用。目的是忽略同步消息让异步消息先执行,一般同步屏障消息是和异步消息一起使用,只是给系统级别使用,当发送异步消息处理完之后就把同步消息给移除

           存消息是按照发送时间去存储的,取出消息的顺序是:是否为同步屏障消息作为判断,遍历单向链表,优先取出异步消息,如果没有,再按照时间顺序去取出普通消息

    Handler常见几个面试题:

     1.Looper.loop里死循环为什么没有卡死

       主线程会进入循环状态,它循环的是线程的生命周期,它结束了这个功能也就结束了,等待其他消息再进来,没有消息时候它会进入休眠,会释放cpu并不会占用很大的消耗

     1.1. handler是如何阻塞的?

        MessageQueue.next()里面的nativePollOnce这个方法里面,里面有个nextPollTimeoutMillis整形变量,如果它!= 0就会进入阻塞状态,如果为-1,它就会进入休眠状态,并且释放cpu。

    2.post和sendMessage的区别

       post发送是Runnable对象,然后封装成Message对象,主要区别是dispatchMessage这个函数时候,handleCallBack会比handleMessage的对象的优先级高,也就是post比sendMessage的使用优先级高

    3.message.obtain和new Message的区别             

       Message.obtain()是一种享元设计模式,采用单向链表数据结构存储消息,Message内部保存了一个缓存的消息池,我们可以用obtain从缓存池获得一个消息,Message使用完后系统会调用recycle回收,如果自己new很多Message,每次使用完后系统放入缓存池,会占用很多内存,造成内存浪费

        4.handler发送延迟原因:

            通过updateTime方法,获取当前时间戳,时间戳顺序来存储到MessageQueue里面

      5.SyncBarrier是什么以及作用:

            它是同步屏障消息,作用是跳过所有的同步消息,然后尽快处理异步消息,提高优先级

    6.如何让子线程弹出Toast的方法:

       需要调用looper.prepare以及Looper.loop函数,需要最后手动调用Looper.quitSafely方法才能退出,否则线程不会结束

    7. 发送一个异步消息

        Message msg = handler.obtainMessage();

        msg.setAsynchronous(true) //设置异步消息

        msg.sendMessage() ......

    十一、HandlerThread、ThreadLocal的工作原理    

    ThreadLocal的工作原理

        ThreadLocal的作用是:简化了参数的传递和数据的获取,线程是不安全的,它是对ThreadLocalMap数据模型进行封装

         数据模型

    四种引用:强引用、弱引用、软应用、虚引用

     弱引用回收的机制:当强引用删除时候,GC才对弱引用进行回收,不在GC内存不足才对它回收

    key是弱引用,value是强引用

    十二、SurfaceView和TextureView的区别

        https://www.zhihu.com/question/30922650  :有篇文章进行讲解     

          1.SurfaceView的理解:   

            普通的View共享一个Window,它根据DecorView找到对应的Windows,Window找到对应的WindowState,WindowState又对应一个Surface,所以所有的view的DecorView会共享一个Surface。SurfaceView就自己会单独有一个属于自己的Surface,然后又可以拿到Canvas对象,这样就可以绘制画面,这也就是为什么它可以在子线程自己单独绘制原因   

       2.SurfaceView双缓冲机制

          双缓冲:在运用时可以理解为:SurfaceView在更新视图时用到了两张Canvas,一张frontCanvas和一张backCanvas,每次实际显示的是frontCanvas,backCanvas存储的是即将显示的视图。前端缓冲区是正在渲染的图形缓冲区,而后端缓冲区是接下来要渲染的图形缓冲区。把要画的东西先画到一个内存区域里,然后整体的一次性画出来,好处是反复局部刷屏带来的闪烁

        3.TextureView的理解:

           它可以将内容流直接投影到View,和SurfaceView不同,它不会在WMS中单独创建窗口,而是作为View hierachy中的一个普通View,因此可以和其它普通View一样进行移动,旋转,缩放,动画等变化。值得注意的是TextureView必须在硬件加速的窗口中,它显示的内容流数据可以来自App进程或是远端进程

        4.SurfaceView、TextureView的区别

      SurfaceView的优缺点:

         优点:可以在一个独立的线程中进行绘制,不会影响主线程,使用双缓冲机制,播放视频时画面更流畅

      缺点:Surface不在View hierachy中,它的显示也不受View的属性控制,所以不能进行平移,缩放等变换,也不能放在其它ViewGroup中。SurfaceView 不能嵌套使用

      TextureView的优缺点

           优点:支持移动、旋转、缩放等动画,支持截图

           缺点:必须在硬件加速的窗口中使用,占用内存比SurfaceView高,在5.0以前在主线程渲染,5.0以后有单独的渲染线程。

    十三、onNewIntent 的使用以及注意事项

        解释 : 如果一个Activity已经启动过,并且存在当前应用的Activity任务栈中,为了防止它再次启动创建新的实例,就会走onNewInetnt这个方法

    1.当ActivityA的LaunchMode为SingleTop时:

        如果ActivityA在栈顶,且现在要再启动ActivityA,这时会调用onNewIntent()方法 ,生命周期顺序为:onCreate--->onStart--->onResume---onPause>onNewIntent--->onResume

    2.当ActivityA的LaunchMode为SingleInstance,SingleTask:如果ActivityA已经在堆栈中,那么此时会调用onNewIntent()方法,生命周期调用顺序为:

        onCreate--->onStart--->onResume---按下Home键>onPause--->onstop--->onNewIntent--->onRestart--->onstart--->onResume

    3.这里面有个注意事项

    @Override

    protected void onNewIntent(Intent intent) {

    super.onNewIntent(intent);

    setIntent(intent);//设置新的intent

    int data = getIntent().getIntExtra("tanksu", 0);//此时的到的数据就是正确的了

    }

        在这里,如果你没有调用setIntent()方法的话,则getIntent()获取的intent都只会是最初那个intent,这里很重要

    十四、Android动画几种方式

    帧动画、补间动画、属性动画

         帧动画可以通过顺序播放资源来实现动画的,补间动画可以实现控件的渐入渐出、移动、旋转和缩放效果,以上两种只是针对于view上,且不能实现复杂动画,而属性动画既可以在作用在view上也可以作用在对象上,还可以制作很复杂的动画显示效果

    十五、application和activity的context区别

            Application的context的生命周期是整个应用结束后

            activity的context的生命周期是当前页面的生命周期       

    十六、Intent 为什么无法传递大数据

            因为它底层是是由Binder实现,Binder底层开辟内存大部分是1024*1024

            Intent和Bundle区别

            Bundle可对对象进行操作,而Intent是不可以,Bundle相对于Intent拥有更多的接口,用起来比较灵活,但是使用Bundle也还是需要借助Intent才可以完成数据传递总之,Bundle旨在存储数据,而Intent旨在传值。

    十七、Android的类加载机制

            1.什么是双亲委派机制

             首先判断parent的loadClass是否加载过,如果没有,一直找到最顶级parent去处理;如果到最顶级的parent都没有找到,则去交换给子的class去调用findClass去处理

             它的作用 : 防止.class文件被重复加载、保证一个类在JVM虚拟机中具有唯一性、保证系统的.class文件不能被修改

       2.Android主要几个加载器

            PathClassLoader、DexClassLoader、BaseClassLoader、BootClassLoader            

      3.PathClassLoader和DexClassLoader的区别

           PathClassLoader : 负责app的应用程序的class的加载和资源文件的加载

           DexClassLoader : 

          他们在8.0以后没有区别,8.0以前的区别是:DexClassLoader不为空,PathClassLoader为空,optimizedDirectory作用是存放优化后的dex,DexClassLoader存放优化后的dex位置可以自己定义,而PathClassLoader为空,只能默认存储系统的位置

            冷启动优化的点:BaseClassLoader里面有个Element的数组,里面就是所有的.dex的文件,它需要通过dexElement.findClass遍历整个数组,找到对应的.class才能启动,把我们要先启动的.class放在最前面,这样就可以加快启动速度。

      4.Class文件不同的加载方式

            为什么静态变量不能加载非静态变量呢?

            类加载到内存步骤:加载、链接、初始化

            加载:查找并加载类的二进制数据

            链接:验证(是否为java文件)、准备(包含初始化静态变量和函数-->static)、解析

            初始化:给静态变量赋值

            5.Class.forName和ClassLoader.loaderClass的区别

              class.forName会初始化静态变量和初始化过程,ClassLoader.loaderClass不会有,只是把.class文件加载到jvm内存中

    十八、ActivityLifecycleCallBack去监听生命周期函数的回调,有时间去学习

    DecorView->Activity->Window->DecorView->ViewGroup

    主线程的Looper对象的生命周期 = 该应用程序的生命周期

    十九、System.currentTimeMillis和SystemClock.elapsedRealtime区别

            System.currentTimeMillis : 获取当前系统时间戳,可以修改

            SystemClock.elapsedRealtime : 从设备boot后经历的时间值,不可以修改

    相关文章

      网友评论

        本文标题:程序员自我修养之Android篇

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