Java
一、顺序表和链表的区别++++
顺序表
顺序表
是用一段物理地址连续的存储单元依次存储数据元素的线性结构
,一般情况下采用数组存储
。在数组上完成数据的增删查改。
顺序表一般可以分为:
- 静态顺序表:使用定长数组存储。
- 动态顺序表:使用动态开辟的数组存储。
静态顺序表适用于确定知道需要存多少数据的场景. 静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用.
相比之下动态顺序表更灵活, 根据需要动态的分配空间大小.
顺序表中间/头部的插入删除,时间复杂度为O(N)
增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗.
增容一般是呈2倍的增长,势必会有一定的空间浪费。
链表
链表
是一种物理存储结构上非连续存储结构
,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:单向、双向 带头、不带头 循环、非循环
无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希、图的邻接表等等。另外这种结构在笔试面试中出现很多.
无头双向链表:在Java的集合框架库中LinkedList底层实现就是无头双向循环链表。
顺序表:
优点:空间连续、支持随机访问
缺点:
1.中间或前面部分的插入删除时间复杂度O(N)
2.增容的代价比较大。
链表:
缺点:以节点为单位存储,不支持随机访问
优点:
1.任意位置插入删除时间复杂度为O(1)
2.没有增容问题,插入一个开辟一个空间。
二、HashMap的原理,存取的流程+++
hashmap在存数据的时候是基于hashing的原理,当调用put(key,value)方法的时候,会先对键key调用key.hashcode()方法,根据方法返回的hashcode来找到bucket的位置来存Entry对象。(Entry对象存有key和value)。
根据hashcode找到对应的bucket之后,还会在对应的链表逐一检查这个链表里有没存在相同的key对象,这个时候是通过equals这个方法来对比的。如果有,者用新的value取代旧的value。如果没有,在链表的尾部加上这个新的Entry对象。
总结:
- 当我们调用get(key)的时候,会调用key的hashcode方法获得hashcode.
- 根据hashcode获取相应的bucket。
- 由于一个bucket对应的链表中可能存有多个Entry,这个时候会调用key的equals方法来找到对应的Entry
- 最后把值返回
三、http和https的区别+++
HTTP和HTTPS的基本概念
HTTP:是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准(TCP),用于从WWW服务器传输超文本到本地浏览器的传输协议,它可以使浏览器更加高效,使网络传输减少。
HTTPS:是以安全为目标的HTTP通道,简单讲是HTTP的安全版,即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。
HTTPS协议的主要作用可以分为两种:一种是建立一个信息安全通道,来保证数据传输的安全;另一种就是确认网站的真实性。
HTTP与HTTPS有什么区别?
HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全,为了保证这些隐私数据能加密传输,于是网景公司设计了SSL(Secure Sockets Layer)协议用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPS。简单来说,HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全。
HTTPS和HTTP的区别主要如下:
- https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
- http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
- http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
- http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
HTTPS的工作原理
- 客户使用https的URL访问Web服务器,要求与Web服务器建立SSL连接。
- Web服务器收到客户端请求后,会将网站的证书信息(证书中包含公钥)传送一份给客户端。
- 客户端的浏览器与Web服务器开始协商SSL连接的安全等级,也就是信息加密的等级。
- 客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站。
- Web服务器利用自己的私钥解密出会话密钥。
-
Web服务器利用会话密钥加密与客户端之间的通信。
HTTPS的优点
尽管HTTPS并非绝对安全,掌握根证书的机构、掌握加密算法的组织同样可以进行中间人形式的攻击,但HTTPS仍是现行架构下最安全的解决方案,主要有以下几个好处:
- 使用HTTPS协议可认证用户和服务器,确保数据发送到正确的客户机和服务器;
- HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全,可防止数据在传输过程中不被窃取、改变,确保数据的完整性。
- HTTPS是现行架构下最安全的解决方案,虽然不是绝对安全,但它大幅增加了中间人攻击的成本。
- 谷歌曾在2014年8月份调整搜索引擎算法,并称“比起同等HTTP网站,采用HTTPS加密的网站在搜索结果中的排名将会更高”。
HTTPS的缺点
虽然说HTTPS有很大的优势,但其相对来说,还是存在不足之处的:
- HTTPS协议握手阶段比较费时,会使页面的加载时间延长近50%,增加10%到20%的耗电;
- HTTPS连接缓存不如HTTP高效,会增加数据开销和功耗,甚至已有的安全措施也会因此而受到影响;
- SSL证书需要钱,功能越强大的证书费用越高,个人网站、小网站没有必要一般不会用。
- SSL证书通常需要绑定IP,不能在同一IP上绑定多个域名,IPv4资源不可能支撑这个消耗。
- HTTPS协议的加密范围也比较有限,在黑客攻击、拒绝服务攻击、服务器劫持等方面几乎起不到什么作用。最关键的,SSL证书的信用链体系并不安全,特别是在某些国家可以控制CA根证书的情况下,中间人攻击一样可行。
四、voliate 关键字++
五、Lru 算法+
略
六、泛型++
什么是泛型?
Java泛型设计原则:只要在编译时期没有出现警告,那么运行时期就不会出现ClassCastException异常.
泛型:把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型
参数化类型:
- 把类型当作是参数一样传递
- <数据类型> 只能是引用类型
相关术语:
- ArrayList<E>中的E称为类型参数变量
- ArrayList<Integer>中的Integer称为实际类型参数
- 整个称为ArrayList<E>泛型类型
- 整个ArrayList<Integer>称为参数化的类型ParameterizedType
为什么需要泛型
早期Java是使用Object来代表任意类型的,但是向下转型有强转的问题,这样程序就不太安全
首先,我们来试想一下:没有泛型,集合会怎么样
- Collection、Map集合对元素的类型是没有任何限制的。本来我的Collection集合装载的是全部的Dog对象,但是外边把Cat对象存储到集合中,是没有任何语法错误的。
- 把对象扔进集合中,集合是不知道元素的类型是什么的,仅仅知道是Object。因此在get()的时候,返回的是Object。外边获取该对象,还需要强制转换
有了泛型以后:
- 代码更加简洁【不用强制转换】
- 程序更加健壮【只要编译时期没有警告,那么运行时期就不会出现ClassCastException异常】
- 可读性和稳定性【在编写集合的时候,就限定了类型】
七、四大引用及其用到的地方+++
强引用
Object obj =new Object();
上述Object这类对象就具有强引用,属于不可回收的资源,垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠回收具有强引用的对象,来解决内存不足的问题。
值得注意的是:如果想中断或者回收强引用对象,可以显式地将引用赋值为null,这样的话JVM就会在合适的时间,进行垃圾回收。
软引用
如果一个对象只具有软引用,那么它的性质属于可有可无的那种。如果此时内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。
软引用有对应的实体列为SoftReference
,使用软引用引用的对象只有在程序发生oom异常前才会回收,也就是说如果内存充足永远不会被回收,只有在内存不足时才会回收,很好的避免oom,非常适合做缓存
弱引用
弱引用对应的实体类为WeakReference
如果一个对象具有弱引用,那其的性质也是可有可无的状态。
而弱引用和软引用的区别在于:弱引用的对象拥有更短的生命周期,只要垃圾回收器扫描到它,不管内存空间充足与否,都会回收它的内存。
虚引用
虚引用对应的实体类为PhantonReference
。虚引用不论所引用的对象是不是null,不论内存空间是否充足,都会被垃圾回收器回收
程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
引用总结
对于垃圾回收器回收的顺序为
虚引用---弱引用----软引用---强引用
- 多使用软引用做缓存可以很好地避免oom.
- 对于强引用,平时在编写代码时会经常使用。
- 而其他三种类型的引用,使用得最多就是软引用和弱引用,这两种既有相似之处又有区别,他们都来描述非必须对象。
- 被软引用关联的对象只有在内存不足时才会被回收,而被弱引用关联的对象在JVM进行垃圾回收时总会被回收。
八、泛型
Java的泛型是伪泛型,因为是向下兼容旧版本,编译之后都会泛型擦除
1.通配符:使得代码更加灵活,但会有限制(只读\只写)
<? extends Number> 上限通配符 只读
<? super Number> 下限通配符 只写
<?> 不受限制 不能使用T的方法
2.泛型擦除 编译后会把当前类擦除为第一个绑定类
//String等会被擦除为Object
九、多线程
-
thread.run()和thread.start()的区别?
run()只是方法调用,start()才是真正启动线程的方式
-
thread能停止线程吗?
不能强制执行,只能获取interrupt()是否拦截的返回值来确定是否取消
-
如何控制线程的顺序执行
可以使用join()方法
-
在java中能不能指定线程的执行
不能,java中是不能完成的,只有C才能完成
-
sleep和wait的区别
sleep是休眠,wait是等待,需要唤醒。wait会在等待的时候释放锁
-
让出当前线程执行权
yield方法
守护线程 : setDaemon(true)
ThreadLocal就是让每个线程在自己的内部拥有单独的map
十、String、StringBuffer与StringBuilder之间区别
String、StringBuffer与StringBuilder之间区别
String | StringBuffer | StringBuilder |
---|---|---|
String的值是不可变的,这就导致每次对String的操作都会生成新的String对象,不仅效率低下,而且浪费大量优先的内存空间 | StringBuffer是可变类,和线程安全的字符串操作类,任何对它指向的字符串的操作都不会产生新的对象。每个StringBuffer对象都有一定的缓冲区容量,当字符串大小没有超过容量时,不会分配新的容量,当字符串大小超过容量时,会自动增加容量 | 可变类,速度更快 |
不可变 | 可变 | 可变 |
线程安全 | 线程不安全 | |
多线程操作字符串 | 单线程操作字符串 |
小结:
- 如果要操作少量的数据用 String;
- 多线程操作字符串缓冲区下操作大量数据 StringBuffer;
- 单线程操作字符串缓冲区下操作大量数据 StringBuilder。
Android
一、事件分发++++
- 如果事件不被中断,整个事件流向是一个类U型图
dispatchTouchEvent 和 onTouchEvent 一旦return true,事件就停止传递了(到达终点)(没有谁能再收到这个事件)。对于return true我们经常说事件被消费了,消费了的意思就是事件走到这里就是终点,不会往下传,没有谁能再收到这个事件了。
dispatchTouchEvent 和 onTouchEvent return false的时候事件都回传给父控件的onTouchEvent处理。
总结
- 对于 dispatchTouchEvent,onTouchEvent,return true是终结事件传递。return false 是回溯到父View的onTouchEvent方法。
- ViewGroup 想把自己分发给自己的onTouchEvent,需要拦截器onInterceptTouchEvent方法return true 把事件拦截下来。
- ViewGroup 的拦截器onInterceptTouchEvent 默认是不拦截的,所以return super.onInterceptTouchEvent()=return false;
- View 没有拦截器,为了让View可以把事件分发给自己的onTouchEvent,View的dispatchTouchEvent默认实现(super)就是把事件分发给自己的onTouchEvent。
二、Handler 原理+++++
Android Handler消息机制原理最全解读
Android中handler机制原理详解
Handler所有问题灵魂拷问
handler的作用
handler是android线程之间的消息机制,主要的作用是将一个任务切换到指定的线程中去执行,(准确的说是切换到构成handler的looper所在的线程中去出处理)
- 在使用handler的时候,在handler所创建的线程需要维护一个唯一的Looper对象, 每个线程对应一个Looper,每个线程的Looper通过ThreadLocal来保证,Looper对象的内部又维护有唯一的一个MessageQueue,所以一个线程可以有多个handler,但是只能有一个Looper和一个MessageQueue。
- Message在MessageQueue不是通过一个列表来存储的,而是将传入的Message存入到了上一个
Message的next中,在取出的时候通过顶部的Message就能按放入的顺序依次取出Message。 - Looper对象通过loop()方法开启了一个死循环,不断地从looper内的MessageQueue中取出Message,
然后通过handler将消息分发传回handler所在的线程。
三、activity 四大启动及应用场景++++
standard
standard模式是默认的启动模式,不用为配置android:launchMode属性即可,当然也可以指定值为standard。不管有没有已存在的实例,都生成新的实例。
singleTop
跳转时系统会先在栈结构中寻找是否有一个该Activity实例正位于栈顶,如果有则不再生成新的,而是直接使用。如果发现有对应的Activity实例正位于栈顶,则重复利用,不再生成新的实例。
singleTask
如果在栈中已经有该Activity的实例,就重用该实例(会调用实例的onNewIntent())。重用时,会让该实例回到栈顶,因此在它上面的实例将会被移除栈。如果栈中不存在该实例,将会创建新的实例放入栈中。 如果在manifest中设置activity的launchMode为SingleTask或者是SingleTop 当activity任务栈存在该实例时,我们使用startActivity打开该activity时就会调用它的onNewIntent()方法而不是调用onCreate().
singleInstance
可分为两种情况:
- 如果将要启动的目标Activity不存在,系统将会创建一个全新的Task栈,在创建目标Activity实例,并将它加入到新的Task栈顶。
- 如果将要启动的目标Activity已经存在,无论它位于那个程序中、位于哪个Task栈中,系统都会将该Activity所在的Task转到前台,从而使该Activity显示出来。
在一个新栈中创建该Activity实例,并让多个应用共享该栈中的该Activity实例。一旦该模式的Activity的实例存在于某个栈中,任何应用再激活该Activity时都会重用该栈中的实例,其效果相当于多个应用程序共享一个应用,不管谁激活该Activity都会进入同一个应用中。
应用场景:
- singleTop适合接收通知启动的内容显示页面。例如,某个新闻客户端的新闻内容页面,如果收到10个新闻推送,每次都打开一个新闻内容页面是很烦人的。聊天的对话窗口,再例如QQ接受到消息后弹出Activity,如果一次来10条消息,总不能一次弹10个Activity。
- singleTask适合作为程序入口点。例如浏览器的主界面。不管从多少个应用启动浏览器,只会启动主界面一次,其余情况都会走onNewIntent,并且会清空主界面上面的其他页面。之前打开过的页面,打开之前的页面就ok,不再新建。
- singleTask:a界面购物,b界面确认订单,c界面付款,如果付款成功会跳到a,如果不付款则返回b,这时候重启a就会用到singleTask.
- singleInstance适合需要与程序分离开的页面。例如闹铃提醒,将闹铃提醒与闹铃设置分离。singleInstance不要用于中间页面,如果用于中间页面,跳转会有问题,比如:A -> B (singleInstance) -> C,完全退出后,在此启动,首先打开的是B。
四、OkHttp 源码+++
五、Retrofit 源码+
六、RxJava用过的多少操作符?map和flatMap的区别?+++
创建操作
- create: 使用OnSubscribe从头创建一个Observable
- from: 将一个Iterable, 一个Future, 或者一个数组,内部通过代理的方式转换成一个Observable
- just: 将一个或多个对象转换成发射这个或这些对象的一个Observable
- range: 创建一个发射指定范围的整数序列的Observable<Integer>
合并操作
- concat: 按顺序连接多个Observables。需要注意的是Observable.concat(a,b)等价于a.concatWith(b)。
- startWith: 在数据序列的开头增加一项数据。startWith的内部也是调用了concat
过滤操作
- filter: 过滤数据。内部通过OnSubscribeFilter过滤数据。
- take: 只发射开始的N项数据或者一定时间内的数据。
条件/布尔操作
- all: 判断所有的数据项是否满足某个条件,内部通过OperatorAll实现
- exists: 判断是否存在数据项满足某个条件
变换操作
- map: 对Observable发射的每一项数据都应用一个函数来变换。
- flatMap: 将Observable发射的数据变换为Observables集合,然后将这些Observable发射的数据平坦化的放进一个单独的Observable,内部采用merge合并。
工具集
- observeOn: 指定观察者观察Observable的调度器
- subscribeOn: 指定Observable执行任务的调度器
Rxjava操作符之辩解map和flatmap的区别,以及应用场景
共同点:
- 都是依赖FuncX(入参,返回值)进行转换(将一个类型依据程序逻辑转换成另一种类型,根据入参和返回值)
- 都能在转换后直接被subscribe
区别:
- map返回的是结果集,flatmap返回的是包含结果集的Observable(返回结果不同)
- map被订阅时每传递一个事件执行一次onNext方法,flatmap多用于多对多,一对多,再被转化为多个时,一般利用from/just进行一一分发,被订阅时将所有数据传递完毕汇总到一个Observable然后一一执行onNext方法(执行顺序不同)>>>>(如单纯用于一对一转换则和map相同)
- map只能单一转换,单一只的是只能一对一进行转换,指一个对象可以转化为另一个对象但是不能转换成对象数组(map返回结果集不能直接使用from/just再次进行事件分发,一旦转换成对象数组的话,再处理集合/数组的结果时需要利用for一一遍历取出,而使用RxJava就是为了剔除这样的嵌套结构,使得整体的逻辑性更强。)
flatmap既可以单一转换也可以一对多/多对多转换,flatmap要求返回Observable,因此可以再内部进行from/just的再次事件分发,一一取出单一对象(转换对象的能力不同)
使用场景:
- map适用于一对一转换,当然也可以配合flatmap进行适用
- flatmap适用于一对多,多对多的场景
七、ListView优化(笔试题两次,可忽视)++
八、内存优化++++
当界面不可见时释放内存
用户点击了Home键或者Back键退出应用,所有UI界面被隐藏,这时候应该释放一些不可见的时候非必须的资源。
使用ProGuard来剔除不需要的代码
使用 ProGuard 来剔除不需要的代码,移除任何冗余的,不必要的,或臃肿的组件,资源或库完善 APP 的内存消耗。
降低整体尺寸APK
您可以通过减少 APP 的整体规模显著减少 APP 的内存使用情况。文章:Android APK瘦身实践
优化布局层次
通过优化视图层次结构,以减少重叠的 UI 对象的数量来提高性能。
避免Bitmap浪费
Bitmap是内存消耗的大头,当使用时要及时回收。
Cursor关闭
如查询数据库的操作,使用到Cursor,也要对Cursor对象及时关闭。
监听器的注销
Android程序里面存在很多需要register与unregister的监听器,手动add的listener,需要记得及时remove这个listener。
九、activity之间传递数据+
六种方式
- 使用Intent的putExtra传递
- 使用Intent的Bundle传递
- 使用Activity销毁时传递数据
- SharedPreferences传递数据
- 使用序列化对象Seriazable
- 使用静态变量传递数据
十、livedata 原理 +++
- LiveData通常会配合ViewModel来使用,ViewModel负责触发数据的更新,更新会通知到LiveData,然后LiveData再通知活跃状态的观察者。
- 在fragment/activity里获取ViewModel的MutableLiveData然后调用observer方法添加订阅,需要传入自身作为LifeCycleOwner(新版本包中的 Fragment/Activity 会默认继承自 LifecycleOwner)
- 如果该LifecycleOwner已经destory,则直接无视。如果已经添加过,则会有已添加的提示。否则就会添加这个观察者。
- LifecycleOwner有继承Lifecycle的接口,所以可以感知或主动获取 LifecycleOwner 的状态。
- 假设当网络请求的数据改变时,会调用MutableLiveData的setValue()方法将数据放入LiveData。(setValue()在放入User的时候必须在主线程,否则会报错,而postValue则没有这个检查,而是会把数据传入到主线程)
- LiveData调用assertMainThread()检查是否在主线程,接着将要更新的数据赋给mData,然后调用 dispatchingValue()方法并传入null,将数据分发给各个观察者。
- 如果观察者不是活跃状态(通过LifecycleOwner获取状态),将不会通知此观察者。LiveData的实现还是要依赖于Lifecycle
十一、WebView优化++
Android WebView 优化梳理
1.针对加载webView中的资源时加快加载的速度优化(主要是针对图片)
原因:html代码下载到WebView后,webkit开始解析网页各个节点,发现有外部样式文件或者外部脚本文件时,会异步发起网络请求下载文件,但如果在这之前也有解析到image节点,那势必也会发起网络请求下载相应的图片。在网络情况较差的情况下,过多的网络请求就会造成带宽紧张,影响到css或js文件加载完成的时间,造成页面空白loading过久。
解决方法:告诉WebView先不要自动加载图片,等页面finish后再发起图片加载。
2.WebView硬件加速导致页面渲染闪烁
原因:4.0以上的系统我们开启硬件加速后,WebView渲染页面更加快速,拖动也更加顺滑。但有个副作用就是,当WebView视图被整体遮住一块,然后突然恢复时(比如使用SlideMenu将WebView从侧边滑出来时),这个过渡期会出现白块同时界面闪烁。
解决方法:是在过渡期前将WebView的硬件加速临时关闭,过渡期后再开启。
3.可以提前显示加载进度条
原因:WebView.loadUrl("url") 不会立马就回调 onPageStarted 或者 onProgressChanged 因为在这一时间段,WebView 有可能在初始化内核,也有可能在与服务器建立连接,这个时间段容易出现白屏,白屏用户体验是很糟糕的。
解决方法:提前显示进度条虽然不是提升性能 , 但是对用户体验来说也是很重要的一点。
4.WebView密码明文存储漏洞优化
原因:WebView 默认开启密码保存功能 mWebView.setSavePassword(true),如果该功能未关闭,在用户输入密码时,会弹出提示框,询问用户是否保存密码,如果选择”是”,密码会被明文保到 /data/data/com.package.name/databases/webview.db 中,这样就有被盗取密码的危险。
解决方法:通过 WebSettings.setSavePassword(false) 关闭密码保存提醒功能。
5.自定义加载异常error的状态页面,比如下面这些方法中可能会出现error
原因:当WebView加载页面出错时(一般为404 NOT FOUND,Android WebView会默认显示一个出错界面。当WebView加载出错时,会在WebViewClient实例中的onReceivedError(),还有onReceivedTitle方法接收到错误。
解决方法:自定义错误页面样式。
6.WebView加载证书错误
原因:webView加载一些别人的url时候,有时候会发生证书认证错误的情况。
解决方法:要将正常的呈现页面给用户,我们需要忽略证书错误,需要调用WebViewClient类的onReceivedSslError方法,调用handler.proceed()来忽略该证书错误。
7.WebView音频播放销毁后还有声音
原因:WebView页面中播放了音频,退出Activity后音频仍然在播放。
解决方法:需要在Activity的onDestory()中从父容器中移除WebView。
8.Android后台无法释放js导致发热耗电
原因:有些手机你如果webView加载的html里,有一些js一直在执行比如动画之类的东西,如果此刻webView 挂在了后台这些资源是不会被释放用户也无法感知。导致一直占有cpu 耗电特别快。
解决方法:WebView在后台的时候,会调用onStop方法,即此时关闭js交互,回到前台调用onResume再开启js交互。
十二、app的启动流程++
- 点击桌面图标,Launcher会通过Binder来向system_server进程发送startActivity请求
- system_server收到请求后向zygote进程发送创建进程的请求
- zygote进程通过scket fork一个子进程
- App进程通过Binder向system_server进程发起attachApplication请求
- system_server进程在做一系列的准备工作后,通过Binder向App进程发送scheduleLaunchActivity请求
- App进程的binder线程(ActivityThread)在收到请求后,通过handler向主线程发送LAUNCH_ACTIVITY消息
- 主线程收到Message后,通过反射机制创建目标Activity,并回调Activity.onCreate()等方法
十三、热修复原理+++
关于bug的概念:
- 代码功能不符合项目预期,即代码逻辑有问题。
- 程序代码不够健壮导致App运行时崩溃。
这两种情况一般是一个或多个class出现了问题,在一个理想的状态下,我们只需将修复好的这些个class更新到用户手机上的app中就可以修复这些bug了。但说着简单,要怎么才能动态更新这些class呢?其实,不管是哪种热修复方案,肯定是如下几个步骤:
下发补丁(内含修复好的class)到用户手机,即让app从服务器上下载(网络传输)
app通过"某种方式",使补丁中的class被app调用(本地更新)
这里的"某种方式",对本篇而言,就是使用Android的类加载器,通过类加载器加载这些修复好的class,覆盖对应有问题的class,理论上就能修复bug了。
安卓的类加载器在加载一个类时会先从自身DexPathList对象中的Element数组中获取(Element[] dexElements)到对应的类,之后再加载。采用的是数组遍历的方式,不过注意,遍历出来的是一个个的dex文件。
在for循环中,首先遍历出来的是dex文件,然后再是从dex文件中获取class,所以,我们只要让修复好的class打包成一个dex文件,放于Element数组的第一个元素,这样就能保证获取到的class是最新修复好的class了(当然,有bug的class也是存在的,不过是放在了Element数组的最后一个元素中,所以没有机会被拿到而已)。
十四、kotlin 的apply和let的区别++++
Kotlin之let,apply,run,with等函数区别
apply:apply函数是这样的,调用某对象的apply函数,在函数范围内,可以任意调用该对象的任意方法,并返回该对象
let:首先let()的定义是这样的,默认当前这个对象作为闭包的it参数,返回值是函数里面最后一行,或者指定return
run:run函数和apply函数很像,只不过run函数是使用最后一行的返回,apply返回当前自己的对象。(相当于apply和let的结合体)
函数名 | 定义 | block参数 | 闭包返回返回值 | 函数返回值 | extension | 其他 |
---|---|---|---|---|---|---|
repeat | fun repeat(times: Int, action: (Int) -> Unit) | 无 | Unit | Unit | 否 | 普通函数 |
with | fun <T, R> with(receiver: T, f: T.() -> R): R = receiver.f() | 无,可以使用this | Any | 闭包返回 | 否 | 普通函数 |
run | <R> run(block: () -> R): R | 无 | Any | 闭包返回 | 否 | 普通函数 |
let | fun <T, R> T.let(f: (T) -> R): R | it | Any | 闭包返回 | 是 | |
apply | fun <T> T.apply(f: T.() -> Unit): T | 无,可以使用this | Unit | this | 是 | |
run | fun <T, R> T.run(f: T.() -> R): R | 无,可以使用this | Any | 闭包返回 | 是 | |
also | fun <T> T.also(block: (T) -> Unit): T | it | Unit | this | 是 | |
takeIF | fun <T> T.takeIf(predicate: (T) -> Boolean): T? | it | Boolean | this 或 null | 是 | 闭包返回类型必须是Boolean |
takeUnless | fun <T> T.takeUnless(predicate: (T) -> Boolean): T? | it | Boolean | this 或 null | 是 | 闭包返回类型必须是Boolean |
十五、kotlin 的协程及其原理+++
Android Kotlin协程 coroutines 的真正入门
Kotlin Coroutines(协程) 完全解析(一),协程简介
Kotlin Coroutines(协程) 完全解析(二),深入理解协程的挂起、恢复与调度
协程:协程就像非常轻量级的线程。线程是由系统调度的,线程切换或线程阻塞的开销都比较大。而协程依赖于线程,但是协程挂起时不需要阻塞线程,几乎是无代价的,协程是由开发者控制的。所以协程也像用户态的线程,非常轻量级,一个线程中可以创建任意个协程。
总而言之:协程可以简化异步编程,可以顺序地表达程序,协程也提供了一种避免阻塞线程并用更廉价、更可控的操作替代线程阻塞的方法 -- 协程挂起。
挂起函数的工作原理
协程的内部实现使用了 Kotlin 编译器的一些编译技术,当挂起函数调用时,背后大致细节如下:
挂起函数或挂起 lambda 表达式调用时,都有一个隐式的参数额外传入,这个参数是Continuation
类型,封装了协程恢复后的执行的代码逻辑。
协程就是一段可以挂起和恢复执行的运算逻辑,而协程的挂起是通过挂起函数实现的,挂起函数用状态机的方式用挂起点将协程的运算逻辑拆分为不同的片段,每次运行协程执行的不同的逻辑片段。所以协程有两个很大的好处:一是简化异步编程,支持异步返回;而是挂起不阻塞线程,提供线程利用率。
十六、flutter的有状态和无状态的widget有什么区别+
十七、binder主要讲对比socker和 共享内存的拷贝和安全性++++
十八、AS代码生成APK的步骤
aapt把资源生成R文件,jdk工具把java代码生成.class,dx工具把class打包成一个或多个.dex文件,最后把所有资源打包压缩成apk,再通过签名
十九、屏幕适配方案
activity启动先获取到手机的density ,然后对比设计图的density ,算出百分比,最后设置回去,这样就可以等比缩放空间的大小
二十、Android 如何保证service在后台不被kill
onStartCommand方法,返回START_STICKY
START_STICKY 在运行onStartCommand后service进程被kill后,那将保留在开始状态,但是不保留那些传入的intent。不久后service就会再次尝试重新创建,因为保留在开始状态,在创建 service后将保证调用onstartCommand。如果没有传递任何开始命令给service,那将获取到null的intent。
START_NOT_STICKY 在运行onStartCommand后service进程被kill后,并且没有新的intent传递给它。Service将移出开始状态,并且直到新的明显的方法(startService)调用才重新创建。因为如果没有传递任何未决定的intent那么service是不会启动,也就是期间onstartCommand不会接收到任何null的intent。
START_REDELIVER_INTENT 在运行onStartCommand后service进程被kill后,系统将会再次启动service,并传入最后一个intent给onstartCommand。直到调用stopSelf(int)才停止传递intent。如果在被kill后还有未处理好的intent,那被kill后服务还是会自动启动。因此onstartCommand不会接收到任何null的intent。
提升service优先级
在AndroidManifest.xml文件中对于intent-filter可以通过android:priority = "1000"这个属性设置最高优先级,1000是最高值,如果数字越小则优先级越低,同时适用于广播。
提升service进程优先级
Android中的进程是托管的,当系统进程空间紧张的时候,会依照优先级自动进行进程的回收。Android将进程分为6个等级,它们按优先级顺序由高到低依次是:
- 前台进程( FOREGROUND_APP)
- 可视进程(VISIBLE_APP )
- 次要服务进程(SECONDARY_SERVER )
- 后台进程 (HIDDEN_APP)
- 内容供应节点(CONTENT_PROVIDER)
- 空进程(EMPTY_APP)
当service运行在低内存的环境时,将会kill掉一些存在的进程。因此进程的优先级将会很重要,可以使用startForeground 将service放到前台状态。这样在低内存时被kill的几率会低一些。
onDestory里面发送广播重启service
service +broadcast 方式,就是当service走ondestory的时候,发送一个自定义的广播,当收到广播的时候,重新启动service;
Application加上Persistent属性
监听系统广播判断Service状态
通过系统的一些广播,比如:手机重启、界面唤醒、应用状态改变等等监听并捕获到,然后判断我们的Service是否还存活,别忘记加权限啊。
双进程Service
让2个进程互相保护,其中一个Service被清理后,另外没被清理的进程可以立即重启进程
联系厂商,加入白名单
二十一、Android如何在Service中执行耗时操作
二十二、避免发生内存抖动的几点建议:
- 尽量避免在循环体内创建对象,应该把对象创建移到循环体外。
- 注意自定义View的onDraw()方法会被频繁调用,所以在这里面不应该频繁的创建对象。
- 当需要大量使用Bitmap的时候,试着把它们缓存在数组中实现复用。
- 对于能够复用的对象,同理可以使用对象池将它们缓存起来。
二十三、在Activity的onCreate方法中获取控件宽高的N种方法
- 利用ViewTreeObserver添加OnPreDrawListener监听,该监听在绘制控件前的一刹那进行回调,示例如下:
ViewTreeObserver vto = mView.getViewTreeObserver();
vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
public boolean onPreDraw() {
if (hasMeasured == false) {// 只执行一次就够了
Toast.makeText(MainActivity.this, "mView:width=" + mView.getWidth() + ";height=" + mView.getHeight(), Toast.LENGTH_SHORT).show();
hasMeasured = true;
}
return true;
}
});
注:此回调监听会在控件绘制时调用多次,在控件滑动的时候仍然会再次调用,所以定义了一个boolean类型的变量hasMeasured,保证方法体只执行一次。
- 利用ViewTreeObserver添加OnGlobalLayoutListener,OnGlobalLayoutListener 是ViewTreeObserver的内部类,当一个视图树的布局发生改变时,可以被ViewTreeObserver监听到,这是一个注册监听视图树的观察者(observer),在视图树的全局事件改变时得到通知,示例如下:
ViewTreeObserver vto2 = mView.getViewTreeObserver();
vto2.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
tv_show.getViewTreeObserver().removeGlobalOnLayoutListener(this);//移除注册,让代码只执行一次
Toast.makeText(MainActivity.this, "mView:width=" + mView.getWidth() + ";height=" + mView.getHeight(), Toast.LENGTH_SHORT).show();
}
});
- 利用view.post()方法
mView.post(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, "mView:width=" + mView.getWidth() + ";height=" + mView.getHeight(), Toast.LENG TH_SHORT).show();
}
});
二十四、handle为什么可以在子线程上做任务切换
例如现在有A、B两个线程,在A线程中有创建了handler,然后在B线程中调用handler发送一个message。
通过分析我们可以知道,当在A线程中创建handler的时候,同时创建了MessageQueue与Looper,Looper在A线程中调用loop进入一个无限的for循环从MessageQueue中取消息,当B线程调用handler发送一个message的时候,会通过msg.target.dispatchMessage(msg);将message插入到handler对应的MessageQueue中,Looper发现有message插入到MessageQueue中,便取出message执行相应的逻辑,因为Looper.loop()是在A线程中启动的,所以则回到了A线程,达到了从B线程切换到A线程的目的。
二十五、OKhttp简单工作流程
通过Call发起网络请求的准备工作
通过构造者模式添加 url,method,header,body 等完成一个请求的信息 Request 对象
val request = Request.Builder()
.url("")
.addHeader("","")
.get()
.build()
- 同样通过构造者模式创建一个 OkHttpClicent 实例,可以按需配置
val okHttpClient = OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.addInterceptor()
.build()
- 将Request 封装成 Call 对象并且发起网络请求
val call = okHttpClient.newCall(request)
//异步请求数据
call.enqueue(object :Callback{
override fun onFailure(call: Call, e: IOException) {}
override fun onResponse(call: Call, response: Response) {}
})
//同步请求数据
val response = call.execute()