2、Android中还了解哪些方便线程切换的类?
- 参考回答:
- AsyncTask:底层封装了线程池和Handler,便于执行后台任务以及在子线程中进行UI操作。
- HandlerThread:一种具有消息循环的线程,其内部可使用Handler。
- IntentService:是一种异步、会自动停止的服务,内部采用HandlerThread。
3、讲讲AsyncTask的原理
- 参考回答:
- AsyncTask中有两个线程池(SerialExecutor和THREAD_POOL_EXECUTOR)和一个Handler(InternalHandler),其中线程池SerialExecutor用于任务的排队,而线程池THREAD_POOL_EXECUTOR用于真正地执行任务,InternalHandler用于将执行环境从线程池切换到主线程。
- sHandler是一个静态的Handler对象,为了能够将执行环境切换到主线程,这就要求sHandler这个对象必须在主线程创建。由于静态成员会在加载类的时候进行初始化,因此这就变相要求AsyncTask的类必须在主线程中加载,否则同一个进程中的AsyncTask都将无法正常工作。
4、IntentService有什么用 ?
- 参考回答:
- IntentService可用于执行后台耗时的任务,当任务执行完成后会自动停止,同时由于IntentService是服务的原因,不同于普通Service,IntentService可自动创建子线程来执行任务,这导致它的优先级比单纯的线程要高,不容易被系统杀死,所以IntentService比较适合执行一些高优先级的后台任务。
5、直接在Activity中创建一个thread跟在service中创建一个thread之间的区别?
- 参考回答:
- 在Activity中被创建:该Thread的就是为这个Activity服务的,完成这个特定的Activity交代的任务,主动通知该Activity一些消息和事件,Activity销毁后,该Thread也没有存活的意义了。
- 在Service中被创建:这是保证最长生命周期的Thread的唯一方式,只要整个Service不退出,Thread就可以一直在后台执行,一般在Service的onCreate()中创建,在onDestroy()中销毁。所以,在Service中创建的Thread,适合长期执行一些独立于APP的后台任务,比较常见的就是:在Service中保持与服务器端的长连接。
6、ThreadPoolExecutor的工作策略 ?
- 参考回答:ThreadPoolExecutor执行任务时会遵循如下规则
- 如果线程池中的线程数量未达到核心线程的数量,那么会直接启动一个核心线程来执行任务。
- 如果线程池中的线程数量已经达到或则超过核心线程的数量,那么任务会被插入任务队列中排队等待执行。
- 如果在第2点无法将任务插入到任务队列中,这往往是由于任务队列已满,这个时候如果在线程数量未达到线程池规定的最大值,那么会立刻启动一个非核心线程来执行任务。
- 如果第3点中线程数量已经达到线程池规定的最大值,那么就拒绝执行此任务,ThreadPoolExecutor会调用RejectedExecutionHandler的rejectedExecution方法来通知调用者。
7、Handler、Thread和HandlerThread的差别?
- 参考回答:
- Handler:在android中负责发送和处理消息,通过它可以实现其他支线线程与主线程之间的消息通讯。
- Thread:Java进程中执行运算的最小单位,亦即执行处理机调度的基本单位。某一进程中一路单独运行的程序。
- HandlerThread:一个继承自Thread的类HandlerThread,Android中没有对Java中的Thread进行任何封装,而是提供了一个继承自Thread的类HandlerThread类,这个类对Java的Thread做了很多便利的封装。HandlerThread继承于Thread,所以它本质就是个Thread。与普通Thread的差别就在于,它在内部直接实现了Looper的实现,这是Handler消息机制必不可少的。有了自己的looper,可以让我们在自己的线程中分发和处理消息。如果不用HandlerThread的话,需要手动去调用Looper.prepare()和Looper.loop()这些方法。
8、ThreadLocal的原理
- 参考回答:
- ThreadLocal是一个关于创建线程局部变量的类。使用场景如下所示:
- 实现单个线程单例以及单个线程上下文信息存储,比如交易id等。
- 实现线程安全,非线程安全的对象使用ThreadLocal之后就会变得线程安全,因为每个线程都会有一个对应的实例。 承载一些线程相关的数据,避免在方法中来回传递参数。
- 当需要使用多线程时,有个变量恰巧不需要共享,此时就不必使用synchronized这么麻烦的关键字来锁住,每个线程都相当于在堆内存中开辟一个空间,线程中带有对共享变量的缓冲区,通过缓冲区将堆内存中的共享变量进行读取和操作,ThreadLocal相当于线程内的内存,一个局部变量。每次可以对线程自身的数据读取和操作,并不需要通过缓冲区与 主内存中的变量进行交互。并不会像synchronized那样修改主内存的数据,再将主内存的数据复制到线程内的工作内存。ThreadLocal可以让线程独占资源,存储于线程内部,避免线程堵塞造成CPU吞吐下降。
- 在每个Thread中包含一个ThreadLocalMap,ThreadLocalMap的key是ThreadLocal的对象,value是独享数据。
9、多线程是否一定会高效(优缺点)
- 参考回答:
- 多线程的优点:
- 方便高效的内存共享 - 多进程下内存共享比较不便,且会抵消掉多进程编程的好处
- 较轻的上下文切换开销 - 不用切换地址空间,不用更改CR3寄存器,不用清空TLB
- 线程上的任务执行完后自动销毁
- 多线程的缺点:
- 开启线程需要占用一定的内存空间(默认情况下,每一个线程都占512KB)
- 如果开启大量的线程,会占用大量的内存空间,降低程序的性能
- 线程越多,cpu在调用线程上的开销就越大
- 程序设计更加复杂,比如线程间的通信、多线程的数据共享
- 综上得出,多线程不一定能提高效率,在内存空间紧张的情况下反而是一种负担,因此在日常开发中,应尽量
- 不要频繁创建,销毁线程,使用线程池
- 减少线程间同步和通信(最为关键)
- 避免需要频繁共享写的数据
- 合理安排共享数据结构,避免伪共享(false sharing)
- 使用非阻塞数据结构/算法
- 避免可能产生可伸缩性问题的系统调用(比如mmap)
- 避免产生大量缺页异常,尽量使用Huge Page
- 可以的话使用用户态轻量级线程代替内核线程
10、多线程中,让你做一个单例,你会怎么做
- 参考回答:
多线程中建立单例模式考虑的因素有很多,比如线程安全 -延迟加载-代码安全:如防止序列化攻击,防止反射攻击(防止反射进行私有方法调用) -性能因素
实现方法有多种,饿汉,懒汉(线程安全,线程非安全),双重检查(DCL),内部类,以及枚举
推荐文章:单例模式的总结
11、除了notify还有什么方式可以唤醒线程
- 参考回答:
- 当一个拥有Object锁的线程调用 wait()方法时,就会使当前线程加入object.wait 等待队列中,并且释放当前占用的Object锁,这样其他线程就有机会获取这个Object锁,获得Object锁的线程调用notify()方法,就能在Object.wait 等待队列中随机唤醒一个线程(该唤醒是随机的与加入的顺序无关,优先级高的被唤醒概率会高)
- 如果调用notifyAll()方法就唤醒全部的线程。注意:调用notify()方法后并不会立即释放object锁,会等待该线程执行完毕后释放Object锁。
12、什么是ANR ? 什么情况会出现ANR ?如何避免 ? 在不看代码的情况下如何快速定位出现ANR问题所在 ?
- 参考回答:
- ANR(Application Not Responding,应用无响应):当操作在一段时间内系统无法处理时,会在系统层面会弹出ANR对话框
- 产生ANR可能是因为5s内无响应用户输入事件、10s内未结束BroadcastReceiver、20s内未结束Service
- 想要避免ANR就不要在主线程做耗时操作,而是通过开子线程,方法比如继承Thread或实现Runnable接口、使用AsyncTask、IntentService、HandlerThread等
- 推荐文章:如何快速分析定位ANR
Bitmap
1、Bitmap使用需要注意哪些问题 ?
- 参考回答:
- 要选择合适的图片规格(bitmap类型):通常我们优化Bitmap时,当需要做性能优化或者防止OOM,我们通常会使用RGB_565,因为ALPHA_8只有透明度,显示一般图片没有意义,Bitmap.Config.ARGB_4444显示图片不清楚,Bitmap.Config.ARGB_8888占用内存最多。:
- ALPHA_8 每个像素占用1byte内存
- ARGB_4444 每个像素占用2byte内存
- ARGB_8888 每个像素占用4byte内存(默认)
- RGB_565 每个像素占用2byte内存
- 降低采样率:BitmapFactory.Options 参数inSampleSize的使用,先把options.inJustDecodeBounds设为true,只是去读取图片的大小,在拿到图片的大小之后和要显示的大小做比较通过calculateInSampleSize()函数计算inSampleSize的具体值,得到值之后。options.inJustDecodeBounds设为false读图片资源。
- 复用内存:即通过软引用(内存不够的时候才会回收掉),复用内存块,不需要再重新给这个bitmap申请一块新的内存,避免了一次内存的分配和回收,从而改善了运行效率。
- 使用recycle()方法及时回收内存。
- 压缩图片。
2、Bitmap.recycle()会立即回收么?什么时候会回收?如果没有地方使用这个Bitmap,为什么垃圾回收不会直接回收?
- 参考回答:
- 通过源码可以了解到,加载Bitmap到内存里以后,是包含两部分内存区域的。简单的说,一部分是Java部分的,一部分是C部分的。这个Bitmap对象是由Java部分分配的,不用的时候系统就会自动回收了
- 但是那个对应的C可用的内存区域,虚拟机是不能直接回收的,这个只能调用底层的功能释放。所以需要调用recycle()方法来释放C部分的内存
- bitmap.recycle()方法用于回收该Bitmap所占用的内存,接着将bitmap置空,最后使用System.gc()调用一下系统的垃圾回收器进行回收,调用System.gc()并不能保证立即开始进行回收过程,而只是为了加快回收的到来。
3、一张Bitmap所占内存以及内存占用的计算
- 参考回答:
- Bitamp 所占内存大小 = 宽度像素 x (inTargetDensity / inDensity) x 高度像素 x (inTargetDensity / inDensity)x 一个像素所占的内存字节大小
- 注:这里inDensity表示目标图片的dpi(放在哪个资源文件夹下),inTargetDensity表示目标屏幕的dpi,所以你可以发现inDensity和inTargetDensity会对Bitmap的宽高进行拉伸,进而改变Bitmap占用内存的大小。
- 在Bitmap里有两个获取内存占用大小的方法。
- getByteCount():API12 加入,代表存储 Bitmap 的像素需要的最少内存。
- getAllocationByteCount():API19 加入,代表在内存中为 Bitmap 分配的内存大小,代替了 getByteCount() 方法。
- 在不复用 Bitmap 时,getByteCount() 和 getAllocationByteCount 返回的结果是一样的。在通过复用 Bitmap 来解码图片时,那么 getByteCount() 表示新解码图片占用内存的大 小,getAllocationByteCount() 表示被复用 Bitmap 真实占用的内存大小
4、Android中缓存更新策略 ?
- 参考回答:
- Android的缓存更新策略没有统一的标准,一般来说,缓存策略主要包含缓存的添加、获取和删除这三类操作,但不管是内存缓存还是存储设备缓存,它们的缓存容量是有限制的,因此删除一些旧缓存并添加新缓存,如何定义缓存的新旧这就是一种策略,不同的策略就对应着不同的缓存算法
- 比如可以简单地根据文件的最后修改时间来定义缓存的新旧,当缓存满时就将最后修改时间较早的缓存移除,这就是一种缓存算法,但不算很完美
5、LRU的原理 ?
- 参考回答:
- 为减少流量消耗,可采用缓存策略。常用的缓存算法是LRU(Least Recently Used):当缓存满时, 会优先淘汰那些近期最少使用的缓存对象。主要是两种方式:
- LruCache(内存缓存):LruCache类是一个线程安全的泛型类:内部采用一个LinkedHashMap以强引用的方式存储外界的缓存对象,并提供get和put方法来完成缓存的获取和添加操作,当缓存满时会移除较早使用的缓存对象,再添加新的缓存对象。
- DiskLruCache(磁盘缓存): 通过将缓存对象写入文件系统从而实现缓存效果
性能优化
1、图片的三级缓存中,图片加载到内存中,如果内存快爆了,会发生什么?怎么处理?
- 参考回答:
首先我们要清楚图片的三级缓存是如何的
如果内存足够时不回收。内存不够时就回收软引用对象
2、内存中如果加载一张500*500的png高清图片.应该是占用多少的内存?
- 参考回答:
不考虑屏幕比的话:占用内存=500 * 500 * 4 = 1000000B ≈ 0.95MB
考虑屏幕比的的话:占用内存= 宽度像素 x (inTargetDensity / inDensity) x 高度像素 x (inTargetDensity / inDensity)x 一个像素所占的内存字节大小
inDensity表示目标图片的dpi(放在哪个资源文件夹下),inTargetDensity表示目标屏幕的dpi
3、WebView的性能优化 ?
- 参考回答:
一个加载网页的过程中,native、网络、后端处理、CPU都会参与,各自都有必要的工作和依赖关系;让他们相互并行处理而不是相互阻塞才可以让网页加载更快:
WebView初始化慢,可以在初始化同时先请求数据,让后端和网络不要闲着。
常用 JS 本地化及延迟加载,使用第三方浏览内核
后端处理慢,可以让服务器分trunk输出,在后端计算的同时前端也加载网络静态资源。
脚本执行慢,就让脚本在最后运行,不阻塞页面解析。
同时,合理的预加载、预缓存可以让加载速度的瓶颈更小。
WebView初始化慢,就随时初始化好一个WebView待用。
DNS和链接慢,想办法复用客户端使用的域名和链接。
推荐文章:WebView性能、体验分析与优化
4、Bitmap如何处理大图,如一张30M的大图,如何预防OOM?
- 参考回答:避免OOM的问题就需要对大图片的加载进行管理,主要通过缩放来减小图片的内存占用。
BitmapFactory提供的加载图片的四类方法(decodeFile、decodeResource、decodeStream、decodeByteArray)都支持BitmapFactory.Options参数,通过inSampleSize参数就可以很方便地对一个图片进行采样缩放
比如一张1024_1024的高清图片来说。那么它占有的内存为1024_1024_4,即4MB,如果inSampleSize为2,那么采样后的图片占用内存只有512_512*4,即1MB(注意:根据最新的官方文档指出,inSampleSize的取值应该总是为2的指数,即1、2、4、8等等,如果外界输入不足为2的指数,系统也会默认选择最接近2的指数代替,比如2)
综合考虑。通过采样率即可有效加载图片,流程如下
将BitmapFactory.Options的inJustDecodeBounds参数设为true并加载图片
从BitmapFactory.Options中取出图片的原始宽高信息,它们对应outWidth和outHeight参数
根据采样率的规则并结合目标View的所需大小计算出采样率inSampleSize
将BitmapFactory.Options的inJustDecodeBounds参数设为false,重新加载图片
5、内存回收机制与GC算法(各种算法的优缺点以及应用场景);GC原理时机以及GC对象
- 参考回答:
- 内存判定对象可回收有两种机制:
- 引用计数算法:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。然而在主流的Java虚拟机里未选用引用计数算法来管理内存,主要原因是它难以解决对象之间相互循环引用的问题,所以出现了另一种对象存活判定算法。
- 可达性分析法:通过一系列被称为『GCRoots』的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为_引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。其中可作为GC Roots的对象:虚拟机栈中引用的对象,主要是指栈帧中的__本地变量_*、本地方法栈中Native方法引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象
- GC回收算法有以下四种:
- 分代收集算法:是当前商业虚拟机都采用的一种算法,根据对象存活周期的不同,将Java堆划分为新生代和老年代,并根据各个年代的特点采用最适当的收集算法。
- 新生代:大批对象死去,只有少量存活。使用『复制算法』,只需复制少量存活对象即可。
- 复制算法:把可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用尽后,把还存活着的对象『复制』到另外一块上面,再将这一块内存空间一次清理掉。实现简单,运行高效。在对象存活率较高时就要进行较多的复制操作,效率将会变低
- 老年代:对象存活率高。使用『标记—清理算法』或者『标记—整理算法』,只需标记较少的回收对象即可。
- 标记-清除算法:首先『标记』出所有需要回收的对象,然后统一『清除』所有被标记的对象。标记和清除两个过程的效率都不高,清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
- 标记-整理算法:首先『标记』出所有需要回收的对象,然后进行『整理』,使得存活的对象都向一端移动,最后直接清理掉端边界以外的内存。标记整理算法会将所有的存活对象移动到一端,并对不存活对象进行处理,因此其不会产生内存碎片
- 推荐文章:图解Java 垃圾回收机制
6、内存泄露和内存溢出的区别 ?AS有什么工具可以检测内存泄露
- 参考回答:
- 内存溢出(out of memory):是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。
- 内存泄露(memory leak):是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。memory leak会最终会导致out of memory!
- 查找内存泄漏可以使用Android Studio 自带的AndroidProfiler工具或MAT
7、性能优化,怎么保证应用启动不卡顿? 黑白屏怎么处理?
- 参考回答:
- 应用启动速度,取决于你在application里面时候做了什么事情,比如你集成了很多sdk,并且sdk的init操作都需要在主线程里实现所以会有卡顿的感觉。在非必要的情况下可以把加载延后或则开启子线程处理
- 另外,影响界面卡顿的两大因素,分别是界面绘制和数据处理。
- 布局优化(使用include,merge标签,复杂布局推荐使用ConstraintLayout等)
- onCreate() 中不执行耗时操作 把页面显示的 View 细分一下,放在 AsyncTask 里逐步显示,用 Handler 更好。这样用户的看到的就是有层次有步骤的一个个的 View 的展示,不会是先看到一个黑屏,然后一下显示所有 View。最好做成动画,效果更自然。
- 利用多线程的目的就是尽可能的减少 onCreate() 和 onReume() 的时间,使得用户能尽快看到页面,操作页面。
- 减少主线程阻塞时间。
- 提高 Adapter 和 AdapterView 的效率。
- 推荐文章:Android 性能优化之内存检测、卡顿优化、耗电优化、APK瘦身
- 黑白屏产生原因:当我们在启动一个应用时,系统会去检查是否已经存在这样一个进程,如果不存在,系统的服务会先检查startActivity中的intent的信息,然后在去创建进程,最后启动Acitivy,即冷启动。而启动出现白黑屏的问题,就是在这段时间内产生的。系统在绘制页面加载布局之前,首先会初始化窗口(Window),而在进行这一步操作时,系统会根据我们设置的Theme来指定它的Theme 主题颜色,我们在Style中的设置就决定了显示的是白屏还是黑屏。
- windowIsTranslucent和windowNoTitle,将这两个属性都设置成true (会有明显的卡顿体验,不推荐)
- 如果启动页只是是一张图片,那么为启动页专一设置一个新的主题,设置主题的android:windowBackground属性为启动页背景图即可
- 使用layer-list制作一张图片launcher_layer.xml,将其设置为启动页专一主题的背景,并将其设置为启动页布局的背景。
- 推荐文章:Android启动页解决攻略
8、强引用置为null,会不会被回收?
- 参考回答:
- 不会立即释放对象占用的内存。 如果对象的引用被置为null,只是断开了当前线程栈帧中对该对象的引用关系,而 垃圾收集器是运行在后台的线程,只有当用户线程运行到安全点(safe point)或者安全区域才会扫描对象引用关系,扫描到对象没有被引用则会标记对象,这时候仍然不会立即释放该对象内存,因为有些对象是可恢复的(在 finalize方法中恢复引用 )。只有确定了对象无法恢复引用的时候才会清除对象内存。
9、ListView跟RecyclerView的区别
- 参考回答:
- 动画区别:
- 在RecyclerView中,内置有许多动画API,例如:notifyItemChanged(), notifyDataInserted(), notifyItemMoved()等等;如果需要自定义动画效果,可以通过实现(RecyclerView.ItemAnimator类)完成自定义动画效果,然后调用RecyclerView.setItemAnimator();
- 但是ListView并没有实现动画效果,但我们可以在Adapter自己实现item的动画效果;
- 刷新区别:
- ListView中通常刷新数据是用全局刷新notifyDataSetChanged(),这样一来就会非常消耗资源;本身无法实现局部刷新,但是如果要在ListView实现局部刷新,依然是可以实现的,当一个item数据刷新时,我们可以在Adapter中,实现一个onItemChanged()方法,在方法里面获取到这个item的position(可以通过getFirstVisiblePosition()),然后调用getView()方法来刷新这个item的数据;
- RecyclerView中可以实现局部刷新,例如:notifyItemChanged();
- 缓存区别:
- RecyclerView比ListView多两级缓存,支持多个离ItemView缓存,支持开发者自定义缓存处理逻辑,支持所有RecyclerView共用同一个RecyclerViewPool(缓存池)。
- ListView和RecyclerView缓存机制基本一致,但缓存使用不同
- 推荐文章:
10、ListView的adapter是什么adapter
参考回答:
- BaseAdapter:抽象类,实际开发中我们会继承这个类并且重写相关方法,用得最多的一个适配器!
- ArrayAdapter:支持泛型操作,最简单的一个适配器,只能展现一行文字〜
- SimpleAdapter:同样具有良好扩展性的一个适配器,可以自定义多种效果!
- SimpleCursorAdapter:用于显示简单文本类型的listView,一般在数据库那里会用到,不过有点过时,不推荐使用!
11、LinearLayout、FrameLayout、RelativeLayout性能对比,为什么?
- 参考回答:
- RelativeLayout会让子View调用2次onMeasure,LinearLayout 在有weight时,也会调用子 View 2次onMeasure
- RelativeLayout的子View如果高度和RelativeLayout不同,则会引发效率问题,当子View很复杂时,这个问题会更加严重。如果可以,尽量使用padding代替margin。
- 在不影响层级深度的情况下,使用LinearLayout和FrameLayout而不是RelativeLayout。
JNI
1、对JNI是否了解
- 参考回答:
- Java的优点是跨平台,但也因为其跨平台的的特性导致其本地交互的能力不够强大,一些和操作系统相关的的特性Java无法完成,于是Java提供JNI专门用于和本地代码交互,通过JNI,用户可以调用C、C++编写的本地代码
- NDK是Android所提供的一个工具集合,通过NDK可以在Android中更加方便地通过JNI访问本地代码,其优点在于
- 提高代码的安全性。由于so库反编译困难,因此NDK提高了Android程序的安全性
- 可以很方便地使用目前已有的C/C++开源库
- 便于平台的移植。通过C/C++实现的动态库可以很方便地在其它平台上使用
- 提高程序在某些特定情形下的执行效率,但是并不能明显提升Android程序的性能
2、如何加载NDK库 ?如何在JNI中注册Native函数,有几种注册方法 ?
- 参考回答:
public class JniTest{
//加载NDK库
static{
System.loadLirary(“jni-test”);
}
}
- 注册JNI函数的两种方法
- 静态方法
- 动态注册
- 推荐文章:
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
设计模式学习笔记
设计模式系列学习视频
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
(备注Android)**
[外链图片转存中…(img-i7IEdsge-1712779038808)]
设计模式学习笔记
[外链图片转存中…(img-KDy18BW2-1712779038808)]
设计模式系列学习视频
[外链图片转存中…(img-gUwwvGYT-1712779038808)]
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-DPCUgqEG-1712779038808)]