Android之 常见问答总结

1,面向对象开发的四大特性:

  • 封装(Encapsulation):封装是将数据和方法组合在一起,对外部隐藏实现细节,只公开对外提供的接口。这样可以提高安全性、可靠性和灵活性。

  • 继承(Inheritance):继承是从已有类中派生出新类,新类具有已有类的属性和方法,并且可以扩展或修改这些属性和方法。这样可以提高代码的复用性和可扩展性。

  • 多态(Polymorphism:多态是指同一种操作作用于不同的对象,可以有不同的解释和实现。它可以通过接口或继承实现,可以提高代码的灵活性和可读性。

  • 抽象(Abstraction):抽象是从具体的实例中提取共同的特征,形成抽象类或接口,以便于代码的复用和扩展。抽象类和接口可以让程序员专注于高层次的设计和业务逻辑,而不必关注底层的实现细节。

2,四大组件

  • 四大组件是什么?Activity可视化界面,Service无界面后台服务,ContentProvider数据共享内容提供者,BroadcastReceiver消息传递广播
  • Activity切换横竖屏时会重新走生命周期,从onstop到onCreate,如果在清单文件配置android:configChanges="orientation|keyboardHidden|screenSize"就可以避免该情况
  • onNewIntent调用时机,Intent是用来进行组件间通信的,那onNewIntent就是用来处理新的数据通信,在每次复用得时候调用,即走onRestart()生命周期得时候调用。
  • Intent传输数据的大小是有限制的,因为Intent 中的 Bundle 是使用 Binder 机制进行数据传送的, 数据会写到内核空间,即Binder 缓冲区域,缓冲区大小一般是1-2M。
  • Activity启动模式,standard默认模式(每次startActivity会创建新得Activity);SingleTop栈顶复用模式;SingleTask栈种单例模式;SingleInstance全局系统单例模式
  • Service和Activity进行通信,通过bindService()可以实现Activity调用Service中的方法;通过广播实现Service向Activity发送消息
  • Activity 的启动过程,调用 startActivity() 后经过重重方法会转移到 ActivityManagerService 的 startActivity(),并通过一个 IPC 回到 ActivityThread 的内部类 ApplicationThread 中,并调用其scheduleLaunchActivity()将启动Activity的消息发送并交由Handler H处理。Handler H对消息的处理会调用handleLaunchActivity()->performLaunchActivity()得以完成Activity对象的创建和启动
  • onSaveInstanceState(),onRestoreInstanceState的调用时机,当系统内存不足等原因回收Activity,Activity销毁前会调用onSaveInstanceState()用来保存数据;当再次启动被回收的Activity会调用onRestoreInstanceState();如切换横竖屏生命周期如下onPause() –> onSaveInstanceState() –> onStop() –> onDestroy() –> onCreate() –> onStart() –> onRestoreInstanceState() –> onResume()

3,RecyclerView缓存

RecyclerView与Listview对比优点:

  • 缓存机制,recycleView四级缓存,listView两级,而且屏幕内缓存都是放在一个list中
  • ViewHolder的编写规范化了,不再需要像 ListView 那样自己调用 setTag
  • 支持多种布局,LayoutManage内置了线性布局、网格布局、瀑布流布局
  • 更加方便添加item动画、控制滑动、修改Item分割线
  • 不支持ItemClick,而是通过RecyclerView.OnItemTouchListener接口来探测触摸事件。虽然稍微 

RecyclerView四级缓存

  • 一级缓存:mAttachedScrap 和 mChangedScrap,用来缓存还在屏幕内的 ViewHolder。
  • 二级缓存:mCachedViews ,用来缓存移除屏幕之外的 ViewHolder,默认大小为2。
  • 三级缓存:ViewCacheExtension,开发给用户的自定义扩展缓存,需要用户自己管理 View 的创建和缓存。(不常用)
  • 四级缓存:RecycledViewPool,ViewHolder的缓存池,默认大小为5。

RecyclerView的滑动回收复用机制 

  • 创建item,比如屏幕内可以显示10个ViewHolder,一开始会创建10个ViewHolder用于显示,创建5个ViewHolder放入缓存池中
  • 上滑时会将滑入屏幕上方的ViewHolder首先放入二级缓存中,由于默认大小为2,所以多余的3个ViewHolder会被回收到四级回收中,此时一级缓存中有10个ViewHolder,二级缓存中有2个ViewHolder,四级缓存中有3个ViewHolder
  • 继续上滑的过程中,一级缓存需要显示15个ViewHolder,此时需要从四级缓存中取出5个ViewHolder,由于只有3个ViewHolder,所以需要创建两个缓存
  • 上滑完毕后,5个一级缓存的ViewHolder移出屏幕,其中2个一级缓存的ViewHolder会被存入二级缓存,之前的2个二级缓存的ViewHolder 和 3个一级缓存的ViewHolder会被清空数据后存至四级缓存中

4,Activity、Window、DecorView与ViewRoot之间的关系 

  • Acitiviy不是真正的展示窗口,是作为一个载体与入口的存在。
  • PhoneWidow是真正的展示窗口,一个Activity上面有一个PhoneWindow,PhoneWinow上有一个DecoView
  • DecoView上面的ContentView(R.id.content)才是我们真是写的布局文件所展示的位置,PhoneWindow通过WindowManager管理DecoView
  • ViewRoot 是 DecorView 的管理者,它负责 View 树的测量、布局、绘制,以及事件分发入口。
  • WMS是PhoneWindow的管理者,是一个系统服务,管理window的显示隐藏以及要显示的位置

5, View绘制流程

View 的坐标系统是相对于父控件而言的,已父控件左上角为原点:

  • getTop()         //获取子 View  最上边  到父 View 顶部的距离
  • getLeft()         //获取子 View  最左边  到父 View 左边的距离
  • getBottom()   //获取子 View  最下方  到父 View 顶部的距离
  • getRight()      //获取子 View  最右边  到父 View 左边的距离

所以如下可以算出控件的宽高:

  • View 的高 = getBottom() - getTop()
  • View 的宽 = getRight() - getLeft()

MotionEvent 中的 getX 和 getRawX的区别:

触摸点相对于其所在父view中的坐标

  • event.getX()  
  • event.getY()

触摸点相对于屏幕坐标系的坐标:

  • event.getRawX()    
  • event.getRawY()      

MeasureSpec 位数含义:

  • 简单来说就是一个 int 值,高 2 位表示测量模式,低 30 位用来表示大小

测量模式:

  • EXACTLY: match_parent 和具体数值的情况
  • AT_MOST:wrap_content 自适应模式
  • UNSPECIFIED:表示不限定测量模式,父容器不对 View 做任何限制,这种适用于系统内部,我们一般不会使用 

OnMeasure测量:

  • 自定义View时候:测量的是自身大小
  • 自定义ViewGroup时候:还需要测试子View大小

测量子view大小代码

  • measureChild(childview, widthMeasureSpec, heightMeasureSpec);

测量自身大小代码:

  • setMeasuredDimension(parentWidth,(heightMode == MeasureSpec.EXACTLY) ? parentHeight: parentautoHeight);

padding

  • 自定义ViewGroup一般处理在OnMeasure中
  • 自定义View一般处理在onDraw中

margin

  • 自定义ViewGroup,在onlayout中自定义实现
  • 自定义View不需要处理,是生效的

OnLayout 布局:

  • 遍历处理子view的位置(自己不需要),child.layout(l,t,r,b)
  • 也就是说,只有自定义ViewGroup时候才需要,如果没有子View可以不用复写。

OnDraw 绘制:

  • paint是画笔,可以设置颜色粗细这些
  • canvas是画布,可以绘制点、线、圆形、方形等,还可以缩放、位移、旋转等
  • matrix是矩阵,canvas实践是通过matrix实现缩放、平移操作的底层实现。

RequesLayout与invalidate区别: 

  • requestLayout是从新走measure-layout-draw流程
  • invalidate是从新走draw流程。    

6,线程

进程和线程 

  • 进程是资源 (CPU、内存等)分配的基本单位,它是程序执行时的一个实例。
  • 线程是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位

进程的种类

  • 前台进程,可见进程,服务进程,后台进程,空进程

Android 进程间数据共享

  • BroadcastReceiver 广播
  1. 优点:简单易用实时通信
  2. 缺点:只支持数据单向传递,效率低且安全性不高
  3. 场景: 一对多的低频率单向通信
  • ContentProvider 内容提供者
  1. 优点:支持一对多的实时并发通信,在数据源共享方面功能强大,可通过Call方法扩展其它操作
  2. 缺点:可以理解为受约束的AIDL,主要提供对数据源的CRUD操作
  3. 场景: 一对多的进程间数据共享
  • AIDL
  1. 优点:功能强大,支持一对多实时并发通信
  2. 缺点:使用稍复杂,需要处理好线程间的关系
  3. 场景: 一对多通信且有RPC需求
  • 文件共享
  1. 优点:简单易用
  2. 缺点:不适用高并发场景,并且无法做到进程间即时通信
  3. 场景:适用于无并发的情况下,交换简单的数据,对实时性要求不高的场景。
  • Bundle
  1. 优点:简单易用
  2. 缺点:只能传输Bundle支持的数据类型
  3. 场景: 四大组件间进程通信
  • Socket
  1. 优点:功能强大,可通过网络传输字节流,支持一对多实时并发通信
  2. 缺点:传输效率低,开销大,实现细节步骤稍繁琐,不支持直接的RPC
  3. 场景: 网络间的数据交换
  • Messenger
  1. 优点:功能一般,支持一对多串行通信,支持实时通信
  2. 缺点:不能很好地处理高并发的情形,不支持RPC,由于数据通过Message传输,因此只能传输Bundle支持的数据类型
  3. 场景: 低并发的一对多实时通信,无RPC需求,或者无需要返回结果的RPC需求

线程的实现 

  • 基础使用
  1. 继承Thread类
  2. 实现Runnable接口
  3. Handler
  • 复合使用
  1. AsyncTask
  2. HandlerThread
  3. IntentService
  • 高级使用
  1. 线程池(ThreadPool)

 线程的状态和生命周期

  • 新建状态(New): 线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。
  • 就绪状态(Runnable):也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。 例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
  • 运行状态(Running):线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
  • 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种: - 等待阻塞 > 通过调用线程的wait()方法,让线程等待某工作的完成。 - 同步阻塞 > 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。 - 其他阻塞 > 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
  • 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
  • 等待状态(Waiting):处于这种状态的线程不会被分配CPU执行时间,它们要等待被其他线程显示地唤醒

线程的终止

  • 使用退出标志终止线程,代码中定义了一个退出标志exit,当exit为true时,while循环退出,exit的默认值为false.在定义exit时,使用了一个Java关键字volatile,这个关键字的目的是使exit同步,也就是说在同一时刻只能由一个线程来修改exit的值
  • 使用stop方法终止线程,thread.stop();程序中可以直接使用thread.stop()来强行终止线程,但是stop方法是很危险的,就象突然关闭计算机电源,而不是按正常程序关机一样,可能会产生不可预料的结果
  • 使用interrupt方法终止线程,使用while(!isInterrupted()){……}来判断线程是否被中断。其原理根标记位差不都,只不过interrupt是内部实现好的,而标志位是全局的,手动设置。

线程同步

  • 使用锁对象:通过使用ReentrantLock、synchronized关键字锁定对象,实现线程同步。这种方法是Java最基本的线程同步技术。
  • 使用volatile关键字:声明一个volatile变量可以保证其对所有线程的可见性,每次写入操作都会强制刷新缓存。这种方法适用于只有一个线程进行写操作,其他线程进行读操作的情况。
  • 使用wait/notify机制:wait/notify机制是一种高级、灵活的线程同步方法。wait和notify方法需要在synchronized块内部调用,通过wait释放锁,让其他线程占用该锁,notify则是唤醒等待该锁的线程。
  • 使用CountDownLatch、CyclicBarrier等辅助类:这些类提供了更高级的线程同步功能,可以实现多个线程之间的同步。其中CountDownLatch用于等待多个线程完成后再执行代码,而CyclicBarrier则用于等待所有线程准备好后再执行代码。
  • 使用Atomic类:Atomic类提供了一些原子操作,可以避免多个线程同时操作同一个变量时产生的数据竞争问题。它常用于对基本数据类型的操作,如AtomicInteger、AtomicBoolean等。
     

7,Handel原理

Handler 管理消息:

  • Handler 通过sendMessage 或 post 两种方式去发送消息
  • post 内部也是 sendMessage 形式,只不过把传入的 Runnable 参数包装成 Message 的 callback。

MessageQueue 消息队列:

  • MessageQueue其实就是维护一个 Message 链表,它会根据 Message 的 when 时间排序,延迟消息不过是延时时间再加上当前时间
  • looper是死循环,看过messageQuqe源码知道messageQuqe存消息和取消息都是一个死循环。
  • 内部实现Parcelable(安卓序列化方式,存内存)接口,实例化message建议使用obtain来实例化一个对象,因为obtain方法默认从message池中获取。

Looper 循环传输消息:

  • Looper一直调用 MessageQueue 的 next 方法取消息,然后再把消息分发出去
  • 然后通过 Message 的 target 去dispatchMessage 分发消息,最后回收消息

MessageQueue 的 next 也是一个死循环,为什么不会卡死:

  • 这里就涉及到linux系统的管道机制,往管道里面写数据来唤醒cpu,采用nativeWake方法
  • messageQuqe没有数据的时候cpu处于休眠状态(调用nativePollOnce),只有消息到来的时候才会唤醒,所以不会占用大量cpu资源。

Message如何插入和回收消息

  • 插入我们可以new,也可以用obtain方法从消息池中获取(推荐),
  • 回收时候当message从messageQuqe中取走之后,就好调用回收方法,将message置空后放入消息池中。

Message如何实现的延时加载

  • 所有的sendMessage方法都会调用sendMessageDelayed方法(只不过delayMillis=0),
  • 将消息发送到messageQuqe中的时候,会带着执行时间参数(当前时间+延迟时间),
  • 从消息队列取消息的时候先比较now和delayMillis的大小,now

8,Android系统启动流程

按下电源时:系统启动后各层的顺序执行
BootLoader层:加载引导程序
Linux Kernel层:启动 Linux 内核,加载Linux驱动
C++Framework层:启动 第一个用户进程:init 进程,启动adb进程,log进程,meadia服务
然后孵化出第一个java进程:Zygote进程(承上启下,连接java与native存在)
AndroidFramework层:通过SystemService.java启动AMS、WMS等所有的系统服务系统服务有一百多个,通过SystemServiceManager管理
App层:找到Launcher然后启动Launcher 

Gradle中subprojects 和 allprojects 的区别:

/build.gradle
buildscript {
    repositories {
        jcenter()
        google()
        maven {
            url 'https://maven.google.com/'
            name 'Google'
        }
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.3'
    }
}

allprojects {
    repositories {
        jcenter()
        google()
        maven {
            url "http://maven.xxxxxxxx/xxxxx"
        }
    }
}
  • allprojects是对所有project的配置,包括Root Project;而subprojects是对所有Child Project的配置
  • buildscript里是gradle脚本执行所需依赖,如上所示对应的是maven库和插件 
  • allprojects里是项目本身需要的依赖,比如代码中某个类是打包到maven私有库中的,那么在allprojects—>repositories中需要配置maven私有库,而不是buildscript中,不然找不到

9,事件分发

事件分发的核心概念

  1. 事件源(Event Source):事件源是指产生事件的对象,通常是用户的输入设备,如触摸屏幕、物理按键等。事件源负责将用户的操作转化为事件对象,并将其发送给应用程序进行处理。
  2. 事件分发(Event Dispatch):事件分发是指将事件对象传递给正确的View进行处理的过程。在Android中,事件分发由ViewGroup负责,它是所有View的基类。当一个事件发生时,系统会将该事件从顶层的ViewGroup开始逐级向下传递,直到找到合适的View进行处理。
  3. 事件拦截(Event Intercept):事件拦截是指在事件分发过程中,某个ViewGroup可以拦截事件的传递,阻止它传递给子View。如果一个ViewGroup拦截了事件,那么该事件将停止向下传递,不会再被其他View处理。
  4. 事件处理(Event Handling):事件处理是指在事件分发到达目标View后,该View执行相应的操作来响应事件。每个View都可以实现自己的事件处理逻辑,例如处理触摸事件、点击事件等。

事件分发的处理事件

  1. dispatchTouchEvent(MotionEvent event):用于分发触摸事件。在ViewGroup中,它会根据事件的类型和位置判断是将事件传递给子View还是进行事件拦截。如果事件被拦截,会调用onInterceptTouchEvent()方法;否则,会将事件传递给子View的dispatchTouchEvent()方法。
  2. onInterceptTouchEvent(MotionEvent event):用于拦截事件的传递。在ViewGroup中,它可以决定是否拦截事件并阻止其继续传递给子View。如果返回true,表示拦截事件;如果返回false,表示不拦截事件。
  3. onTouchEvent(MotionEvent event):用于处理触摸事件。在View中,它负责实际处理触摸事件的逻辑,例如处理触摸位置、手势判断等。如果事件被消费,会返回true;否则,返回false,以便让事件继续传递到上层的View。

事件分发机制流程

  1. 当用户进行触摸操作时,事件源(如触摸屏幕)会将用户的操作转化为一个MotionEvent对象,并将其传递给顶层的ViewGroup的dispatchTouchEvent()方法。
  2. 在dispatchTouchEvent()方法中,ViewGroup会根据事件的类型和位置判断是否要拦截事件。如果该ViewGroup返回true,表示拦截事件,不再向下传递;如果返回false,表示不拦截事件,继续传递给子View。
  3. 如果事件被拦截,会调用onInterceptTouchEvent()方法进行处理。在onInterceptTouchEvent()方法中,ViewGroup可以根据需要判断是否要拦截事件,并返回相应的结果。
  4. 如果事件没有被拦截,会继续传递给子View的dispatchTouchEvent()方法。子View可以根据自身的需求对事件进行处理,例如判断点击事件、滑动事件等。
  5. 最终,事件会传递到目标View的onTouchEvent()方法,该方法负责具体的事件处理逻辑。在onTouchEvent()方法中,可以根据事件的类型进行相应的操作,例如处理触摸位置、手势判断等

事件分发关键方法讲解

  1. dispatchTouchEvent(MotionEvent event): 该方法用于事件的分发,通常位于ViewGroup中。在此方法中,可以根据需要对事件进行拦截或传递给子View
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        // 在此处进行事件的分发和拦截判断
        boolean handled = false;
        // 拦截事件的逻辑
        if (需要拦截事件) {
            handled = true;
        } else {
            // 继续传递事件给子View
            handled = super.dispatchTouchEvent(event);
        }
        return handled;
    }
    
  2. onInterceptTouchEvent(MotionEvent event): 该方法用于拦截事件,通常位于ViewGroup中。在此方法中,可以根据需要判断是否要拦截事件,并返回相应的结果。
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        // 在此处进行事件的拦截判断
        boolean intercepted = false;
        // 拦截事件的逻辑
        if (需要拦截事件) {
            intercepted = true;
        } else {
            intercepted = super.onInterceptTouchEvent(event);
        }
        return intercepted;
    }
    
  3. onTouchEvent(MotionEvent event): 该方法用于处理具体的事件,通常位于View中。在此方法中,可以根据事件的类型进行相应的操作。
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // 在此处处理具体的事件逻辑
        boolean handled = false;
        // 处理事件的逻辑
        if (需要处理事件) {
            handled = true;
            // 执行事件处理的代码
        } else {
            handled = super.onTouchEvent(event);
        }
        return handled;
    }
    

使用举例

  1. 多个滑动控件嵌套引起的滑动冲突,比如水平方向和垂直方向的两个Recycleview嵌套,需要拦截判断是左滑还是优化,从而判断交给哪个Recycleview消费事件
  2. Scrollview和Viewpager嵌套,根上面一样处理滑动冲突,可以选择父类拦截或者子类消费
  3. 滑动吸顶效果,父控件向上滑动一段距离后,交给子类滑动。

10,动画种类

  1. 逐帧动画【Frame Animation】,即顺序播放事先准备的图片
  2. 补间动画【Tween Animation】,View的动画效果可以实现简单的平移、缩放、旋转。
  3. 属性动画【Property Animation】,补间动画增强版,支持对对象执行动画。
  4. 过渡动画【Transition Animation】,实现Activity或View过渡动画效果。包括5.0之后的MD过渡动画等代码

11,持久化方案

  • 文件存储,文件存储是Android中最基本的一种数据存储方式,它不对存储的内容进行任何的格式化处理,所有数据都是原封不动地保存到文件中的,因此比较适合用于存储一些简单的文本数据和二进制数据
  • SharedPreferences存储,SharedPreferences是使用键值对的方式来存储数据的,适合存储轻量级数据,比如登录状态,登录Token,一些配置等信息
  • SQLite数据库存储,适合复杂类型,大量数据的存储,比如依赖网络服务器数据少的应用。
  • ContentProvider,系统内容提供者,根SQLite差不多,可以自定义数据类型存储
  • 网络存储,即数据保存在远程服务器,主要服务器不销毁,数据可以永久保留。

MVC,MVP,MVVM区别

Window,Activity,View

JetPack

ViewBindding,DataBindding

网络相关

注解

反射

Binder原理

ClassLoader作用

组件化

插件化

热修复

类加载流程

垃圾回收机制

内存泄漏

性能优化

apk瘦身

算法

开源框架(LivaData,Glide,OKHttp,Retrofit,Rxjava)

你可能感兴趣的:(android)