2022年Android中高级面试框架

目录

Java

泛型

集合

ArrayList

LinkedList

HashMap

LinkedHashMap

ConcurrentHashMap

多线程并发

volatile

线程

反射

JVM

类加载

怎么判断对象是否已死?

垃圾回收机制

四大引用

泛型

集合 ——HashMap、ConcurrentHashMap源码和数据结构

多线程

反射

JVM ——类加载、内存模型、内存管理机制、垃圾回收机制

Android

四大组件和Fragment

activity

Service

BroadcastReceiver

ContentProvider

Fragment

Handler

Hander消息分发机制

HandlerThread

IdleHandler

View

View绘制流程

View加载流程

View事件分发机制

自定义View

布局、资源

动画

应用启动流程

系统启动流程

View事件分发机制

IPC通信

进程优先级与进程保活

AMS源码

WMS源码

数据结构与算法

设计模式

应用架构

性能优化

安全优化

开源框架源码

网络

数据存储

图片

Kotlin

其他


Java

泛型

  1. 泛型含义:泛型本质就是参数化类型,这种类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法,是一种语法糖(自动拆装箱、变长参数、内部类等也是语法糖)
  2. 泛型好处:让类型更加安全。a、编译时类型检查,将错误暴露在编译期,不用等到运行时(防止在运行时出现 ClassCastException);b、运行时自动类型转换,不用类型强制转换的烦琐操作;c、更加语义化(比如:List清楚知道存储的是 String 对象)和能写出更加通用化的代码(引入泛型后并未增加代码的冗余性)
  3. 类型通配符上限——List:表示集合中的所有元素都是Shape类型或者其子类
  4. 类型通配符下限——List:表示集合中的所有元素都是Circle类型或者其父类
  5. 泛型擦除:Java 泛型是不变的(Fruit 是 Apple 的父类,List 不是 List 的父类),是类型擦除的,可以看做伪泛型,无法在程序运行时获取到一个对象的具体类型。保证类型安全。

Kotlin 泛型 | 01. 基础_Swuagg的博客-CSDN博客一、泛型1.1 含义参数化类型,用尖括号这种方式表示,如、等。比如:方法的参数一般指定具体类型,如果把参数的类型也参数化,那这就是泛型本尊了。总的来说,泛型本质是参数化类型,也就是说所操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。1.2 好处让类型更加安全。编译时类型检查。将错误暴露在编译期,不用等到运行时(防止...https://blog.csdn.net/Agg_bin/article/details/119060507?spm=1001.2014.3001.5501

Kotlin 泛型 | 02. 高阶 - 型变_Swuagg的博客-CSDN博客三、泛型型变:协变、逆变与不变3.1 协变3.1.1 基本定义如果在定义的泛型类、接口和泛型方法的泛型参数前面加上 out 关键词,说明这个泛型类、接口和泛型方法是协变。也就是说,A 是 B 的子类,那么 List 也是List 的子类。class Demo { interface Producer { // 在泛型类型形参前面指定 out 修饰符 val s...https://blog.csdn.net/Agg_bin/article/details/119212350?spm=1001.2014.3001.5501

集合

List、Set、Map、Queue

ArrayList

  1. 以数组实现
  2. 自动扩容,动态数组:节约空间,但数组有容量限制,默认第一次插入元素时创建大小为10的数组,超出限制时会增加50%容量,用 System.arraycopy()复制到新的数组,因此最好能给出数组大小的预估值
  3. 效率:访问快、增删慢

LinkedList

  1. 以双向链表实现
  2. 链表无容量限制,但双向链表本身使用了更多空间,也需要额外的链表指针操作
  3. 效率:访问慢、增删快

HashMap

  1. 数组(哈希表)+链表/红黑树,基于Map接口实现、允许null键/值、非同步、不保证有序(比如插 入的顺序)、也不保证序不随时间变化
  2. 容量(Capacity)——容器大小,默认16;负载因子(Load factor)——容器填满程度的最大比例,默认0.75;当容器内容数目大于 capacity*loadfactory时就需要调整容器的大小为16的2倍
  3. 为什么扩容,并且为2的倍数:数据结构是数组需要扩容;如果是2的倍数,就可以用位运算替代取余操作,更加高效(HashMap存取时,计算index即(length- 1) & hash,使用&运算符(相比%效率更高),如果length为2的倍数,可以最大程度的确保index的均分)
  4. put函数:a、首先对key的hashCode()做hash,然后再计算index;b、如果没碰撞直接放到bucket里;c、如果碰撞了,以链表的形式存在buckets后;d、如果碰撞导致链表过长(大于等于8 ),就把链表转换成红黑树(提高遍历查询),当红黑树上的节点数量小于6个,会重新把红黑树变成单向链表数据结构;e、如果节点已经存在就替换old value(保证key的唯一性);f、如果bucket满了(超过 load factor*current capacity ),就要resize
  5. get函数:a、bucket里的第一个节点,直接命中;b、如果有冲突,则通过key.equals(k)去查找对应的entry;c、若为树,则在树中通过key.equals(k)查找,O(logn);若为链表,则在链表中通过key.equals(k)查找,O(n)
  6. hash函数:a、在get和put的过程中,计算下标时,先对hashCode进行hash操作,然后再通过hash值进一步计算下标;b、hashcode()的高16bit不变,低16bit和高16bit做了一个异或,主要是从速度、功效、质量来考虑的,这么做可以在bucket的n比较小的时候,也能保证考虑到高低bit都参与到hash的计算中, 同时不会有太大的开销;c、哈希冲突使用链表或红黑树解决
  7. HashMap为什么使用String、int作为key:a、都是final修饰的类,能够保证Hash值的不可更改性和计算准确性;b、内部已重写了equals()、hashCode()等方法,能够有效的减少Hash碰撞
  8. HashMap 与Hashtable 有什么区别:a、Hashtable线程安全,效率低,HashMap非线程安全(多个线程同时resize可能会引起死循环、put操作覆盖),效率高;b、null可以作为HashMap的key和value,不可以作为Hashtable的key;c、Hashtable初始容量11,负载因子0.75,扩容2n+1;d、Hashtable没有链表转红黑树的机制
  9. HashMap与SpareArray:key为int时考虑使用SpareArray,SpareArray使用2个数组存储key和value。好处:a、没有自动拆装箱;b、数据量不大时采用二分查找效率高,不需要开辟内存空间来额外存储外部映射,从而节省内存

LinkedHashMap

数组+双向链表/红黑树,能够保证访问顺序的HashMap,LruCache内部使用它做最近最少使用的移除

ConcurrentHashMap

  1. 解决问题:HashMap多线程并发死循环问题和Hashtable、Collections.synchronizedMap(hashMap)加锁效率低问题
  2. table初始化:不会在构造时初始化,会延缓到第一次put行为。执行第一次put操作的线程会执行Unsafe.compareAndSwapInt方 法修改sizeCtl为-1,有且只有一个线程能够修改成功,其它线程通过Thread.yield() 让出CPU时间片等待table初始化完成
  3. put函数:采用CAS+synchronized实现并发插入或更新操作
  4. table扩容:构建一个nextTable,大小为table的两倍;把table的数据复制到nextTable中
  5. get函数:a、判断table是否为空,如果为空,直接返回null;b、计算key的hash值,并获取指定table中指定位置的Node节点,通过遍历链表或则树结构找到对应的节点,返回value值

多线程并发

  1. 线程池执行流程:核心线程数是否已满(未满就创建新线程)——任务队列是否已满(未满就加入队列)——线程池是否已满(未满就创建新线程)——拒绝执行
  2. newFixedThreadPool:new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()) 只有核心线程,并且这些线程都不会被回收,不存在超时机制,采用 LinkedBlockingQueue,所以对于任务队列的大小也是没有限制的
  3. newCachedThreadPool:new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS, new SynchronousQueue()) 任务队列大小为0
  4. newScheduledThreadPool:(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,new DelayedWorkQueue()) 创建一个可定时执行或周期执行任务的线程池;scheduleAtFixedRate——延迟一定时间后,以间隔period时间的频率周期性地执行任务,即:上一个任务开始执行到下一个任务开始执行的间隔;scheduleWithFixedDelay——时间间隔是以上一个任务执行结束到下一个任务开始执行的间隔
  5. newSingleThreadExecutor:new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue())
  6. 线程池使用:CPU密集型任务——线程池中线程个数应尽量少,如配置N+1个线程的线程池;IO密集型任务——IO操作速度远低于CPU速度,CPU绝大多数时间处于空闲状态,那么线程池可以配置尽量多些的线程,以提高CPU利用率,如2*N。和混合型任务——拆分成CPU密集和IO密集
  7. 线程中断:通过变量中断线程;通过抛出InterruptedException来中断线程
  8. 线程同步:a、变量同步——volatile主存和线程缓存进行同步,synchronized资源只能有一个线程使用,AtomicInteger同步变量(可以保证原子性);b、代码块同步——乐观锁ReentrantLock,悲观锁synchronzed、Lock
  9. 线程顺序执行:

Java线程池的使用_Swuagg的博客-CSDN博客线程池的产生(1)线程池的产生来源于new Thread的弊端在程序中创建一个线程,通常如下形式:new Thread(new Runnable() { @Override public void run() {}}).start();但这种方式的弊端很明显:每次都new Thread新建对象,性能差。线程缺乏统一管理,可能无限制新建线程,相互之间竞争,...https://blog.csdn.net/Agg_bin/article/details/93889210?spm=1001.2014.3001.5501

volatile

  1. 保证可见性:不同线程对这个变量进行操作时的可见性,volatile会强制将修改的值立即写入主存。即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的
  2. 禁止指令进行重排序:保证有序性
  3. volatile不能确保原子性:volatile也无法保证对变量的任何操作都是原子性的,而且自增操作不是原子性操作,可以通过lock或synchronized进行加锁,来保证操作的原子性,也可以通过AtomicInteger。

线程

  1. Thread类中的join方法的主要作用就是同步,它可以使得线程之间的并行执行变为串行执行。
  • join():在A线程中调用B线程的join()方法时,表示只有当B线程执行完毕时,A线程才能继续执行。 join() == join(0)
  • join():在A线程中调用B线程的join(10),则表示A线程会等待B线程执行10毫秒,10毫秒过后,A、B线程并行执行。
  • 原理:join方法是通过调用线程的wait方法来达到同步的目的的。如:A线程中调用了B线程的join方法,则相当于在A线程中调用了B线程的wait方法,当B线程执行完(或者到达等待时间),B线程会自动调用自身的notifyAll方法唤醒A线程,从而达到同步的目的。
  1. 一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。
  • Thread.interrupt 的作用其实也不是中断线程,而是「通知线程应该中断」

        if (worker != null) {

            worker.interrupt();

            try {

                worker.join();

            } catch (InterruptedException e) {

                e.printStackTrace();

                worker.interrupt();

            }         

反射

  1. 反射API:getMethods与getDeclaredMethods——获取所有方法与获取所有public方法
  2. 动态代理使用反射

JVM

类加载

  1. 定义:把class文件加载到内存,并对其进行验证、准备、解析和初始化,最终形成可被虚拟机直接使用的Java类型
  2. 类的生命周期:加载,验证,准备,解析,初始化,使用和卸载
  3. 触发类加载的条件:new、static字段,static方法,反射
  4. 类加载过程:加载(在内存中生成对象),验证(Class文件格式、程序语义是否合法),准备(在方法区为类变量分配内存并设置初始值),解析(虚拟机将常量池内的符号引用替换为直接引用的过程,动态和静态解析),初始化(类构造器(不是类构造函数)、static代码块、类变量赋值)
  5. 双亲委派模型:自定义类加载器——Application ClassLoader——Extension ClassLoader——Bootstrap ClassLoader

怎么判断对象是否已死?

  1. 引用计数法:给对象添加一个引用计数器,当多一个引用,计数器就加1;引用失效,则计数器减1;计数器为0,表示对象不再使用。JVM未使用这种方式,因为很难解决对象间互循环引用的问题
  2. 可达性分析算法:GC Roots——Java栈对象、Native栈对象、方法区static对象和final对象
  3. 与引用相关:4大引用。强引用:OOM也不会回收对象;软引用:内存不足时(OOM之前)回收,如:图片缓存;弱引用:下一次GC时回收,防止内存泄漏;虚引用:随时可能被回收,为一个对象设置虚引用的唯一目的就是能在这个对象被GC回收时能得到一个系统通知

Java的引用——强引用 、软引用、弱引用、虚引用_Swuagg的博客-CSDN博客先知Java中除了八大基本数据类型,其他都是引用类型,指向各种不同的对象。java.lang.ref包下提供了引用对象类(共包括五个类:PhantomReference、Reference、ReferenceQueue、SoftReference、WeakReference),支持在某种程度上与垃圾回收器GC(垃圾回收机制回收某个对象主要是看是否有引用指向这个对象)之间的交互。对象分类...https://blog.csdn.net/Agg_bin/article/details/93502712?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165078175816782388062032%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165078175816782388062032&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-93502712.nonecase&utm_term=%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6&spm=1018.2226.3001.4450

垃圾回收机制

  1. 垃圾收集算法:标记-清除算法(效率不高、内存碎片)、复制算法(新生代,内存回收、分配效率高,但内存缩小一半)、标记-整理算法(老年代,对象存活率高时复制算法效率低)、分代收集算法(新生代、老年代)
  2. 新生代分为:Eden区(新建对象)和From Survivor区、To Survivor区
  3. Minor GC和Full/Major GC区别:新生代和老年代垃圾收集动作,Major GC会有Minor GC,且速度慢10倍以上

Android

四大组件和Fragment

activity

  1. 生命周期:常规7个生命周期+特殊情况下生命周期
  2. 启动模式:4个
  3. 启动方式:隐式与显示
  4. 启动过程(流程):跨进程启动;进程内启动
  5. Activity卡顿原因:频繁GC(内存泄漏、加载大数据、大图片)、UI绘制(UI线程耗时操作、过度绘制)
  6. 其他相关知识:传递数据、Activity任务栈

启动Activity的方式_Swuagg的博客-CSDN博客显示启动构造方法传入Component,最常用的方式setComponent(componentName)方法setClass/setClassName方法隐式启动通过在AndroidManifest文件中设置action、data、category,让系统来筛选出合适的Activity。action的匹配规则Intent-filter action可以设置多条intent中的action只要与https://blog.csdn.net/Agg_bin/article/details/90486145?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165078201416782350932823%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165078201416782350932823&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-90486145.nonecase&utm_term=Activity&spm=1018.2226.3001.4450

Service

  1. startService与bindService的区别:生命周期回调不同
  2. 启动流程
  3. Service与Activity通信:binder中注册Service回调,或广播发送消息
  4. Activity与Service通信:binder
  5. IntentService与HandlerThread源码

参考:Android四大组件之Service_Swuagg的博客-CSDN博客https://blog.csdn.net/Agg_bin/article/details/94573631?spm=1001.2014.3001.5502

BroadcastReceiver

  1. 广播分类:普通广播、系统广播、有序广播、本地广播
  2. 使用场景:App内部通信(多线程通信);不同App通信;系统与App通信
  3. 本地广播和全局广播区别:本地广播安全、高效、应用内使用,且只能动态注册;Handler实现与Binder实现
  4. 实现原理:观察者模式(观察者、被观察者、AMS消息中心)
  5. 静态广播无法接收到:include、exclude stop

ContentProvider

  1. 优点:提供一种进程间数据共享的方式;封装和解耦底层数据存储方式,使得数据操作变得简单、高效和安全

Fragment

  1. 生命周期:7+5
  2. Fragment与Activity、其他Fragment通信方式:接口回调、EventBus、共用ViewModel、Bundle的setArgs(Fragment重建时Bundle数据还在)

Handler

Hander消息分发机制

  1. handler实现原理
  2. Looper:ThreadLocal、ThreadLocalMap
  3. Handler:post和sendMessage区别
  4. Message:对象创建方式
  5. MessageQueue:单链表,enqueueMessage,next
  6. 同步屏障:为了让异步消息优先执行。MessageQueue.next判断 message.target ==null为屏障消息。另外,ViewRootImpl.scheduleTraversals中使用了同步屏障
  7. 子线程更新UI:每次更新UI时,ViewRootImpl.checkThread()检验线程是否是View的创建线程,onResume之前VIewRootImpl未被创建
  8. 子线程中是否可以用MainLooper去创建Handler,Looper和Handler 是否一定处于一个线程?

HandlerThread

  1. 问:项目中经常要执行耗时操作, 如果经常要开启线程,接着又销毁线程,这无疑是很消耗性能的,怎么解决?——线程池或HandlerThread
  2. HanderThread好处:开启一个线程起到多个线程的作用,用来执行多个耗时操作,而不需要多次开启线程
  3. HanderThread劣势:串行执行——此时可用线程池解决
  4. run方法运行结束后,线程依旧存活,因为MessageQueue阻塞等待下一消息到来

IdleHandler

  1. 触发时机:Looper循环出现空闲的时候,采取执行任务的一种机制
  2. 使用场景:启动优化

View

View绘制流程

  1. addView流程:ActivityThread.handleResumeActivity——WindowManagerImpl.addView——WindowMangerGlobel.addView——ViewRootImpl.setView——ViewRootImpl.scheduleTraversals——ViewRootImpl.doTraveral——ViewRootImpl.performTraversals
  2. performTraversals流程:performMeasure——DecorView.measure——DecorView.onMeasure——View.measure performLayout...
  3. Measure、Layout、Draw流程
  4. MeasureSpec:是View类的一个静态内部类,用来说明应该如何测量这个View,32位int型,前2位是测量模式SpecMode,低30位表示某种测量模式下的 规格大小SpecSize
  5. View.post与Handler.post区别:performTraversals时执行RunQueue
  6. SurfaceView、TextureView、SurfaceTexture区别:SurfaceView使用双缓冲机制,有自己的Surface,在独立线程绘制,在Android7.0之前不能平移、缩放;TextureView需要硬件加速,和View一样使用;SurfaceTexture对图像流采用OpenGL处理
  7. invalidate() 和 postInvalidate() 方法的区别:onDraw,非UI线程调用
  8. getWidth()方法和getMeasureWidth()区别:onLayout之后,onMeasure之后

View加载流程

setContentView——decorView——LayoutInflate——XML的pull方式IO操作——反射创建

View事件分发机制

  1. dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent——Activity、ViewGroup、View
  2. onTouchListener.onTouch、onTouchEvent、onClickListener关系:onTouch优先级最高;onClick事件是在 onTouchEvent的MotionEvent.ACTION_UP事件通过performClick() 触发的
  3. 事件是先到DecorView还是先到Window:DecorView -> Activity -> PhoneWindow -> DecorView
  4. 同时对父 View 和子 View 设置点击方法,优先响应哪个:优先响应子View,父 view 要优先响应事件,须调用 onInterceptTouchEvent ,直接交给父 view 的 onTouchEvent 处理
  5. 解决滑动冲突:外部拦截法(推荐)——父View根据需要对事件进行拦截onInterceptTouchEvent;内部拦截法——父View不拦截任何事件,子View根据需要决定是自己消费事件还是给父View处理,使用requestDisallowInterceptTouchEvent。2种方式父View的ACTION_DOWN 都返回false,因为一个事件序列只能被一个View拦截且消耗

自定义View

步骤:onMeasure,可以不重写,不重写的话就要在外面指定宽高,建议重写; onDraw,看情况重写,如果需要画东西就要重写; onTouchEvent,也是看情况,如果要做能跟手指交互的View,就重写

注意事项:如果有自定义布局属性的,在构造方法中取得属性后应及时调用recycle方法回收资源; onDraw和onTouchEvent方法中都应尽量避免创建对象,过多操作可能会造成卡顿

考虑机型适配:合理使用warp_content,match_parent。 尽可能地使用RelativeLayout。 针对不同的机型,使用不同的布局文件放在对应的目录下,android会自动匹配。 尽量使用点9图片。 使用与密度无关的像素单位dp,sp。 引入 android的百分比布局。 切图的时候切大分辨率的图,应用到布局当中,在小分辨率的手机上也会有很好的显示效果

Android如何实现阴影_Swuagg的博客-CSDN博客_android ui阴影我们知道,Google 在 2014 年 I/O大会上发布的一种新的设计规范——Material Design,这种设计规范给 Android UI 设计带来了很多的变化。比如,更加强调真实性、有立体感,由此引发的一系列针对阴影的UI设计。我相信,很多专注业务逻辑的Android程序员,拿着这样的一个UI效果,往往一头雾水。所以,我仅在此抛砖引玉,希望打开他们的思路,更好的满足UI的要求。好的...https://blog.csdn.net/Agg_bin/article/details/104855255?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165078222316781435413737%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165078222316781435413737&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-3-104855255.nonecase&utm_term=%E8%87%AA%E5%AE%9A%E4%B9%89View&spm=1018.2226.3001.4450Android实现文本过长时右边渐隐,聚焦时跑马灯效果_Swuagg的博客-CSDN博客写在前面:原创不易,请不要吝啬你的大拇指,点个赞再走呗。然而贴代码很容易,但那不一定有帮助。本文试图从问题点出发,逐步分解,直到实现最终效果。在Android中,我们知道对于控件TextView,在布局时有时候需要单行文本,但可能会存在文本内容的宽度超出了布局宽度,这个时候就需要我们做一定的兼容,不然可能会显示出不太优雅的UI效果。针对于此情况——文本内容的宽度超出了布局宽度,你应该...https://blog.csdn.net/Agg_bin/article/details/104967214?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165078222316781435413737%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165078222316781435413737&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-4-104967214.nonecase&utm_term=%E8%87%AA%E5%AE%9A%E4%B9%89View&spm=1018.2226.3001.4450Kotlin 实现 Android 系统悬浮窗_Swuagg的博客-CSDN博客_kotlin 悬浮窗Android 弹窗浅谈我们知道 Android 弹窗中,有一类弹窗会在应用之外也显示,这是因为他被申明成了系统弹窗,除此之外还有2类弹窗分别是:子弹窗与应用弹窗。应用弹窗:就是我们常规使用的 Dialog 之类弹窗,依赖于应用的 Activity;子弹窗:依赖于父窗口,比如 PopupWindow;系统弹窗:比如状态栏、Toast等,本文所讲的系统悬浮窗就是系统弹窗。系统悬浮窗具体实现权限申请https://blog.csdn.net/Agg_bin/article/details/121913647?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165078222316781435413737%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165078222316781435413737&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-5-121913647.nonecase&utm_term=%E8%87%AA%E5%AE%9A%E4%B9%89View&spm=1018.2226.3001.4450

动画

关于Json动画与帧动画的对比_Swuagg的博客-CSDN博客关于Json动画与帧动画的异同点,本文主要从流畅度、CPU、内存和文件大小,这4个方面进行的比较。1、运行时的状态图:Json动画运行时,CPU和内存动态图如下:帧动画运行时,CPU和内存动态图如下:2、对比分析(1)从动态图可以分析出如下信息:Json动画更流畅;但是一直占用CPU资源——12%左右;内存一直上升,但差不多上升2M左右会被释放掉;帧动画不太流畅;基本不占用CPU资源——1%左右;内存稳定不变2M左右;(2)文件大小对比:Json动画包括:Json资源文件大小6.96Khttps://blog.csdn.net/Agg_bin/article/details/107976298?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165078232316782246464066%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165078232316782246464066&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-107976298.nonecase&utm_term=%E5%8A%A8%E7%94%BB&spm=1018.2226.3001.4450

应用启动流程

Android源码之App启动_Swuagg的博客-CSDN博客下面以应用桌面Launcher启动App的MainActivity来举例:App启动概述首先,MainActivity是由Launcher组件来启动的,而Launcher又是通过Activity管理服务ActivityManagerService(AMS)来启动MainActivity组件的。由于MainActivity组件和Launcher组件,以及AMS是三个不同的进程, 所以这三个不...https://blog.csdn.net/Agg_bin/article/details/94456778?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165078201416782350932823%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165078201416782350932823&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-24-94456778.nonecase&utm_term=Activity&spm=1018.2226.3001.4450

系统启动流程

Android源码之系统启动_Swuagg的博客-CSDN博客Android系统启动概述上电开机,CPU获取第一条指令,执行BootLoader引导装载程序;BootLoader加载Linux内核并启动内核,加载各种驱动和数据结构;内核启动第一个用户进程,也就是内核空间到用户空间启动的第一个进程——init进程(c代码);init进程读取配置文件init.rc,配置文件中会启动一个Zygote孵化器进程App_main.cpp(由c进入c++...https://blog.csdn.net/Agg_bin/article/details/90314007?spm=1001.2014.3001.5501

Binder

IPC通信有哪些

  1. 进程和线程区别:进程是系统进行资源分配和调度的一个独立单位;线程是CPU调度和分派的基本单位,不拥有系统资源,共享进程数据。
  2. IPC通信方式、使用场景和优缺点:Intent(4大组件,只能传输Bundle支持的数据类型)、文件共享(无高并发的简单数据共享)、AIDL(最常用,支持一对多并发实时通信)、Messenger(AIDL简化版,只支持一对多串行实时通信)、ContentProvider(进程间大量数据共享,受约束的AIDL,主要对外提供数据的CRUD)、Socket(网络通信,只能传输原始的字节流)
  3. Android为何不使用Linux IPC:管道、消息队列、共享内存、套接字(Socket)。效率、稳定性和安全性。内存拷贝少,效率高:管道、消息队列和套接字需要2次拷贝,Binder1次拷贝(接收方缓存区与内核缓存区是映射到同一块物理地址),共享内存无拷贝。稳定性:共享内存需要处理并发同步问题,容易出现死锁和资源竞争,稳定性较差。Binder基于C/S架构 ,Server端与Client端相对独立,稳定性较 好。Socket也是基于C/S架构,但传输效率低,开销大。安全性:传统Linux IPC的接收方无法获得对方进程可靠的UID/PID,从而无法鉴别对方身份;而Binder机制为每个进程分配了UID/PID,且在Binder通信时会根据UID/PID进行有效性检测
  4. 一个应用为啥使用多进程:突破进程内存限制、保持功能稳定性、隔离风险避免主进程崩溃

Android进程间通信方式与线程间通信方式的列举_Swuagg的博客-CSDN博客_android 线程间通信本文只是列举了Android进程间通信和Android线程间通信的常见方式,还请见谅,具体实现或原理可参见其他博客。Android IPCIntent方式Bundle通信Broadcast方式文件共享的方式AIDL方式(基于Binder)Messenger方式(AIDL+Handler Message的方式)ContentProvider的方式Socket(网络)...https://blog.csdn.net/Agg_bin/article/details/103212525?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165078238116782184657996%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165078238116782184657996&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-103212525.nonecase&utm_term=%E8%BF%9B%E7%A8%8B%E9%80%9A%E4%BF%A1&spm=1018.2226.3001.4450

Binder是什么

  1. 机制:是一种进程间通信机制
  2. 驱动:是一个虚拟物理设备驱动
  3. 应用层:是一个能发起通信的Java类

Binder原理

Binder驱动、mmap、一次拷贝(come_from_user)+物理地址映射

  1. 创建数据接收缓存区:Binder 驱动使用 mmap() 在内核空间创建(1M-8K)数据接收缓存区
  2. 实现地址映射关系:在内核空间开辟一块内核缓存区,实现 内核缓存区 和 接收进程用户空间地址 同时映射到同一个 数据接收缓存区 中
  3. 发送数据到内核缓存区:发送进程,通过系统调用 copy_from_user() 将数据 copy 到内核空间的内核缓存区(数据拷贝1次)
  4. 接收进程通过内存映射接收到数据:由于内核缓存区和接收进程的用户空间存在内存映射(通过数据接收缓存区),因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信

发送进程——come_from_user到内核缓存区----接收缓存区----接收进程用户空间

AIDL通信

AIDL会生成一个服务端对象的代理类,客户端可以通过它实现间接调用服务端对象的方法,AIDL简化Binder的使用,轻松地实现IPC进程间通信机制。

  1. AIDL使用步骤:a、书写 AIDL——创建要操作的实体类,实现 Parcelable 接口,以便序列化/反序列化;新建 aidl 文件夹,在其中创建接口 aidl 文件以及实体类的映射 aidl 文件;Make project ,生成 Binder 的 Java 文件。b、编写服务端——创建 Service,在Service中创建生成的Stub实例,实现接口定义的方法;在 onBind() 中返回Binder实例。c、编写客户端——实现 ServiceConnection 接口,在其中通过asInterface拿到 AIDL 类;bindService();调用 AIDL 类中定义好的操作请求。
  2. bindService流程:a、通过Binder通知AMS去启动Service;b、AMS创建ServiceRecord,并利用ApplicationThreadProxy回调,通知APP新建并启动 Service;c、AMS把Service启动起来后,让Service返回一个Binder对象给自己,以便传递给Client;d、AMS把从Service处得到这个Binder对象通过onServiceConnected传给Activity;e、在onServiceConnected回调中,使用Stub的asInterface函数将Binder转换为代理Proxy,完成业务代理的转换,之后就能利用Proxy进行通信。
  3. 如何优化多模块都使用AIDL的情况:Binder连接池
  4. AIDL支持数据结构:八大基本数据类型、String、CharSequence、List(接收方必须是ArrayList)、Map(接收方必须是HashMap)、实现Parcelable的类、AIDL类
  5. AIDL关键类、方法:a、AIDL接口——编译完生成的接口继承IInterface;b、Stub——服务实体,Binder的实现类,服务端一般会实例化一个Binder对象,在服务端onBind中绑定, 客户 端asInterface获取到Stub。 这个类在编译aidl文件后自动生成,它继承自Binder,表示它是一个Binder本地对象; 它是一个抽象类,实现了IInterface接口,表明它的子类需要实现Server将要提供的具体能力(即aidl文件中声明的方 法);c、Stub.Proxy——服务的代理,客户端asInterface获取到Stub.Proxy。 它实现了IInterface接口,说明它是 Binder通信过程的一部分;它实现了aidl中声明的方法,但最终还是交由其中的mRemote成员来处理,说明它是一 个代理对象,mRemote成员实际上就是BinderProxy;d、asInterface——客户端在ServiceConnection通过Person.Stub.asInterface(IBinder), 会根据是同一进行通信, 还是不同进程通信,返回Stub()实体,或者Stub.Proxy()代理对象;e、transact——运行在客户端,当客户端发起远程请求时,内部会把信息包装好,通过transact()向服务端发送。并将当前线程挂起, Binder驱动完成一系列的操作唤醒 Server 进程 ,调用 Server 进程本地对象的 onTransact()来调用相关函数,直到远程请求返回,当前线程再继续执行;f、onTransact——运行在服务端的Binder线程池中,当客户端发起跨进程请求时, onTransact()根据 Client传来的 code 调用相关函数 。调用完成后把数据写入Parcel,通过reply发送给Client。 驱动唤醒 Client 进程里刚刚挂起的线程并将结果返回。

性能优化

启动优化

  1. 统计启动时间:a、adb shell am start –W 包/类名,TotalTime为应用自身启动耗时;b、Logcat过滤Displayed;c、Application的attachBaseContext到Activity的onWindowFocusChanged(true)的时间;d、视频采集卡
  2. 启动耗时分析:logcat打印代码执行段的时间差
  3. 启动优化:a、设置windowBackground;b、子线程异步初始化;c、使用有向无环图来启动有依赖的初始化任务;d、延迟执行任务(handler delay以及使用MessageQueue.IdleHandler);e、优化首页布局(减少层级嵌套、使用merge、ViewStub、include、动态布局);f、使用本地缓存先加载

Android性能优化之Activity启动优化_Swuagg的博客-CSDN博客_activity启动优化打开一个app的时候速度比较慢,等一会才能看到UI,有很多种原因,下面是我根据这些个情况做出的多种优化,记录一下。1、采用动态布局: 先是优化了布局,减少层级嵌套,使用merge优化等等。但发现加载xml布局还是慢了点,于是改为动态布局,布局的时间减少了好几倍。2、利用MessageQueue.IdleHandler()回调 按照activity的生命周...https://blog.csdn.net/Agg_bin/article/details/88396449?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165078201416782350932823%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165078201416782350932823&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-2-88396449.nonecase&utm_term=Activity&spm=1018.2226.3001.4450

内存优化

内存泄漏

  1. 什么是内存泄漏:长生命周期的对象持有短生命周期对象的引用,短生命周期对象不再被使用后不能被GC
  2. 内存泄漏原因:a、非静态内部类隐式持有外部类的引用(Handler、Thread);b、listener、callback注册后未移除;c、使用Activity的context而不是Application的context;d、单例模式持有Activity引用未释放;e、资源(数据库、IO、Bitmap)使用后未关闭;f、static集合类持有的对象未被释放
  3. 内存溢出原因:a、手机内存不足;b、app进程内存达到上限(只有两种原因——申请内存速度超出GC释放内存速度;出现内存泄漏)
  4. LeakCanary监测内存泄漏原理:a、Activity调用onDestory后将其放进WeakReference中;b、弱引用关联到一个引用队列ReferenceQueue(弱引用关联的对象被回收后,就会将弱引用加入到ReferenceQueue里);c、延时5秒检测ReferenceQueue中是否存在当前弱引用;d、如果检测不到说明可能发生泄露,通过gcTrigger.runGc()手动调用GC,如果还是没有则断定发生内存泄漏...然后找出内存泄漏对象到GC roots的最短路径,输出分析结果展示到页面

Android 内存泄漏_Swuagg的博客-CSDN博客我想大家在平时的工作中,或多或少都遇到过或听到过内存泄漏,那今天我希望和大家一起讨论一下关于内存泄漏, 什么是内存泄漏?造成内存泄漏的原因?如何解决内存泄漏?以及如何避免内存泄漏等等。。。主要从三个方面进行讨论:Java类执行的过程;Java中的内存泄漏;Android内存泄露及其解决。一、Java类执行的过程https://blog.csdn.net/Agg_bin/article/details/85303598?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165078175816782388062032%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165078175816782388062032&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-3-85303598.nonecase&utm_term=%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6&spm=1018.2226.3001.4450

图片优化

  • 如何加载Bitmap防止OOM:a、对图片进行压缩(图片质量压缩、RGB_565法、采样率压缩,Matrix比例压缩);b、高分辨率图片放入对应文件夹(多套图片资源,使用decodeResource方法会根据屏幕dpi适配);c、内存复用(三级缓存——LruCache & DiskLruCache、采用软引用);d、及时回收(bitmap.recycler、采用软引用)
  • Bitmap内存占用计算:加载res/raw资源图片 = 宽*高*像素点大小*缩放系数(dpi);加载SD卡图片 = 宽*高*像素点大小
  • Bitmap压缩方式:a、质量压缩——bitmap.compress(Bitmap.CompressFormat.JPEG, options, ByteArrayOutputStream); 循环压缩,options从100往下降,知道内存大小合适位置 ;b、RGB_565法——改变一个像素所占的内存,默认是使用ARGB8888配置来处理色彩BItmapFactory.Options设置色彩模式inPreferredConfig为Bitmap.Config.RGB_565,内存减少一半;c、尺寸(采样率)压缩——BItmapFactory.Options设置inJustDecodeBounds只加载Bitmap边界宽高信息,设置inSampleSize的值(int类型:取2的次方数)后,假如设为n,则宽和高都为原来的1/n,宽高都减少;d、Matrix比例压缩——根据图片的缩放比例进行等比大小的缩小宽高,图片文件尺寸变小,相比采样率压缩,可以精确地指定图片的缩放大小
  • LruCache:a、核心思想——当缓存满时,会优先淘汰那些最近最少使用的缓存对象;b、主要算法原理——把最近使用的对象用强引用存储在 LinkedHashMap(数组+双向链表) 中,当缓存满时,把最近最少使用的对象从内存中移除,提供了get(会更新该元素到队头)和put(将该元素添加到队头,并通过trimToSize()方法判断缓存是否已满,如果满了就从队尾开始删除,直到缓存大小小于缓存最大值)方法来完成缓存的获取和添加操作。c、LruCache是个泛型类,采用 LRU算法的缓存有两种:LrhCache内存缓存和DisLruCache硬盘缓存;d、使用LinkedHashMap中双向链表的访问顺序(另一个是插入顺序)特性实现LRU,即最近访问的最后输出
  • 图片三级缓存
  • 加载大图
  • 加载大量图片

内存抖动

  1. 减少不合理的对象创建;ondraw、getView中对象的创建尽量进行复用;避免在循环中不断创建局部变量。

布局优化(绘制优化)

布局分析工具:a、adb shell dumpsys activity top,查看最上层activity信息,找到layout信息;b、使用Layout Inspector

从布局绘制流程出发进行优化:

  1. 减少View树层级:a、复杂布局使用ConstraintLayout;b、不嵌套使用RelativeLayout;c、不在嵌套LinearLayout中使用weight;d、使用merge减少一个根ViewGroup层级;e、ViewStub 延迟化加载标签——当布局整体被inflater,ViewStub也会被解析但是其内存占用非常低,它在使用前是作为占位符存在,对ViewStub的inflater操作只能进行一次,也就是只能被替换1次
  2. 避免过度绘制:a、去掉多余的background;b、减少复杂shape的使用;c、避免层级叠加;d、自定义View使用clipRect屏蔽被遮盖View绘制
  3. 视图与数据绑定耗时:由于网络请求或者复杂数据处理逻辑耗时导致与视图绑定不及时,这里可以从优化数据处理的维度来解决

卡顿优化(帧率优化)

  1. 帧率检测:a、看大概卡顿程度——adb shell dumpsys gfxinfo 包名;b、看准确值——使用python脚本抓取systrace.html,平均帧率(fps/s) = frames/tatol_time(ms/1000转成s)
  2. 卡顿优化:开发app的性能目标就是保持60fps,Android 系统每隔16ms发出VSYNC信号,卡顿优化就是监控和分析由于哪些因素的影响导致绘制渲染任务没有在一个vsync的时间内完成。卡顿产生的原因是错综复杂的,它涉及到代码、内存、绘制、IO、CPU等等
  3. 卡顿优化分析:查看systemtrace.html。布局耗时查看UI线程;过渡绘制结合渲染线程和UI线程;调度延迟在对应卡顿的帧上查看CPU core占用情况,拉取一段CPU占用信息,按耗时排序来分析

CPU优化-暂无

网络优化-暂无

安全优化

PIA(Privacy Impact Assessment)隐私影响评估

SIA —— Fireline火线扫描、娜迦扫描、Converity

进程优先级与进程保活

AMS源码

WMS源码


数据结构与算法

线性表(数组、链表、队列和栈)

二叉树、红黑树

排序算法

排序算法Java实现_Swuagg的博客-CSDN博客冒泡排序特点:运行非常慢,但它的概念是最简单的。基本流程:每一轮从头开始两两比较,将较大的项放在较小项的右边,这样每轮下来保证该轮最大的数在最右边。算法实现:(交换出最大,放在后面)private static void bubbleSort(int[] data) { System.out.println(Arrays.toString(data)); boole...https://blog.csdn.net/Agg_bin/article/details/96307251?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165078243816780357247736%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165078243816780357247736&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-96307251.nonecase&utm_term=%E7%AE%97%E6%B3%95&spm=1018.2226.3001.4450

查找算法

动态规划算法

贪心算法

LeetCode算法题

设计模式

创建型5个

行为型11个

结构型7个

简单理解-Head First 设计模式_Swuagg的博客-CSDN博客我们知道四人组的《设计模式》共介绍了23种,并且将其分成3类,分别是:创建型、结构型、行为型,如下所示: - **创建型**:**5个**。工厂方法模式、抽象工厂模式、Builder模式(Retrofit)、单例模式(类初始化)、原型模式 - **结构型**:**7个**。适配器模式、装饰模式(File操作)、代理模式(静态代理——代理对象内部操作委托对象、动态代理——代理对象里面不指定特定的委托对象,更加灵活)、组合模式、桥接模式、外观模式、享元模式 - **行为型**:**11个**。观察者模式https://blog.csdn.net/Agg_bin/article/details/110940981?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165078248516782350919040%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165078248516782350919040&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-3-110940981.nonecase&utm_term=%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F&spm=1018.2226.3001.4450 Code Smell_Swuagg的博客-CSDN博客——如果尿布臭了,就换掉它。《重构:改善既有代码的设计》,在该书的第3章“代码的坏味道”中,收录了Kent Beck关于重构时机的理解——Code Smell。Code Smell的22种代码坏味道如下:Duplicated Code。a、同一个class内的两个函数含有相同的表达式。——需要Extract Method,提炼出重复代码,然后让两个地点都调用被提炼出来的那一段代码...https://blog.csdn.net/Agg_bin/article/details/92840307?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165078248516782350919040%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165078248516782350919040&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-16-92840307.nonecase&utm_term=%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F&spm=1018.2226.3001.4450六大设计原则SOLID_Swuagg的博客-CSDN博客在软件开发中,前人总结了六大设计原则如下:Single Responsibility Principle:不能有多个导致类变更的原因。一个类只负责一个职责。这个原则不仅仅适用于类,对于接口和方法也适用,而且接口和方法的单一职责更容易实现。——单一职责原则Liskov Substitution Principle:就是只要父类出现的地方子类就可以出现,且替换成子类也不会出现任何错误或者异...https://blog.csdn.net/Agg_bin/article/details/92981605?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165078248516782350919040%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165078248516782350919040&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-13-92981605.nonecase&utm_term=%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F&spm=1018.2226.3001.4450单例模式之Java版本以及5种写法_Swuagg的博客-CSDN博客/** * 为什么使用单例? * 一个类专门提供一些公共功能供别人调用,而本身并不会处理业务逻辑。那么创建多个实例,会消耗内存,造成不必要的开销。此时需要单例。 * 单例是什么? * 让整个生命周期内只有一个实例。 *

* 一般单例模式有如下五种写法:懒汉式,双重校验锁,静态内部类,饿汉式和枚举。 * 懒汉式:非线程安全 * 双重校验锁:线程安全 * 静态内...https://blog.csdn.net/Agg_bin/article/details/93201546?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165078248516782350919040%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165078248516782350919040&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-9-93201546.nonecase&utm_term=%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F&spm=1018.2226.3001.4450

创建型设计模式分享_Swuagg的博客-CSDN博客闲说设计模式信耶稣的人都要读圣经,而信OO的人都要读四人组的《设计模式》,这就是OO的圣经。OO概念是我们的基础,OO原则 是我们的目标,而设计模式是我们的做法。把模式装进脑子里,然后在你的设计和已有的应用中,寻找何处可以使用它们。设计模式定义1、模式:在某情境下,针对某问题的某种解决方案。情境:就是应用某个模式的情况。这应该是会不断出现的情况。问题:就是你想在某情境下达成的目标,但也可以是某情境下的约束。解决方案:就是你所追求的,一个通用的设计,用来解决约束、达到目标。2、STAhttps://blog.csdn.net/Agg_bin/article/details/111320086?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165078248516782350919040%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165078248516782350919040&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-2-111320086.nonecase&utm_term=%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F&spm=1018.2226.3001.4450原型设计模式之Java版本_Swuagg的博客-CSDN博客定义用原型模式指定创建对象的种类,并通过复制这些原型创建新的对象。原型设计模式,是23种设计模式的创建型模式,简单来说,就是对象的克隆。将要被克隆的对象,我们称之为原型。使用场景类初始化或new一个新对象时,需要消耗非常多的资源,因为通过原型复制的方式不会执行构造方法,避免了初始化占有的时间和空间。一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式复制多个对象供调用者使用,即保护性拷贝。UML类图https://blog.csdn.net/Agg_bin/article/details/107027114?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165078248516782350919040%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165078248516782350919040&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-107027114.nonecase&utm_term=%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F&spm=1018.2226.3001.4450Kotlin实现访问者设计模式_Swuagg的博客-CSDN博客访问者设计模式,是GoF阐述的23中常见设计模式中,行为型设计模式的一种。访问者设计模式,表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变个元素类的前提下定义作用于这些元素的新操作。使用该设计模式可以让我们能够访问到各个元素,于是我们可以将相关方法的实现放在类的外部,这样就可以使得类不再臃肿。下面是具体的代码实现:sealed class Target { abstr...https://blog.csdn.net/Agg_bin/article/details/104330847?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165078248516782350919040%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165078248516782350919040&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-4-104330847.nonecase&utm_term=%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F&spm=1018.2226.3001.4450Kotlin实现策略和模版方法设计模式_Swuagg的博客-CSDN博客策略设计模式做的事情就是,将不同的行为策略进行独立封装,与类在逻辑上解耦。根据不同的上下文,切换选择不同的策略,然后用类对象进行调用。有如下例子,一个游泳运动员会蛙泳、仰泳、自由泳多种游泳姿势,我们可以将游泳这个行为封装成接口,根据不同的场景我们可以调用不同的游泳姿势,如下代码所示:package com.agg.kotlinapplicationinterface SwimStra...https://blog.csdn.net/Agg_bin/article/details/104343819?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165078248516782350919040%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165078248516782350919040&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-5-104343819.nonecase&utm_term=%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F&spm=1018.2226.3001.4450Kotlin实现工厂方法和抽象工厂设计模式_Swuagg的博客-CSDN博客_kotlin 工厂方法工厂方法设计模式,它的核心作用就是通过一个工厂隐藏对象实例的创建逻辑,而不需要暴露给客户端,实现解耦。典型的使用场景就是当拥有一个父类与多个子类的时候,我们可以通过这种模式来创建子类对象。比如下面的业务场景:一个电脑加工厂,同时生产个人电脑和服务器主机。interface Computer { val cpu: String companion object Facto...https://blog.csdn.net/Agg_bin/article/details/104331797?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165078248516782350919040%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165078248516782350919040&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-6-104331797.nonecase&utm_term=%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F&spm=1018.2226.3001.4450状态模式之Java版本_Swuagg的博客-CSDN博客定义当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。使用场景代码中包含大量与对象状态有关的条件语句,比如,一个操作中含有庞大的多分支语句(ifelse或switchcase),且这些分支依赖于该对象的状态;设计原则UML类图代码实现日志打印总结状态模式和策略模式的结构几乎完全一样,但它们的目的和本质却完全不一样。优点:将繁琐的状态判断转换成结构清晰的状态族,在避免代码膨胀的同时也保证了可扩展性与可维护性缺点:增加系统类和对象的个数...https://blog.csdn.net/Agg_bin/article/details/110384915?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165078248516782350919040%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165078248516782350919040&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-7-110384915.nonecase&utm_term=%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F&spm=1018.2226.3001.4450策略模式之Java版本_Swuagg的博客-CSDN博客定义策略模式定义了一系列的算法,并将每一个算法封装起来,而且使他们可以互相替换。策略模式让算法独立于使用它的客户而独立变化。使用场景针对同一类型问题的多种处理方式,仅仅是具体行为有差别时;需要安全地封装多种同一类型的操作时;出现同一抽象类有多个子类,而又需要使用if-else或者switch-case来选择具体子类时;···总的来说,可以这么理解,使用继承违背常理(狗继承动物,让它飞就太天真了)时,使用实现繁琐(猴鸡狗猪都会叫,他们都各自实现叫的方法太累了)时,此时可以考虑组合(也就是策略https://blog.csdn.net/Agg_bin/article/details/109178910?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165078248516782350919040%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165078248516782350919040&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-8-109178910.nonecase&utm_term=%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F&spm=1018.2226.3001.4450工厂方法模式之Java版本_Swuagg的博客-CSDN博客定义定义一个用于创建对象的接口,让子类决定实例化哪个类。使用场景复杂对象的创建可以使用工厂模式,用new就可以完成的对象的创建就无需使用工厂模式。UML类图一共四个角色,即四大模块:一是抽象工厂Factory,工厂方法模式的核心;二是具体工厂ConcreteFactory,实现了具体的业务逻辑;三是抽象产品Product,是工厂方法模式所创建的产品父类;四是具体产品ConcreteProduct,是抽象产品的某个具体产品对象。代码实现package com.tcl.tvweishhttps://blog.csdn.net/Agg_bin/article/details/107179193?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165078248516782350919040%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165078248516782350919040&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-10-107179193.nonecase&utm_term=%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F&spm=1018.2226.3001.4450抽象工厂模式之Java版本_Swuagg的博客-CSDN博客定义为创建一组相关或是相互依赖的对象提供一个接口,而不需要指定它们的具体类。抽象工厂模式是围绕一个超级工厂创建其他工厂。使用场景一个对象族有相同的约束时可以使用抽象工厂模式,能解决接口选择的问题(比如Android和IOS是不同系统但有相同的电话、短信等软件)。UML类图...https://blog.csdn.net/Agg_bin/article/details/108880991?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165078248516782350919040%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165078248516782350919040&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-11-108880991.nonecase&utm_term=%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F&spm=1018.2226.3001.4450建造者模式之Java版本_Swuagg的博客-CSDN博客定义将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。使用场景初始化一个对象特别复杂时(如:参数较多,且很多参数都具有默认值);相同的方法,不同的执行顺序,产生不同的事件结果时;产品类非常复杂,或者产品类中的调用顺序不同产生了不同的作用,这个时候使用建造者模式非常合适。UML类图Product产品类——产品的抽象类;Builder——抽象Builder类,规范产品的组建,一般是由子类实现具体的组建过程;ConcreteBuilder——具体的Builder类https://blog.csdn.net/Agg_bin/article/details/111313558?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165078248516782350919040%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165078248516782350919040&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-12-111313558.nonecase&utm_term=%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F&spm=1018.2226.3001.4450使用策略模式和多态去掉switch_Swuagg的博客-CSDN博客_去掉switch为了更好的扩展性,可以“使用策略模式和多态去掉switch”(业务逻辑层适用,界面表现层除外,会增加代码的复杂度)。“使用策略模式和多态去掉switch”的思想如下:多处使用相同switch时(我觉得这个是前提,如果只有一处使用switch,而且后面基本上不会再扩展,那简单封装一下就可以了,毕竟时间那么宝贵,为啥还多去折腾一下呢?),1、先将switch代码块封装到一个新clas...https://blog.csdn.net/Agg_bin/article/details/92839080?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165078248516782350919040%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165078248516782350919040&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-14-92839080.nonecase&utm_term=%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F&spm=1018.2226.3001.4450

应用架构

MVC

  1. 缺点:a、XML做为View层,控制能力太差;b、View与Model存在耦和;c、Activity既作为Control也作为View,当业务逻辑复杂时导致Activity臃肿、代码量庞大

MVP

  1. 优点:a、解耦View与Model,分离View和业务逻辑;b、方便对业务做单测,直接对presenter
  2. 缺点:a、presenter复用性不强;b、内存泄漏
  3. 如何避免内存泄漏:在View销毁时释放presenter(比如:释放持有的View,取消网络请求等)
  4. 接口比较多怎么处理:将通用接口抽象出来放在基类,非通用接口采用继承方式

MVVM

View产生事件,使用ViewModel进行逻辑处理后,通知Model更新数据,Model把更新的数据给ViewModel, ViewModel自动通知View更新界面,而不是主动调用View的方法

  1. 优点:解耦更彻底,viewmodel不持有view,数据驱动,复用性更强,没有内存泄漏
  2. 缺点:DataBinding实现View和ViewModel双向绑定,导致ViewModel和View通信更困难,排查问题不容易
  3. LiveData作为Model和ViewModel的桥梁,通知ViewModel更新数据

MVI

模块化

组件化

插件化、热修复

海外应用插件化(动态模块)之路_Swuagg的博客-CSDN博客1 引言1.1 背景与需求背景:应用是具有多个模块的模块化应用,其中一个模块的功能,是能进行端到端的视频通话(视频通话模块)。需求:视频通话模块接入多个具有相同功能的 SDK,支持动态切换不同 SDK;需求细化:在视频通话模块已接入一个 SDK 的情况下,需要支持动态切换不同 SDK,且不增加应用安装包的大小。1.2 术语和缩略语术语/缩略语全称说明应用Application文章中的应用,都是指海外https://blog.csdn.net/Agg_bin/article/details/117198244?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165078279316781818783208%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165078279316781818783208&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-117198244.nonecase&utm_term=%E6%A8%A1%E5%9D%97%E5%8C%96&spm=1018.2226.3001.4450插件化框架之VirtualAPK_Swuagg的博客-CSDN博客本文为降低阅读难度,并未采用“插件”这一词,而是采用通俗易懂的“模块”来进行阐述。本文从以下6个方面进行阐述,如有理解不对的地方,希望各位大牛不吝赐教。VirtualAPK是什么VirtualAPK使用场景VirtualAPK如何使用VirtualAPK原理简析VirtualAPK下载与识别VirtualAPK加载与应用VirtualAPK是什么VirtualA...https://blog.csdn.net/Agg_bin/article/details/103210946?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165078279316781818783208%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165078279316781818783208&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-2-103210946.nonecase&utm_term=%E6%A8%A1%E5%9D%97%E5%8C%96&spm=1018.2226.3001.4450

开源框架源码

Glide

OkHttp3

Retrofit

EventBus

Arouter

Android Debug Database原理简析_Swuagg的博客-CSDN博客写在前面:本文大约有2.5k字,可能需要一刻钟阅读时间。1、Android Debug Database方式与其他方式查看/修改数据库?Android在开发调试过程中,查看/修改app的数据库是比较麻烦的,一般有以下几种方式:将手机app中的SQLite数据库pull到电脑,通过电脑端的软件(如SQLite Expert Professional)打开这个数据库,可以执行相关的CR...https://blog.csdn.net/Agg_bin/article/details/86495897?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165078286516781667879158%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165078286516781667879158&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-5-86495897.nonecase&utm_term=%E6%BA%90%E7%A0%81&spm=1018.2226.3001.4450

网络

  1. 打开一个网页的过程:a、浏览器DNS获取域名对应IP;b、浏览器向服务器发送Http请求;c、服务器处理请求;d、服务器发回一个HTML响应;e、浏览器开始显示HTML
  2. IP地址分类:A类(0+7位网络号+24位主机号);B类(10+14位网络号+16位主机号);C类(110+21位网络号+8位主机号)
  3. Cookie与Session区别:Cookie是客户端机制,Session是服务器机制;存储量少(不超过20个Cookie,单个数据不超过4K),存储量多;不安全,安全;损耗低,损耗高;存放设备配置信息,存放重要信息

Http/Https协议

  1. 工作方式:a、服务器不断监听TCP 80端口;b、客户端发起连接请求;c、双方建立TCP连接;d、客户端发送页面请求(Http请求报文);e、服务端返回页面请求的响应;f、关闭TCP连接
  2. Http与Https的区别:Http在应用层,数据不加密明文传输,不安全,80端口,不需要CA申请证书;Https在传输层,对数据SSL加密、身份认证,安全,443端口,需要CA申请证书

请求头、响应头

四层网络模型

  1. TCP/IP协议通信的过程:其实就对应着数据入栈与出栈的过程。入栈的过程,数据发送方每层不断地封装首部与尾部,添加一些传输的信息,确保能传输到目的地。出栈的过程,数据接收方每层不断地拆除首部与尾部,得到最终传输的数据
  2. ping的原理:利用IP层的ICMP协议包来侦测另一个主机是否可达。原理是用类型码为0的ICMP(网络控制报文)发请求,受到请求的主机则用类型码为8的ICMP回应。ping程序来计算间隔时间,并计算有多少个包被送达,用户就可以判断网络大致的情况

TCP协议

三次握手

  1. 握手过程:a、客户端发送连接请求报文段;b、服务端发送连接确认报文段;c、客户端发送连接确认报文段
  2. 挥手过程:a、客户端发送连接释放报文段;b、服务端发送连接释放确认报文段;c、客户端发送释放连接的报文段;d、客户端发送释放连接确认报文段
  3. 全双工通信:TCP是全双工通信,通信双方的应用进程在任何时候都能发送数据

四次挥手

Socket通信

  1. Socket是什么:Socket不是一种协议,是应用层与TCP/IP 协议族通信的中间软件抽象层,表现为一个封装了 TCP / IP协议族的编程接口(API),属于传输层(主要解决数据如何在网络中传输)
  2. Socket与Http协议的对比:Socket采用服务器主动发送数据的方式,Http协议采用请求-响应方式(客户端主动)

参考:全面&详细的面试指南:计算机网络篇 (附答案)_Carson带你学Android的博客-CSDN博客

数据存储

设计一个日志系统

SP、DataStore、MMKV

File

Sqlite、ContentProvider、GreenDao

图片

Kotlin

我又拿起了《Kotlin核心编程》_Swuagg的博客-CSDN博客_kotlin核心编程《Kotlin核心编程》是水滴技术团队2019-4-15出版的,也不知道为啥这么火,网上搜Kotlin学习文档、书籍,除了Kotlin官方文档,其他大家极力推荐的就是此书了,读完以后才知道为什么。首先,我是19年8月份入手的Kotlin核心编程,先是读了前面3章,但由于工作原因就先放下来了。不幸的是,今年新型冠状病毒导致的疫情异常严重,从刚开始的武汉封城,到城市小区、乡下农村,封区的封区,...https://blog.csdn.net/Agg_bin/article/details/104375560?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165078292016782395325614%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165078292016782395325614&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-7-104375560.nonecase&utm_term=Kotlin&spm=1018.2226.3001.4450

其他

蓝牙BLE通信

Android BLE4.0 从小白到理解的过程_Swuagg的博客-CSDN博客学习蓝牙低功耗的开发过程,要达到的效果是——利用两台Android手机,通过BLE4.0进行通信,可以发送和接收数据。其中一台Android手机T模拟发出广播,作为BLE设备(周边设备),这个BLE设备在生产环境中就是我们用到的气体检测传感器、智能手环、体重秤、血压计等等;另一台Android手机B,作为中央设备,搜索手机T发出的广播并连接;手机B可以接收手机T的数据,也可以发送数据给...https://blog.csdn.net/Agg_bin/article/details/88713612?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165078295216780357267982%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165078295216780357267982&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-88713612.nonecase&utm_term=BLE&spm=1018.2226.3001.4450Android回声消除调研 20220321_Swuagg的博客-CSDN博客回声产生回声就是声音信号经过一系列反射之后,又听到了自己讲话的声音,这就是回声。在VoIP(Voice over Internet Protocol,基于IP的语音传输 )实时语音通话中,是近端通话者的声音被自己的麦克风拾取后通过网络传到远端,远端扬声器播放出来的声音被麦克风拾取后通过网络又重新发回近端,再加上网络和数据处理等各种延迟的影响,使得近端通话者能够从扬声器中听到自己的刚才所说的话,就产生了回声。回声分类在通信系统中,回声主要分为两类:电路回声和声学回声(线性回声和非线性回声)。https://blog.csdn.net/Agg_bin/article/details/123638819?spm=1001.2014.3001.5501Android 单元测试,从小白到入门开始_Swuagg的博客-CSDN博客_android 单元测试 教程1 引言1.1 背景随着 Android 应用越来越壮大,对应代码量显著增加,代码逻辑也日趋复杂,此时需要采取一定的行动去保证代码质量,减少逻辑漏洞等,于是严格地执行单元测试编写任务,将单元测试落实到平常开发和维护任务当中去,就是很重要的一环,不可忽视。然而,很多应用开发者之前并未编写过单元测试代码,那么如果有一篇通俗易懂并带有操作步骤的文章,能帮助应用开发者完成从单元测试小白到入门的过渡,就再好不过了,于是本文就是在此情况写就的,如有不好之处,请多多包涵,谢谢...https://blog.csdn.net/Agg_bin/article/details/120768579?spm=1001.2014.3001.5501需求评审流程_Swuagg的博客-CSDN博客_需求评审一、目的为了更好的为业务、产品、开发及测试建立统一的需求管理机制和跟踪机制,保证产品开发成果与需求的一致性,提升需求管理全链路运作效率,特制定本流程。此文档主要描述需求和评审等相关的流程,开发、自测、提测、测试、发布和线上监控等流程请参考相应文档。二、适用范围本流程适用于软件研发人员熟悉业务需求。三、参与人员需求方:产品经理必要参与人员:产品经理、开发Owner、设计Owner、测试Owner可能参与人员:项目经理(该需求有项目经理)、安全合规同事(该需求涉及到安全合规有关事项)、云端同事https://blog.csdn.net/Agg_bin/article/details/116533231?spm=1001.2014.3001.5501技术简历的书写建议_Swuagg的博客-CSDN博客感谢:这份简历是在华为部门老大、前腾讯大佬、同行Android高级工程师、测试工程师和HR的建议和意见下,以及自己一周的反复斟酌后,新鲜出炉的。简历共分为六大部分部分一:个人基本信息头像:还看得过去,就贴上。自我描述:20-30个字。比如:“2.5年Android开发经验,喜欢专研不服输,责任心强”。基本信息:有党员身份,写上的话大公司一般会喜欢,如果工作五年以上,...https://blog.csdn.net/Agg_bin/article/details/95358030?spm=1001.2014.3001.5501我用了两年时间去读《Thinking in Java》_Swuagg的博客-CSDN博客路漫漫其修远兮,吾将上下而求索。 ——题记我用了两年时间去读《Think...https://blog.csdn.net/Agg_bin/article/details/89703012?spm=1001.2014.3001.55012022年Android中高级面试框架_Swuagg的博客-CSDN博客Java泛型集合ArrayListLinkedListHashMapLinkedHashMapConcurrentHashMap多线程并发volatile线程反射JVM类加载怎么判断对象是否已死?垃圾回收机制四大引用泛型集合 ——HashMap、ConcurrentHashMap源码和数据结构多线程反射JVM ——类加载、内存模型、内存管理机制、垃圾回收机制Android四大组件和FragmentactivityServiceBroadcastReceiverContentProviderFragmentHhttps://blog.csdn.net/Agg_bin/article/details/123955215?spm=1001.2014.3001.5501


以上内容是Android中高级面试的框架内容,也包括笔者理解的一些答案(仅供参考),谢谢。


2023年Android开发面试系列拆解:

Android开发面试:Android知识答案精解

Android开发面试:Java知识答案精解

Android开发面试:架构设计和网络知识答案精解

Android开发面试:数据结构与算法知识答案精解

Android开发面试:Kotlin面试知识答案精解


你可能感兴趣的:(简历与面试,Android中高级,面试内容)