Android面试

android面试

1,简述事件分发机制

2,Activity的生命周期

3,Fragment生命周期

5,内存优化

6,适配问题:文字适配;软键盘适配;机型适配 ,宽高适配(todo)

7,自定义view

8,  handler

9,网络请求 okhttp(同步/异步;原理;拦截器;设计模式) retrofit 原理

10,架构 rxjava mvp mvvm  ARooter原理

11 ,动画

12,性能优化,内存泄漏,内存优化

13,常见设计模式

14,view的绘制流程

15 sparseArray

16 静态注册广播和动态注册广播

17,Android中的类加载器

18,跨进程通信

19, binder机制

20,数据库升级

21,onActivityResult好用吗,能不能用接口回调的方式代替

22,https

23,启动模式

24,webview与原生交互,防注入等

25,几种网络框架的对比

26,几种图片加载库的对比

27,几种架构设计的对比,

28,项目中有什么难点,如何解决的

29,  android10的适配

30、window、activity、DecorView之间的关系

31、window的创建过程

32, surfaceview

33, looper.looper()死循环为什么不阻塞主线程;looper.loop()会不会消耗cpu资源

34,listview和recylerview区别

35,http跟tcp区别,还有socket

36,项目相关 1gradle 2,安全键盘 3,jni 4,jenkens打包 5,volley

37,热修复

38,子线程为什么不跟新ui

39,binder

 

1,简述事件分发机制

        先讲点击事件是由上往下传递规则,当点击事件产生后由activity处理,传递给phonewindow,再传递给DecorView,最后传递给顶层的ViewGroup。一般在事件传递中,我们只考虑ViewGroup的onInterceptTouchEvent方法,因为一般情况下我们不会重写dispatchTouchEvent()方法。对于根view,点击事件首先传递给它的dispatchTouchEvent()方法,如果该ViewGroup的onInterceptTouchEvent()返回true,表示它要拦截这个事件,这个事件就交给onTouchEvent()处理,如果返回false,表示不拦截,事件交个子元素的dispatchTouchEvent()来处理,如此反复。如果传递给底层的view,view没有子view,就会调用View的dispatchTouchEvent()方法,一般最终会调用View的onTouchEvent()方法。

         点击事件由下往上传递。当点击事件传给底层view时,如果其onTouchEvent()方法返回true,事件由底层view消耗处理。如果返回false表示不处理,传递给父View的onTouchEvent()处理,如果父View的onTouchEvent()仍旧返回false,则继续传递给该父View的父view处理。         

         简述  activity->viewgroup->view   消费就是view->viewgroup->activity

         onTouch() 方法优先级高于 onTouchEvent(event) 

         onTouch() 执行总优先于 onClick()

 

 

         1.2 滑动冲突

        滑动冲突分为同方向滑动冲突,例如ScrollView和ListView,同方向滑动冲突,可以计算ListView高度而动态设置ListView的高度,ScrollView高度可变。例如ViewPager和ListView,不同方向滑动冲突,一个是横向滑动一个是竖直滑动,不同方向滑动可以判断滑动的x,y轴是横向还是竖直滑动,如果判断得到是横向滑动,就拦截ListView的事件,竖则反之。

2,Activity的生命周期

     2.1 activity A启动activity B

          A  onPause ->B onCreate onStart onResume ->A onStop

      2.2 接上3.1 关闭activityB

          B onpause  ->A onRestart() onStart(),onResume() ->B onStop() onDestroy()

      onPause方法使用

     关闭动画,或其它耗费cpu的操作

3,Fragment生命周期

      onAttach -> onCreate -> onCreateView -> onActivityCreated ->onStart -> onResume -> onPause -> onStop ->onSaveInstanceState -> onDestroyView -> onDestroy -> onDetach

       3.1  Fragment注意方面

       执行onActivityCreated方法时,ActivityonCreate已经返回,可以开始获取Activity中的控件。

       getActivity() 引用问题,getActivity()有可能为null,也有可能引起内存泄漏。如果需要上下文,可以使用onAttach里面的context

        Activity 在一些特殊状况下会发生 destroy 并重新 create 的情形,比如屏幕旋转、内存吃紧时。这时会自动执行Fragment的无参构造函数,会导致一些参数被再次初始化,从而影响相应逻辑。

         3.2 add和replace区别

           add覆盖老的,repace移除老的

        3.3  懒加载

              可见的fragment进行数据加载,不可见的延迟加载。

               主要方法 setuserVisibleHint。但这个方法的调用条件:只有当fragment存在FragmentPagerAdapter中时才会触发。

       3.4 FragmentPagerAdapter 与 与 FragmentStatePagerAdapter 的区别与使用场景?

FragmentPagerAdapter 的每个 Fragment 会持久的保存在 FragmentManager 中,只要用户可以返回到页面中,它都不会被销毁。因此适用于那些数据 相对静态的页,Fragment 数量也比较少的那种;FragmentStatePagerAdapter 只保留当前页面,当页面不可见时,该 Fragment 就会被消除,释放其资源。因此适用于那些 数据动态性较大、 占用内存较多,多 Fragment 的情况

 

5,内存

    Bitmap 大小的计算公式是:长所占像素 * 宽所占像素 * 每个像素所占内存。想避免 OOM 有两种方法:等比例缩小长宽、减少每个像素所占的内存。

             等比例缩小长高:BitmapFactory里面的Options内部类的inSampleSize属性

             减少像素所占内存:Options 中有一个属性 inPreferredConfig,默认是 ARGB_8888,代表每个像素所占尺寸。我们可以通过将之修改为 RGB_565 或者 ARGB_4444 来减少一半内存

      图片压缩工具压缩处理,使用tinypng

      大图加载使用局部加载:屏幕都显示不小的图片,BitmapRegionDecoder局部加载

  5.2 内存回收机制

       内存回收机制:对象创建在eden区,当执行GC后,如果对象还存活,将转移到survivor apace,当此处操作达到一定的次数,转移到old generation,old generation达到一定程度后,移动到permanent  generation

   5.3可回收对象的判定

        不可回收:静态变量,线程栈变量,常量池,JNI指针

    5.4,内存优化

       解决三个问题,

       内存抖动: 内存波动图形成锯齿状

       内存泄漏:当应用周期内不再使用的对象被GCRoot使用

       内存溢出 :java虚拟机对单个应用的最大内存分配就确定下来了,超出这个值就会OOM

      

 

6,适配问题

     文字适配,https://www.jianshu.com/p/33d499170e25

     软键盘适配,https://www.jianshu.com/p/33d499170e25

     机型适配      https://www.jianshu.com/p/2b6ac837a173

      推送设配 :判手机系统,小米使用小米推送,华为使用华为推送,其他手机使用友盟推送

    宽高适配

     目前市面上的屏幕适配方案只能以宽或高一个维度去适配,另一个方向用滑动或权重的方式去适配的原因

     1,屏幕分辨率限定符

     2,smallestWidth 限定符

     3, 今日头条的方案:动态的修改设备的density值,达到不同分辨率设备的适配。先求density,再求px,可以实现等比例缩放。

            补充:为什么dp还会存在适配问题,因为android手机碎片化,密度类型相同的手机,屏幕密度会有差别。打个比方:同样是1080的宽,正常设备屏幕密度按480算,手机的宽就是360dp刚好是整个屏幕大小。但是如果设备屏幕密度不是480,那屏幕宽计算就是1080/(density/160)就不是360dp了,density小于480,屏幕宽就会大于360dp,大于480dp,屏幕宽就会小于480dp。

     4,点9图

     5,自适应尺寸,matchparent,wrapcontent,权重

     6,布局选择  约束布局constraintlayout

 

density的意思就是 1 dp占当前设备多少像素

        https://juejin.im/post/5b7a29736fb9a019d53e7ee2

7,自定义view

       定义View我们大部分时候只需重写两个函数:onMeasure()、onDraw()。onMeasure负责对当前View的尺寸进行测量,onDraw负责把当前这个View绘制出来。还得写至少写2个构造函数

       三种测量模式

         UNSPECIFIED 父容器没有对当前view有任何限制,当前view可以去任意尺寸。未指定模式,这种情况不多。

         EXACTLY  当前尺寸是当前view一个个取的尺寸  。  精确模式  matchprarent,具体宽高

         AT_MOST 当前尺寸是当期view能取的最大尺寸 。最大模式  wrapcontent

         自定义布局属性:

                   在res/values/styles.xml文件(如果没有请自己新建)里面声明一个我们自定义的属性:

                   布局文件用上我们的自定义的属性啦

                   自定义的View里面把我们自定义的属性的值取出来,在构造函数中,通过AttributeSet把布局里面的属性取出来

 

 

        

      自定义viewGroup  (重写onLayout,确定子控件的位置)大小是遍历计算,位置是逐个摆放。

         1.首先,我们得知道各个子View的大小吧,只有先知道子View的大小,我们才知道当前的ViewGroup该设置为多大去容纳它们。

         2.根据子View的大小,以及我们的ViewGroup要实现的功能,决定出ViewGroup的大小

         3.ViewGroup和子View的大小算出来了之后,接下来就是去摆放了吧,具体怎么去摆放呢?这得根据你定制的需求去摆放了,  比如,你想让子View按照垂直顺序一个挨着一个放,或者是按照先后顺序一个叠一个去放,这是你自己决定的。

         4.已经知道怎么去摆放还不行啊,决定了怎么摆放就是相当于把已有的空间"分割"成大大小小的空间,每个空间对应一个子View,我们接下来就是把子View对号入座了,把它们放进它们该放的地方去

 

 

       (1)让view支持wrap_content需要在onMeasure()中定义默认值,否则会和match_parent一样的效果,因为wrap_content的模式是AT_MOST,不设默认值spec_size会是parent。

      (2)让View支持padding需要在ondraw中处理。

      (3)view发送消息直接post而不用使用handler,handler容易内存泄漏,runnable的内部类会持有外部类的应用。

      (4)View中如果有线程或者动画,需要在 onDetachedFromWindow(销毁时调用)中处理,避免内存泄露。

8,  handler

      8.1 handler机制

             Message 两个成员变量

                             target 发送handler对象

                             callback  是当调用 handler.post(runnable) 时传入的 Runnable 类型的任务。

             MessageQueue  消息队列很明显是存放消息的队列。next() 方法,它会返回下一个待处理的消息

             Looper  想要在一个线程中创建一个 Handler,首先要通过 Looper.prepare() 创建 Looper,之后还得调用 Looper.loop()开启轮询。

                        prepare()。 这个方法做了两件事:首先通过ThreadLocal.get()获取当前线程中的Looper,如果不为空,则会抛出一个RunTimeException,意思是一个线程不能创建2个Looper。如果为null则执行下一步。第二步是创建了一个Looper,并通过 ThreadLocal.set(looper)。将我们创建的Looper与当前线程绑定。这里需要提一下的是消息队列的创建其实就发生在Looper的构造方法中。

                         loop()。 这个方法开启了整个事件机制的轮询。它的本质是开启了一个死循环,不断的通过 MessageQueue的next()方法获取消息。拿到消息后会调用 msg.target.dispatchMessage()来做处理。

              Handler   发送消息和处理消息。

                           发送:其实发送消息除了 sendMessage 之外还有 sendMessageDelayed 和 post 以及 postDelayed 等等不同的方式。但它们的本质都是调用了 sendMessageAtTime。在 sendMessageAtTime 这个方法中调用了 enqueueMessage。在 enqueueMessage 这个方法中做了两件事:通过 msg.target = this 实现了消息与当前 handler 的绑定。然后通过 queue.enqueueMessage 实现了消息入队。

                                      延迟发送原理:依靠nativePollOnce(ptr, nextPollTimeoutMillis);,这个方法中的nextPollTimeoutMills中now < msg.when表示阻塞,nextPollTimeoutMills就是等待的时间,

                          处理:消息处理的核心其实就是dispatchMessage()这个方法。这个方法里面的逻辑很简单,先判断 msg.callback 是否为 null,如果不为空则执行这个 runnable。如果为空则会执行我们的handleMessage方法

           ThreadLocal:MessageQueue对象,和Looper对象在每个线程中都只会有一个对象,怎么能保证它只有一个对象,就通过ThreadLocal来保存。Thread Local是一个线程内部的数据存储类,通过它可以在指定线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储到数据,对于其他线程来说则无法获取到数据。
 

      8.2  Handler的工作原理总结

  在使用Handler之前必须要调用Looper.prepare()这句代码,这句代码的作用是将Looper与当前的线程进行绑定,在实例化Handler的时候,通过Looper.myLooper()获取Looper,然后再获得Looper中的MessageQueue。在子线程中调用Handler的sendMessage方法就是将Message放入MessageQueue中,然后调用Looper.loop()方法来从MessageQueue中取出Message,在取到Message的时候,执行 **msg.target.dispatchMessage(msg);**这句代码,这句代码就是从当前的Message中取出Handler然后执行Handler的handleMessage方法。

      8.3 handler线程切换原理

         在子线程中Handler在发送消息的时候已经把自己与当前的message进行了绑定,在通过Looper.loop()开启轮询message的时候,当获得message的时候会调用 与之绑定的Handler的**handleMessage(Message msg)**方法,由于Handler是在主线程创建的,所以自然就由子线程切换到了主线程。

9,网络请求

     okhttp:

             9.1基本的网络请求,get/post

              get请求分为同步请求异步请求,

                     同步请求,需在子线程中使用   

//创建OkHttpClient对象
                OkHttpClient client = new OkHttpClient();
                //创建Request
                Request request = new Request.Builder()
                        .url(url)//访问连接
                        .get()
                        .build();
                //创建Call对象        
                Call call = client.newCall(request);
                //通过execute()方法获得请求响应的Response对象        
                Response response = call.execute();

                       异步请求 call.enqueue(callback),不需要创建子线程

                  post请求

             post()方法需要一个RequestBody的对象作为参数

FormBody body = new FormBody.Builder()
            .add(key,value)
            .build();
    Request request = new Request.Builder()
            .url(url)
            .post(body)
            .build();

          9.2 拦截器

                 Log输出

                 增加公共请求参数

                 修改请求头

                  加密请求参数

                  服务器端错误码处理(时间戳异常为例)

          9.3 原理

                okHttp主要包括Request、Response、Call方法。

               Call是个接口,具体的实现在RealCall里面。同步执行Call的execute方法会将Request放入请求队列,异步执行call.enqueue(),创建一个runnable(RealCall.AsyncCall)对象,加入到队列中。

                 Okhttp内有个Dispatcher类,是Okhttp内部维护的线程池,线程池取请求队列中的方法进行Socket连接。

          9.4 使用的设计模式

                责任链模式:OkHttp

                建造者模式:Response、Request

                原型模式:OkHttpClient

     Retrofit

     Retrofit:先用注解定义一个请求接口,用Retorfit对象生成该接口的代理对象,调用接口的方法会进入代理对象的invoke方法里面,调用methodHandler方法返回一个Call对象,在execute方法中使用okHttp进行网络连接。                  

      NetwrokInterceptor  网络拦截器,执行一次

      RetryAndFollowUpInterceptor   失败重连拦截器

      BridgeInterceptor 数据请求,响应response转化等处理

      CacheInterceptor 缓存策略

      ConnectInterceptor  socket连接层面的

      CallServerInterceptor  服务器进行数据读写操作

10,架构

        10.1 Rxjava

       操作符

        map:变换操作符:进行数据转换     

        flatmap:变换操作符:对集合进行数组转换

       each:遍历集合

       concat:组合操作符,严格按照顺序发射数据,

       zip :组合操作符,合并两个或者多个Observable发射的数据项,并发射一个新值。

        filter:过滤操作符,集合进行过滤

        skip:过滤操作符,跳过前几个元素

        take:过滤操作符,取出集合中的前几个

        这些变换虽然功能各有不同,但实质上都是针对事件序列的处理和再发送。而在 RxJava 的内部,它们是基于同一个基础的变换方法: lift(Operator)。在 Observable 执行了 lift(Operator) 方法之后,会返回一个新的 Observable,这个新的 Observable 会像一个代理一样,负责接收原始的 Observable 发出的事件,并在处理后发送给 Subscriber。

      (1)、Observable.create 创建事件源,但并不生产也不发射事件。

      (2)、实现observer接口,但此时没有也无法接受到任何发射来的事件。

      (3)、订阅 observable.subscribe(observer), 此时会调用具体Observable的实现类中的subscribeActual方法,

      此时会才会真正触发事件源生产事件,事件源生产出来的事件通过Emitter的onNext,onError,onComplete发射给observer对应的方法由下游observer消费掉。从而完成整个事件流的处理。

      背压是指在异步场景中,被观察者发送事件速度远快于观察者的处理速度的情况下,一种告诉上游的被观察者降低发送速度的策略简而言之,背压是流速控制的一种策略。

              观察者可以根据自身实际情况按需拉取数据,而不是被动接收(也就相当于告诉上游观察者把速度慢下来),最终实现了上游被观察者发送事件的速度的控制,实现了背压的策略。

       rxjava线程切换原理: RxJava 如何通过 RxAndroid 线程调度器来切换到主线程运行,其实 RxAndroid 的核心就是 Handler。

      为什么 subscribeOn() 只有第一次切换有效:

                 因为 RxJava 最终能影响 ObservableOnSubscribe 这个匿名实现接口的运行环境的只能是最后一次运行的 subscribeOn() ,又因为 RxJava 订阅的时候是从下往上订阅,所以从上往下第一个 subscribeOn() 就是最后运行的,这就造成了写多个 subscribeOn() 并没有什么乱用的现象。

                  observeOn将它下游的onNext操作扔给它切换的线程中

 

        10.2 MVVM :

           viewmodel跟Model和view进行双向绑定:当view发生改变时,ViewModel通知Model进行数据更新;同理,当数据更新后,viewModel通知view更新,主要是用databing来实现

           使用动态更新机制,

                               使用Observable,继承自 BaseObservable

                               使用ObservableField

                               使用Observable容器类

         databing原理:针对每个activity布局,在编译阶段,生产一个ViewDataBinding类的对象,改对象持有Activity要展示的数据和布局中的各个view的引用

         10.3 mvp 

              MVC:模型model-视图view-控制器controller,能够将业务逻辑,数据,界面分离,但是互相耦合度比较高。

              MVP:是MVC的一种演进版本,将controller改为presenter,View通过接口与presenter进行交互,降低交互。

                      优点:ui层与逻辑层分离;缺点:代码类增加了

         10.4、ARooter原理

              https://www.jianshu.com/p/46d174f37e82

             采用编译时解析技术,生成了相应的映射文件,我们可以断定,ARouter 的初始化会将这些文件加载到内存中去,形成一个路由表,以供后面路由查找跳转之用。不同modoule下组名不能相同。

11 ,动画

        11.1动画的种类

               逐帧动画;

               补间动画(移动了效果,其实 view 没有变化,还在原地);

               属性动画 (改变 view 内部的属性值,真正的改变 view)

         11.2  属性动画原理

                 属性动画以动画的效果多次去调用set,计算出每帧的属性值,通过反射的方式调用控件的相关属性,进行赋值。

         11.3  插值器与估值器

                  插值算法用于计算特定时间下属性值改变的百分比,估值器根据改变的百分比得到具体的值。

          11.4 动画信号的传递

                  View 树里面不管哪个 View 发起了布局请求、绘制请求,统统最终都会走到 ViewRootImpl 里的 scheduleTraversals(),然后在最近的一个屏幕刷新信号到了的时候再通过 ViewRootImpl 的 performTraversals() 从根布局 DecorView 开始依次遍历 View 树去执行测量、布局、绘制三大操作。

12 ,性能优化

        1,布局优化

              在 LinearLayout 和 RelativeLayout 都可以完成布局的情况下优先选择 RelativeLayout,可以减少 View 的层级

              将常用的布局组件抽取出来使用 < include >标签

               通过 < ViewStub >标签来加载不常用的布局

              使用 < Merge >标签来减少布局的嵌套层次

               用DDMS中的Hierarchy Viewer进行布局分析。

         2, 网络优化:

                 尽量减少网络请求,能够合并的就尽量合并

                避免 DNS 解析,根据域名查询可能会耗费上百毫秒的时间,也可能存在DNS劫持的风险。可以根据业务需求采用增加动态更新 IP 的方式,或者在 IP 方式访问失败时切换到域名访问方式。

                大量数据的加载采用分页的方式

                网络数据传输采用 GZIP 压缩 

                 加入网络数据的缓存,避免频繁请求网络

                  上传图片时,在必要的时候压缩图片

        3,安装包优化: 安装包优化的核心就是减少 apk 的体积,常见的方案如

               使用混淆,可以在一定程度上减少 apk 体积,但实际效果微乎其微

               减少应用中不必要的资源文件,比如图片,在不影响 APP 效果的情况下尽量压缩图片,有一定的效果

              在使用了 SO 库的时候优先保留 v7 版本的 SO 库,删掉其他版本的SO库。原因是在 2018 年,v7 版本的 SO 库可以满足市面上绝大多数的要求

          4, 内存优化:避免内存泄漏、扩大内存

               内存泄漏的本质就是较长生命周期的对象引用了较短生命周期的对象。

               常见的内存泄漏

               单例模式导致的内存泄漏。 最常见的例子就是创建这个单例对象需要传入一个 Context,这时候传入了一个 Activity 类型的 Context,由于单例对象的静态属性,导致它的生命周期是从单例类加载到应用程序结束为止,所以即使已经 finish 掉了传入的 Activity,由于我们的单例对象依然持有 Activity 的引用,所以导致了内存泄漏。解决办法也很简单,不要使用 Activity 类型的 Context,使用 Application 类型的 Context 可以避免内存泄漏。

                静态变量导致的内存泄漏。 静态变量是放在方法区中的,它的生命周期是从类加载到程序结束,可以看到静态变量生命周期是非常久的。最常见的因静态变量导致内存泄漏的例子是我们在 Activity 中创建了一个静态变量,而这个静态变量的创建需要传入 Activity 的引用 this。在这种情况下即使 Activity 调用了 finish 也会导致内存泄漏。原因就是因为这个静态变量的生命周期几乎和整个应用程序的生命周期一致,它一直持有 Activity 的引用,从而导致了内存泄漏。

                非静态内部类导致的内存泄漏。 非静态内部类导致内存泄漏的原因是非静态内部类持有外部类的引用,最常见的例子就是在 Activity 中使用 Handler 和 Thread 了。使用非静态内部类创建的 Handler 和 Thread 在执行延时操作的时候会一直持有当前Activity的引用,如果在执行延时操作的时候就结束 Activity,这样就会导致内存泄漏。解决办法有两种:第一种是使用静态内部类,在静态内部类中使用弱引用调用Activity。第二种方法是在 Activity 的 onDestroy 中调用 handler.removeCallbacksAndMessages 来取消延时事件。

                 使用资源未及时关闭导致的内存泄漏。 常见的例子有:操作各种数据流未及时关闭,操作 Bitmap 未及时 recycle 等等。
 

                  使用第三方库未能及时解绑。 有的三方库提供了注册和解绑的功能,最常见的就 EventBus 了,我们都知道使用 EventBus 要在  onCreate 中注册,在 onDestroy 中解绑。如果没有解绑的话,EventBus 其实是一个单例模式,他会一直持有 Activity 的引用,导致内存泄漏。同样常见的还有 RxJava,在使用 Timer 操作符做了一些延时操作后也要注意在 onDestroy 方法中调用 disposable.dispose()来取消操作。


                    属性动画导致的内存泄漏。 常见的例子就是在属性动画执行的过程中退出了 Activity,这时 View 对象依然持有 Activity 的引用从而导致了内存泄漏。解决办法就是在 onDestroy 中调用动画的 cancel 方法取消属性动画。

                      WebView 导致的内存泄漏。WebView 比较特殊,即使是调用了它的 destroy 方法,依然会导致内存泄漏。其实避免WebView导致内存泄漏的最好方法就是让WebView所在的Activity处于另一个进程中,当这个 Activity 结束时杀死当前 WebView 所处的进程即可

 

             扩大内存通常有两种方法:一个是在清单文件中的 Application 下添加largeHeap="true"这个属性,另一个就是同一个应用开启多个进程来扩大一个应用的总内存空间。第二种方法其实就很常见了,比方说我使用过个推的 S DK,个推的 Service 其实就是处在另外一个单独的进程中。

        

         5,anr日志分析

                主线程对输入事件超过一段时间没有响应就会出现anr,anr生成后会在/data/anr/traces.txt中生成日志信息。日志信息会记录执行哪个进程的时候出了问题,如果没发现问题,继续看具体的trace日志,可以看关键字thread的地方。

         4, listView与bitmap优化

            ViewHolder+converview 复用

            bitmap主要是加载时进行压缩。

 13,常见的设计模式

        13.1 单例模式

              13.1.1饿汉式:线程安全,非懒加载。线程安全通过类加载机制保障

                   执行类构造器 <clinit>() 方法的过程JVM 会对类加锁,保证在多线程环境下,只有一个线程能成功执行 <clinit>() 方法,其他线程都将被拥塞,并且 <clinit>() 方法只能被执行一次,被拥塞的线程被唤醒之后也不会再去执行<clinit>()方法。

                   由此可见,静态代码的多线程安全是由 JVM 在类加载阶段为其加锁实现的。

              13.1.2 懒汉式:懒加载,非线程安全。

              13.1.3 双重检查锁:自己加锁加volatile禁止指令重排

                      指令重排序可能会改变代码的执行顺序,能够保证不影响代码在单线程中的结果,但是多线程中并发执行的结果则是不可控的          

              13.1.4 静态内部类:JVM 保证了线程安全,并且是在使用的时候进行加载的,也实现了懒加载

       13.2 构建者模式(生成器模式)

               构造者模式是将一个复杂对象构建和它的表示相分离,使得同样的构造过程可以创建出不同的对象 。只需要定义我们想要的对象的属性,最终就可以得到我们想要的对象,而不用去关心内部是怎么构建对象的。

              四个角色:

                          1,Build构造对象的抽象,

                         2,ConcreteBuilder Builder抽象的具体实现

                         3,Product被构建的具体产品对象

                         4,Director 指挥者,导演者

                         需求->告诉Director(提供一个制作的方法,通过传参告诉Product),我们要什么Product,通过ConcreteBuilder(继承Builder),完成实际的产品制作。比如喝咖啡,我们只需要告诉服务员,我们要什么咖啡就可以喝的想要的咖啡了。

             建造者模式可以封装成链式调用

       13.3   工厂模式

             简单工厂模式:平时开发过程中的一个开发习惯,比如在一个类中提供一个静态方法,根据传递进来的不同参数,返回具体的对象。

                                  创建一个抽象产品类,抽象产品的实现类,创建工厂类,通过传参调用对应的实现类

                                  Factory.doMethod("参数类别")

             工厂方法模式:取消了统一创建对象的 PayFactory ,而是提供一个创建对象的抽象,把具体的创建任务交给具体的工厂,比如获取苹果支付的对象,用 ApplePayFactory ,获取华为支付的对象用 HuaWeiPayFactory

                                  创建抽象工厂(包含创建产品的抽象方法),创建工厂的实现类,实现抽象方法,

                                  realFactory1.create().pay()

               抽象工厂模式: 创建抽象产品,创建抽象工厂, 创建对抽象产品的实现类(可以多个), 创建工厂的实现类(可多个)

                                     realFactory1.createRealProduct1().doAction()                           

         13.4 代理模式(委托模式)

                限制对对象的直接访问,要想访问具体对象需要通过该对象的代理类去访问。静态代理类,自己编写,动态,动态生成

                 静态代理:主要有三个部分,抽象接口,具体对象,代理对象

                 动态代理:DynamicProxyHandler 里面的 invoke 里面的代码,是在执行代理接口的里面的方法的时候才会被执行到,动态代理的代理类则是由运行时使用 newProxyInstance 动态生成,

         13.5 责任链模式

                为请求创建了一个接收者对象的链。有一事件,可以被多个对象同时处理,但是由哪个对象处理则在运行时动态决定!

                纯责任链:当请求到达一个类是要么处理完,要么扔给下个对象。像事件处理。

                不纯责任链:当请求到达时,该类处理一部分,然后扔给下一个。

                责任链模式的特点

                     1.抽象一个任务处理类。

                     2.任务处理类持有下一个处理任务对象

                     3.纯责任链适合针对不对的要求,做不同的操作。例如if-else,switch-case

                     4.不纯责任链适合对一个结果做分步骤操作,动态的去完成一个结果,更加灵活。

               OKHttp的责任链模式是一种不纯的责任链,即每一个拦截器都组装Reponse的一部分。

14,view的绘制流程

        视图绘制的起点在 ViewRootImpl 类的 performTraversals()方法,在这个方法内其实是按照顺序依次调用了 mView.measure()、mView.layout()、mView.draw()
View的绘制流程分为3步:测量、布局、绘制,分别对应3个方法 measure、layout、d

  • 测量阶段。 measure 方法会被父 View 调用,在measure 方法中做一些优化和准备工作后会调用 onMeasure 方法进行实际的自我测量。onMeasure方法在View和ViewGroup做的事情是不一样的:

    • View。 View 中的 onMeasure 方法会计算自己的尺寸并通过 setMeasureDimension 保存。
    • ViewGroup。 ViewGroup 中的 onMeasure 方法会调用所有子 iew的measure 方法进行自我测量并保存。然后通过子View的尺寸和位置计算出自己的尺寸并保存。
  • 布局阶段。 layout 方法会被父View调用,layout 方法会保存父 View 传进来的尺寸和位置,并调用 onLayout 进行实际的内部布局。onLayout 在 View 和 ViewGroup 中做的事情也是不一样的:

    • View。 因为 View 是没有子 View 的,所以View的onLayout里面什么都不做。
    • ViewGroup。 ViewGroup 中的 onLayout 方法会调用所有子 View 的 layout 方法,把尺寸和位置传给他们,让他们完成自我的内部布局。
  • 绘制阶段。 draw 方法会做一些调度工作,然后会调用 onDraw 方法进行 View 的自我绘制。draw 方法的调度流程大致是这样的:

    • 绘制背景。 对应 drawBackground(Canvas)方法。
    • 绘制主体。 对应 onDraw(Canvas)方法。
    • 绘制子View。 对应 dispatchDraw(Canvas)方法。
    • 绘制滑动相关和前景。 对应 onDrawForeground(Canvas)。

15,sparseArray (参考数据结构部分更为详细)

16,静态注册广播和动态注册广播的区别

          动态注册广播不是常驻型广播,也就是说广播跟随 Activity 的生命周期。注意在 Activity 结束前,移除广播接收器。 静态注册是常驻型,也就是说当应用程序关闭后,如果有信息广播来,程序也会被系统调用自动运行。

          Android7.0之后,静态方式注册广播已经不能使用,需要改成动态注册。

          让一个栈顶activity给一个栈内activity发送消息,如果中间隔着多个页面,不用广播实现起来相对麻烦。

     

17,Android中的类加载器

       PathClassLoader,只能加载系统中已经安装过的 apk
       DexClassLoader,可以加载 jar/apk/dex,可以从 SD卡中加载未安装的 apk

 

18,跨进程通信

(1)bundle:四大组件中activity,service,receiver都可以在intent中传递bundle数据,传递的数据需要序列化。

(2)ContentProvider

(3)messenger

(4)aidl

         aidl实现本质上是系统为我们提供了一种快速实现binder的工具。aidl系统中,服务端创建aidl文件,并在service中实现这个接口。客户端需要绑定服务端的service,并将服务端返回的binder对象转成aidl所属接口的类型,就可以调用aidl中的方法了。客户端调用aidl方法是同步的,不可以在UI线程调用耗时的aidl方法。服务端是运行在binder线程池中,本身可以执行耗时操作,不用再另开线程。

(5)Scoket

         tcp

(6)文件共享

19, binder机制

20,数据库升级

      第一种:在onCreate()方法里面调用onUpgrade方法,在onUpgrade()方法里面对不同版本号做处理

      第二种:在onUpgrade()方法里面,使用switch.case,判断oldversion,之前的case不过break处理,新增case做升级操作,这样能保证在升级的时候,每一次的数据库修改都能被全部执行到。

      修改表定义时需要创建临时表,拷贝数据后删除原表。

21,onActivityResult好用吗,能不能用接口回调的方式代替

      a,startActivity 跟onActivityResult,逻辑分开,使用不方便

      b,onActivityResult跟setResult,参数类型必须一致

      c,onActivityResult 种类较多,容易臃肿

      不能用回调的方式,因为回调会引用匿名内部类,匿名内部类会持有外部类引用,会导致gc无法正常回收,Activity的销毁和恢复机制不允许匿名内部类出现

 

22,https

       http+ssl

      22.1数据传输是用对称加密,

           非对称加密效率低,http场景端与端会存在大量交互,这种效率是难以接受的。 https服务端值保留了私钥,一对公私钥只能实现单向的加解密,所以内容传输采用的是对称加密。

      22.2 为什么需要CA认证机构颁发证书

           http协议本地请求如果被第三方中间人劫持,中间人可以篡改传输信息,没有证书校验,客户端不知道自己的网络已被拦截,传输内容存在被窃取的风险。同时可以为网站提供身份证明。

      22.3 本地随机数安全怎么保证

            放在so里面处理,采用对对称加密,获取native层面的的动态秘钥。

      22.4  HTTPS 为什么安全?

               因为 HTTPS 保证了传输安全,防止传输过程被监听、防止数据被窃取,可以确认网站的真实性。

      22.5 https传输过程

              客户端发起 HTTPS 请求,服务端返回证书,客户端对证书进行验证,验证通过后本地生成用于改造对称加密算法的随机数。通过证书中的公钥对随机数进行加密传输到服务端,服务端接收后通过私钥解密得到随机数,之后的数据交互通过对称加密算法进行加解密。

        22.6 签名验证

               假设A要想B发送消息,A会先计算出消息的消息摘要,然后使用自己的私钥加密这段摘要加密,最后将加密后的消息摘要和消息一起发送给B,被加密的消息摘要就是“签名”。

              B收到消息后,也会使用和A相同的方法提取消息摘要,然后使用A的公钥解密A发送的来签名,并与自己计算出来的消息摘要进行比较。如果相同则说明消息是A发送给B的,同时,A也无法否认自己发送消息给B的事实。

              其中,A用自己的私钥给消息摘要加密成为“签名”;B使用A的公钥解密签名文件的过程,就叫做“验签”。

      22.7 https会被抓包吗

              会被抓包,如果用户主动授信,可以构建“中间人”网络。https只防用户在不知情情况下通信被监听。

 

23,启动模式

       23.1 standard 标准模式

       23.2 singleTop 栈顶复用,A启动B,如果B在栈顶,就不再创建B的实例。并回调onNewIntent方法

                      应用:防止重复点击同一个页面;从外部点击进入某一个页面,该页面用singleTop模式

        23.3 singleTask 栈内复用,A启动B,如果B在栈顶,就不再创建B的实例,并清除回退栈中该activity上面的任何其它activity。是否会启动新的task要看taskAffinity属性。跟singleTop一样也会回调onNewIntent方法

                      如果跳转界面时intent加了FLAG_NEW_TASK,系统会把已启动并且具有相同taskAffinity属性的Activity移至前台,也就是说 可能会新开一个栈并把不同App中相同taskAffinity属性Activity"吸附"进栈。

        23.4 singleInstance  启动一个activity到新的task中,该activity是新的task中的唯一activity,在该task再启动activity会再重新启动回原task中。

                        A-B(singleInstance)->c,这时返回,会直接进入A中,再返回进入b中

                        应用:打电话

24,webview与原生交互,防注入等

js调用原生方法:1. 通过WebView的addJavascriptInterface()进行对象映射 

2. 通过 WebViewClient 的shouldOverrideUrlLoading ()方法回调拦截 url 

3. 通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS对话框alert()、confirm()、prompt() 消息

原生调用js方法:

1. 通过WebView的loadUrl() 

2. 通过WebView的evaluateJavascript()

 

防注入主要是4.0之前,4.0之后需要加@javaScriptInterface

 

25,人事面试

https://www.jianshu.com/p/d61b553ff8c9

26,为什么选择我们公司

       1,岗位的匹配度,工作经验和技能要求

       2,表示对贵公司很有认同感。

       3,在大公司工作过,能很好的处理同事和领导之间的关系。

27,什么职业规划

       自己努力提升自己岗位的技能,根据公司业务的需要实时调整和补充自己的专业技能,在专业领域有所深入。

28,项目中有什么难点,如何解决的

       数据库升级,

       当时eclipse转android studio ,svn转git,

       项目的重构,包括项目工作量大的问题,新老框架迁移代码的兼容性问题

       接口耗时的问题,一个个接口去抓包,看耗时,耗时特别长的让服务端优化,同时客户端做接口合并

    生成一个随机key,通过证书拿到公钥加密key生成enKEY,用公钥加密json生成enData,防止篡使用md5对enKEY,enDATA生成mc,组合成一个json传递给服务端。
    服务端返回值后,通过解密方法解密,同事对enKEY做rsa验证签名。签名正确则解析返回response
    密码会转成ticket,传输等操作都是ticket,密码已经做了转换了。
    keystore.setCertificateEntry("ca", ca);

29,  android10的适配

       1,用户存储权限的变更,隔离存储沙盒,其他应用无法直接访问应用的沙盒文件。Q平台上取消读写外部storage权限,替换为新的媒体权限

       2,原来的READ_PHONE_STATE权限已经不能获得IMEI和序列号,改成新的方法。

       3,运行在Q设备上的应用targetSDK>=23,不然会向用户发出警告。

30、window、activity、DecorView之间的关系

      Activity 创建时通过 attach()初始化了一个 Window ,window表示一个窗口的概念,它的具体实现PhoneWindow。一个 PhoneWindow 持有一个DecorView 的实例。DecorView是顶级View,但是不包括状态栏都系统UI。,Android中所有的视图都是通过Window来呈现的,因此window实际上是View直接管理者。访问window必须通过windowManager。

31、window的创建过程

        Activity setContentView的过程(1)如果没有DecroView,那么就创建它(2)将View添加DecorView的mContentParent中(3)回调Activity的onContentChanged方法通知Activity视图已经改变。

        dialog的Window创建过程:(1)创建window(2)初始化DecorView并将Dialog的视图添加到DecorView中(3)将DecorView添加到window中并显示

        toast属于系统window,可以使用系统默认样式,也可以指定view。采用handler进行定时取消,内部有两次IPC过程,NoticFicationService

32,surfaceview

    当view需要频繁的刷新,或者在刷新是数据处理量大,可能引起卡顿,可以考虑使用surfaceview来替代view.

     view在主线程刷新ui,surfaceview在子线程进行画面更新

     view 没有双缓冲机制,surfaceview双缓冲机制(后缓冲区,减少等待时间)

33,looper.loop()死循环

       线程既然是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了。主线程也是线程,它也是有生命周期,死循环就是保住主线程一直存活。

    真正会卡死主线程的操作是在回调方法onCreate/onStart/onResume等操作时间过长,会导致掉帧,甚至发生ANR,looper.loop本身不会导致应用卡死。

 33.1  主线程中的Looper.loop()一直无限循环检测消息队列中是否有新消息为什么不会造成ANR? 

          a,为什么要做消息循环:ActivityThread的main方法主要就是做消息循环,一旦退出消息循环,那么你的应用也就退出了。

          b,为什么不anr:我们的代码其实就是在这个循环里面去执行的,当然不会阻塞了

                因为Android 的是由事件驱动的,looper.loop() 不断地接收事件、处理事件,每一个点击触摸或者说Activity的生命周期都是运行在 Looper.loop() 的控制之下,如果它停止了,应用也就停止了。只能是某一个消息或者说对消息的处理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞它。

         总结:Looer.loop()方法可能会引起主线程的阻塞,但只要它的消息循环没有被阻塞,能一直处理事件就不会产生ANR异常。

        (理解为:消息循环过程可能因为主线程当前的事件没有得到机会处理,或者当前事件正在处理,会引起主线程阻塞。looper.loop()方法在messagequeue没有消息的时候也会造成主线程阻塞,但这种情况下,当子线程往消息队列里继续发消息的时候,主线程会被唤醒,还能继续处理事件,所以不会ANR) 

         https://blog.youkuaiyun.com/ksjay_1943/article/details/54893773?utm_source=blogxgwz5

 33.2  主线程的死循环一直运行是不是特别消耗CPU资源呢?     

      epoll模型 
      当没有消息的时候会epoll.wait,这个时候其实是阻塞的。简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,详情见Android消息机制1-Handler(Java层),此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制。

     主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源

https://www.cnblogs.com/chenxibobo/p/9640472.html

 33.3 Activity的生命周期是怎么实现在死循环体外能够执行起来的?

   ActivityThread的内部类H继承于Handler,通过handler消息机制,简单说Handler机制用于同一个进程的线程间通信。

    Activity的生命周期都是依靠主线程的Looper.loop,当收到不同Message时则采用相应措施:
     在H.handleMessage(msg)方法中,根据接收到不同的msg,执行相应的生命周期。

     比如收到msg=H.LAUNCH_ACTIVITY,则调用ActivityThread.handleLaunchActivity()方法,最终会通过反射机制,创建Activity实例,然后再执行Activity.onCreate()等方法;
      再比如收到msg=H.PAUSE_ACTIVITY,则调用ActivityThread.handlePauseActivity()方法,最终会执行Activity.onPause()等方法。 上述过程,我只挑核心逻辑讲,真正该过程远比这复杂。

      主线程的消息又是哪来的呢?当然是App进程中的其他线程通过Handler发送给主线程,请看接下来的内容:

   33.4、一个线程可以有几个Handler?几个Looper?几个MessageQueue?

            一个线程一个Lopper,一个MessageQueue,多个handler.

 

34,listview和recycleview

    listview

     继承的时BaseAdapter,需要重写四个方法

     不强制使用viewholder

     可以直接使用item的点击事件

      不用单独设置分隔线

        不可以定向刷新某一条数据

        reccycleview

       继承的是Recycleview.Adapter
       必须使用viewholder,封装了view的复用
       使用布局管理器管理布局的样式(横向、竖向、网格、瀑布流布局)
      点击事件可以使用给控件设置点击事件,也可以自定义点击事件。
      可以自定义绘制分隔线
      可以自定义item删除增加的动画效果
     可以定向刷新某一条数据notifyItemChanged等众多方法

     缓冲机制不同

       recycleview四级缓存,底层用sparseArray。listview二级


35,TCP和HTTP的区别和联系

      TCP是底层通讯协议,定义的是数据传输和连接方式的规范
      HTTP是应用层协议,定义的是传输数据的内容的规范

      TCP

      建立起一个TCP连接需要经过“三次握手”:

      第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;

      第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;

      第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。

        握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连 接之前,TCP 连接都将被一直保持下去。

       断开TCP连接需要经过"四次挥手"

       四次挥手,同样也是基于以上的原理。尤其是通信双方都可以独立关闭自己的通信通道,也就是半关闭。
client先发送FIN告知对方我已经完成数据发送了,server回复ack来确定我知道了。这样一个流程,就关闭了client的发送信息通道。但是还可以接收来自server方的数据。
server此时已经知道接收不到client的数据了,但是还可以给它发送数据。如果server也没有啥数据要发送给对方了,server也会以FIN标志位发送一个信息给client,client接到后,也会传递一个ack表示知道了。这样子,双方都完成了关闭。
 

       HTTP

      TCP用来建立连接,HTTP用来传输数据。

       UDP是无连接的,知道IP地址和端口号,向其发送数据即可,不管数据是否发送成功

        HTTP 超文本传输协议。建立在tcp协议之上。客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为“一次连接”。

       HTTP每次连接都会释放连接,因此成为短连接。

       Socket

       Socket 是支持TCP协议,网络通信是包含五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,原地进程的协议端口,

      建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket ,另一个运行于服务器端,称为ServerSocket 。

       套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。

36,项目相关

        36.1 gradle

        gradle多渠道打包,       

               通过  flavorDimensions "sdk_flavor", "environment"。在productFlavors下区分不同的渠道,通过dimension来定义sdk_flavor,和environment,来生成不同的包,比如 百度release。在不同的渠道下面可以定义不同的属性,可以通过BuildConfig来引用

        不同的渠道引用不同的资源文件:通过sourceSets

        构建:自己定义不同的task,能自动构建出对应的版本。   

        36.2  安全键盘

                 初衷防止被监听。

                 注意的点。1,仿截屏攻击,2,密码在内存中全程加密存储 3,随机布局 

        36.3,jni的实现

                1,java中声明native方法

                2,编译java源文件得到class文件,然后通过javah命令导出JNI的头文件

                3, 实现JNI方法,

                4,编译so库并在java中调用

          36.4  jenkens自动化打包

                  jenkens不仅可以提供android,还可以ios, java服务打包。android通过配置gradle,jdk,之后输入对应的task命令即可,构建完成后打包地址可以自动发邮件等 。

           37.5 apk缩包

            1,代码混淆:

                   设置minifyEnabled开启混淆,删除无用代码,shrinkResources,删除无用资源,proguardFiles,混淆的配置文件

             2,第三方包,避免重复

                    比如多个图片加载库会导致功能重复。用源码引入,然后根据所用的功能做裁剪,避免整个库的引入。

              3,图片Webp格式

                    Google开发的新图片格式,压缩效率高于png和jpeg,占用资源较小

              4,第三方tinypng进行png图片的压缩。

              5,Android Lint删除冗余资源

              6, 只支持arm-v7a架构的库

              7,插件化   

                    将业务插件apk放在服务器,使用时启动预加载

              8,使用微信团队的资源混淆方案

                      原理类似java混淆。他会将原本冗长的资源路径变短res/drawable/wechat变为r/d/a

                      https://github.com/shwenzhang/AndResGuard/blob/master/README.zh-cn.md

       36.4  volley

                多并发网络连接,Volley适合数据量小和请求频繁的操作.

               Volley不适合大的下载和流操作,支持请求的优先级

                 我们在主线程中调用RequestQueue的add()方法来添加一条网络请求,这条请求会先被加入到缓存队列当中,如果发现可以找到相应的缓存结果就直接读取缓存并解析,然后回调给主线程。如果在缓存中没有找到结果,则将这条请求加入到网络请求队列中,然后处理发送HTTP请求,解析响应结果,写入缓存,并回调主线程。

 

37,onSaveInstance什么时候触发

      容易被销毁的时候调用

             onPause->onSaveInstanceState->onStop   没有掉finish,而去走了stop方法

             按下Home键 : Activity 进入了后台, 此时会调用该方法;
             按下电源键 : 屏幕关闭, Activity 进入后台;
             启动其它 Activity : Activity 被压入了任务栈的栈底;
             横竖屏切换 : 会销毁当前 Activity 并重新创建;

      之后我们就可以在onCreate()中拿到了

        onRestoreInstanceState()

             销毁之后重建

              

37,热修复原理

 (1)代码热修复

     Android的热修复原理大体上分为两种,其一是通过dex的执行顺序实现Apk热修复的功能,但是其需要将App重启才能生效,      其二是通过Native修改函数指针的方式实现热修复

    代码热修复:

    冷启动方案,用dexElements的顺序调整来实现,启动时用dexClassLoader加载补丁dex,将补丁dex放到dexElements第一   位,这样补丁类就会被打上class_ispreverified标志,原来的类就无法加载了。类加载方案时效性差,限制少。

    热启动方案Sophix,修改native代码,改变指针指向的方法,在已经加载的类中直接替换掉原有的方法,无法增减方法和字段,可以实时生效。

 (2)SO热修复

    So采用和dex替换相似的方法,将补丁so文件插入到nativeLibraryDirections数组的最前面,替换掉原有的native方法。

 (3)资源热修复

    google instantRun方案:构造新的AssetMananger,并通过反射调用addAssetPath,把新资源加入到AssetManager中,得到一个含有所有新资源的AssetManager。然后找到所有AssetMananger被用到的地方,使用反射替换成新的AssetManager。

    Sophix构建了一个和0x7f不一样资源包ID,不和原有资源冲突,就可以将新的资源文件放到Assetmanager中了。

 

38.1 子线程可不可以更新ui

      ViewRootImpl的创建是在onResume方法回调之后,而我们一开篇是在onCreate方法中创建子线程并访问UI,在那个时刻,ViewRootImpl还没有来得及创建,无法检测当前线程是否是UI线程,所以程序没有崩溃。而之后修改了程序,让线程休眠了100毫秒后再更新UI,程序就崩了。很明显在这100毫秒内ViewRootImpl已经完成了创建,并能执行checkThread方法检查当前访问并更新UI的线程是不是UI线程。

38.2 子线程为什么不跟新ui
        Android的UI访问是没有加锁的,多个线程可以同时访问更新操作同一个UI控件。也就是说访问UI的时候,android系统当中的控件都不是线程安全的,这将导致在多线程模式下,当多个线程共同访问更新操作同一个UI控件时容易发生不可控的错误,而这是致命的。所以Android中规定只能在UI线程中访问UI,这相当于从另一个角度给Android的UI访问加上锁,一个伪锁。
       总结:

      子线程可以在ViewRootImpl还没有被创建之前更新UI

      访问UI是没有加对象锁的,在子线程环境下更新UI,会造成不可预期的风险;

39,binder

       从android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindservice的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端可以获取服务端的服务和数据(服务包括普通服务和aidl服务)

       Binder主要在aidl和Messenger,Messenger底层是AIDL

    39.2、binder底层机制

在客户端自动生成的binder实现中,会生成一个内部代理类实现aidl接口,在客户端中将binder转成aidl代理对象。客户端调用代理中的接口方法,将参数序列化后调用binder的transact方法,通过系统底层封装后交由服务端的binder线程池中的onTransact方法处理,调用该方法时会为每个方法分配一个id,通过这个id服务端就知道客户端具体是调的哪个方法,执行完后将结果写入reply再交回给客户端的transact方法,就完成了一次跨进行方法调用。

    39.3、binder机制的优点

  • 高性能:从数据拷贝次数来看Binder只需要进行一次内存拷贝,而管道、消息队列、Socket都需要两次,共享内存不需要拷贝,Binder的性能仅次于共享内存。
  • 稳定性:上面说到共享内存的性能优于Binder,那为什么不适用共享内存呢,因为共享内存需要处理并发同步问题,控制负责,容易出现死锁和资源竞争,稳定性较差。而Binder基于C/S架构,客户端与服务端彼此独立,稳定性较好。
  • 安全性:我们知道Android为每个应用分配了UID,用来作为鉴别进程的重要标志,Android内部也依赖这个UID进行权限管理,包括6.0以前的固定权限和6.0以后的动态权限,传统IPC只能由用户在数据包里填入UID/PID,这个标记完全是在用户空间控制的,没有放在内核空间,因此有被恶意篡改的可能,因此Binder的安全性更高。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值