1.什么是协程?
什么是协程
协程,英文名是 Coroutine, 又称为微线程,是一种用户态的轻量级线程。协程不像线程和进程那样,需要进行系统内核上的上下文切换,协程的上下文切换是由程序员决定的。
协程相对于多线程的优点
多线程编程是比较困难的,因为调度程序任何时候都能中断线程, 必须记住保留锁, 去保护程序中重要部分, 防止多线程在执行的过程中断。
而协程默认会做好全方位保护, 以防止中断。我们必须显示产出才能让程序的余下部分运行。对协程来说, 无需保留锁, 而在多个线程之间同步操作, 协程自身就会同步, 因为在任意时刻, 只有一个协程运行。总结下大概下面几点:
无需系统内核的上下文切换,减小开销;
无需原子操作锁定及同步的开销,不用担心资源共享的问题;
单线程即可实现高并发,单核 CPU 即便支持上万的协程都不是问题,所以很适合用于高并发处理,尤其是在应用在网络爬虫中。
协程的缺点
同样的总结下大概以下 2 点。
无法使用 CPU 的多核
协程的本质是个单线程,它不能同时用上单个 CPU 的多个核,协程需要和进程配合才能运行在多 CPU上。当然我们日常所编写的绝大部分应用都没有这个必要,就比如网络爬虫来说,限制爬虫的速度还有其他的因素,比如网站并发量、网速等问题都会是爬虫速度限制的因素。除非做一些密集型应用,这个时候才可能会用到多进程和协程。
处处都要使用非阻塞代码
写协程就意味着你要一值写一些非阻塞的代码,使用各种异步版本的库,比如后面的异步爬虫教程中用的 aiohttp 就是一个异步版本的request库等。 不过这些缺点并不能影响到使用协程的优势。
android中的内存泄露通常是Activity或者Fragment的泄露。下文分析以Activity展开,Fragment同理。
这是为了防止内存泄漏,可以在onDetachedFromWindow方法中结束,这个方法回调的时机是 当View的Activity退出或者当前View被移除的时候 会调用 这时候是结束动画或者线程的好时机 另外还有一个对应的方法 onAttachedToWindow 这个方法调用的时机是在包含View的Activity启动时 回调 回调在onDraw方法 之前
10. 不一定非要使用弱引用才行
如避免AsyncTask内存泄漏的简单例子:
这里是AsyncTask:
当然这个例子非常基础,但是我认为作为另一种解决方案的演示来说足够了。
这里是另一个使用RxJava实现的简单例子,我们仍然没有使用弱引用。
参考:https://blog.csdn.net/unicorn97/article/details/81009204
3. 进程间的五种通信方式介绍
三、数组的缺点:
3.1 从头部删除、从头部插入的效率低,时间复杂度是o(n),因为需要相应的向前搬移和向后搬移。
3.2 空间利用率不高
3.3 内存空间要求高,必须要有足够的连续的内存空间。
3.4 数组的空间大小是固定的,不能进行动态扩展
链表(ListNode)
一、链表的特点:
所谓链表,链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。
链表:链表存储区间离散,占用内存比较宽松,故空间复杂度很小,但时间复杂度很大,达O(N)。链表的特点是:查询相对于数组困难,增加和删除容易。
1.1 在内存中,元素的空间可以在任意地方,空间是分散的,不需要连续。
1.2 链表中的元素有两个属性,一个是元素的值,另一个是指针,此指针标记了下一个元素的地址。
每一个数据都会保存下一个数据的内存地址,通过该地址就可以找到下一个数据
1.3 查找数据时间效率低,时间复杂度是o(n)
因为链表的空间是分散的,所以不具有随机访问性,如果需要访问某个位置的数据,需要从第一个数开始找起,依次往后遍历,知道找到待查询的位置,故可能在查找某个元素时,时间复杂度是o(n)
1.4 空间不需要提前指定大小,是动态申请的,根据需求动态的申请和删除内存空间,扩展方便,故空间的利用率较高
1.5 任意位置插入元素和删除元素时间效率较高,时间复杂度是o(1)
1.6 链表的空间是从堆中分配的。(堆:先进先出,后进后出)
二、链表的优点
2.1 任意位置插入元素和删除元素的速度快,时间复杂度是o(1)
2.2 内存利用率高,不会浪费内存
2.3 链表的空间大小不固定,可以动态拓展。
三、链表的缺点
随机访问效率低,时间复杂度是o(1)
4.Bitmap对象的理解
1.Bitmap在Android中指的是一张图片。
2.通过BitmapFactory类提供的四类方法:
1)decodeFile(从文件系统加载出一个Bitmap对象)
2)decodeResource(从资源中加载出一个Bitmap对象)
3)decodeStream(从输入流中加载出一个Bitmap对象)
4)decodeByteArray(从字节数组中加载出一个Bitmap对象)
其中decodeFile , decodeResource又间接调用了decodeStream方法,这四类方法最终是在Android的底层实现的,对应着BitmapFactory类的几个native方法。
3.BitmapFactory.Options的参数
①inSampleSize参数
上述四类方法都支持BitmapFactory.Options参数,而Bitmap的按一定采样率进行缩放就是通过BitmapFactory.Options参数实现的,主要用到了inSampleSize参数,即采样率。通过对inSampleSize的设置,对图片的像素的高和宽进行缩放。
当inSampleSize=1,即采样后的图片大小为图片的原始大小。小于1,也按照1来计算。
当inSampleSize>1,即采样后的图片将会缩小,缩放比例为1/(inSampleSize的二次方)。
关于inSampleSize取值的注意事项:
通常是根据图片宽高实际的大小/需要的宽高大小,分别计算出宽和高的缩放比。但应该取其中最小的缩放比,避免缩放图片太小,到达指定控件中不能铺满,需要拉伸从而导致模糊。
②inJustDecodeBounds参数
我们需要获取加载的图片的宽高信息,然后交给inSampleSize参数选择缩放比缩放。
想先不加载图片却能获得图片的宽高信息,可通过inJustDecodeBounds=true,然后加载图片就可以实现只解析图片的宽高信息,并不会真正的加载图片,所以这个操作是轻量级的。
当获取了宽高信息,计算出缩放比后,然后在将inJustDecodeBounds=false,再重新加载图片,就可以加载缩放后的图片。
4.高效加载Bitmap的流程
①将BitmapFactory.Options的inJustDecodeBounds参数设为true并加载图片。这里要设置Options.inJustDecodeBounds=true,这时候decode的bitmap为null,只是把图片的宽高放在Options里,
②从BitmapFactory.Options中取出图片的原始宽高信息,它们对应于outWidth和outHeight参数。
③根据采样率的规则并结合目标View的所需大小计算出采样率inSampleSize。
④将BitmapFactory.Options的inJustDecodeBounds参数设为false,然后重新加载图片。
5.什么是深拷贝与浅拷贝
浅拷贝:
对内存地址的复制,让目标对象指针和源对象指向同一片内存空间。注意:当内存销毁的时候,只想对象的指针,必须重新定义,才能够使用
浅拷贝是一个传址,也就是把a的值赋给b的时候同时也把a的址赋给了b,当b(a)的值改变的时候,a(b)的值同时也会改变
深拷贝:深拷贝是指,拷贝对象的具体内容,二内存地址是自主分配的,拷贝结束之后俩个对象虽然存的值是一样的,但是内存地址不一样,俩个对象页互相不影响,互不干涉
深拷贝的几种方法:
1、JSON内置的方法
var a={x:1}
2. var b=Object.assign({}, a);
3. console.log(b); //{x:1}
4. b.x = 2;
5. console.log(b); //{x:2}
6. console.log(a);
7. 原理:该方法是用Object.assign对对象进行拼接, 将后续对象的内容插入到第一个参数指定的对象,不会修改第一个参数之后的对象,而我们将第一个对象指定为一个匿名空对象,实现深拷贝
注:对象嵌套层次过深,超过2层,就会出现浅拷贝的状况,比如echarts组件的option对象
3.递归实现
6. 对象锁和类锁是否会互相影响?
·对象锁:Java的所有对象都含有1个互斥锁,这个锁由JVM自动获取和释放。线程进入synchronized方法的时候获取该对象的锁,当然如果已经有线程获取了这个对象的锁,那么当前线程会等待;synchronized方法正常返回或者抛异常而终止,JVM会自动释放对象锁。这里也体现了用synchronized来加锁的1个好处,方法抛异常的时候,锁仍然可以由JVM来自动释放。
· 类锁:对象锁是用来控制实例方法之间的同步,类锁是用来控制静态方法(或静态变量互斥体)之间的同步。其实类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的。我们都知道,java类可能会有很多个对象,但是只有1个Class对象,也就是说类的不同实例之间共享该类的Class对象。Class对象其实也仅仅是1个java对象,只不过有点特殊而已。由于每个java对象都有1个互斥锁,而类的静态方法是需要Class对象。所以所谓的类锁,不过是Class对象的锁而已。获取类的Class对象有好几种,最简单的就是MyClass.class的方式。
· 类锁和对象锁不是同1个东西,一个是类的Class对象的锁,一个是类的实例的锁。也就是说:1个线程访问静态synchronized的时候,允许另一个线程访问对象的实例synchronized方法。反过来也是成立的,因为他们需要的锁是不同的。
7. Android Looper用法及原理
Looper是android为线程间异步消息通信提供的一种机制,利用Looper机制可以方便我们实现多线程编程时线程间的相互沟通。当然,如果不用Looper而采用其它的线程间通信方式(像管道,信号量,共享内存,消息队列等)也是一样的。Looper的实现是利用消息队列的方式,为用户封装了一层API,用户不需要考虑互斥加锁的问题,方便用户的使用。
8.Handler机制相关概念
• Looper:一个线程可以产生一个Looper对象,由它来管理此线程里的MessageQueue(消息队列)。
• Handler:你可以构造Handler对象来与Looper沟通,以便push新消息到MessageQueue里;或者接收Looper从Message Queue取出发送来的消息。
• Message Queue(消息队列):用来存放线程放入的消息。
• 线程:UIthread 通常就是main thread,而Android启动程序时会替它建立一个MessageQueue。
8. final、finally、finalize的区别
一、final
如果一个类被声明为final,意味着它不能再派生出新的子类,不能作为父类被继承。因此一个类不能既被声明为 abstract的,又被声明为final的。将变量或方法声明为final,可以保证它们在使用中不被改变。被声明为final的变量必须在new一个对象时初始化(即只能在声明变量或构造器或代码块内初始化),而在以后的引用中只能读取,不可修改。被声明为final的方法也同样只能使用,不能覆盖(重写)。
二、finally
在异常处理时提供 finally 块来执行任何清除操作。如果抛出一个异常,那么相匹配的 catch 子句就会执行,然后控制就会进入 finally 块(如果有的话)。
finally和return的执行顺序:
https://blog.csdn.net/jdfk423/article/details/80406297
情况一:
如果程序是从try代码块或者catch代码块中返回时,finally中的代码总会执行。而且finally语句在return语句执行之后return返回之前执行的。可以使用编译器的Debug功能查看详细过程。
情况二:
我们可以看到当finally有返回值时,会直接返回。不会再去返回try或者catch中的返回值。
情况三:
如果try和catch的return是一个变量时且函数的是从其中一个返回时,后面finally中语句即使有对返回的变量进行赋值的操作时,也不会影响返回的值。
三、finalize
方法名。Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在 Object 类中定义的,因此所有的类都继承了它。子类覆盖 finalize() 方法以整理系统资源或者执行其他清理工作。finalize() 方法是在垃圾收集器删除对象之前对这个对象调用的。注意:finalize不一定被jvm调用,只有当垃圾回收器要清除垃圾时才被调用。
ActivityThread,AMS,WMS的工作原理
1.ActivityThread:是Android应用的主线程(UI线程)
2.WMS(WindowManagerService)管理的整个系统所有窗口的UI
作用:
为所有窗口分配Surface:客户端向WMS添加一个窗口的过程,其实就是WMS为其分配一块Suiface的过程,一块块Surface在WMS的管理下有序的排布在屏幕上。Window的本质就是Surface。(简单的说Surface对应了一块屏幕缓冲区)
管理Surface的显示顺序、尺寸、位置
管理窗口动画
输入系统相关:WMS是派发系统按键和触摸消息的最佳人选,当接收到一个触摸事件,它需要寻找一个最合适的窗口来处理消息,而WMS是窗口的管理者,系统中所有的窗口状态和信息都在其掌握之中,完成这一工作不在话下。
3.AMS(ActivityManagerService)
ActivityManager是客户端用来管理系统中正在运行的所有Activity包括Task、Memory、Service等信息的工具。但是这些这些信息的维护工作却不是又ActivityManager负责的。在ActivityManager中有大量的get()方法,那么也就说明了他只是提供信息给AMS,由AMS去完成交互和调度工作。
作用:
统一调度所有应用程序的Activity的生命周期
启动或杀死应用程序的进程
启动并调度Service的生命周期
注册BroadcastReceiver,并接收和分发Broadcast
启动并发布ContentProvider
调度task
处理应用程序的Crash
查询系统当前运行状态
volatile关键字的作用
1 保证内存可见性(变量i加上volatile关键字修饰的话,它可以保证当A线程对变量i值做了变动之后,会立即刷回到主内存中,而其它线程读取到该变量的值也作废,强迫重新从主内存中读取该变量的值,这样在任何时刻,AB线程总是会看到变量i的同一个值
)
2 禁止指令重排序(单例)
3 不保证原子性
注释:尽管volatile关键字可以保证内存可见性和有序性,但不能保证原子性。也就是说,对volatile修饰的变量进行的操作,不保证多线程安全
多线程安全问题,可以采取加锁synchronized的方式,也可以使用concurrent包下的原子类AtomicInteger