Android最全面试题整理、还有Kotlin

目录

1.理解线程间通信

2.工作者线程(workerThread)与主线程(UI线程)的理解

3.通过Handler在线程间通信的原理

4.子线程发消息到主线程进行更新 UI,除了 handler 和 AsyncTask,还有什么?

5.子线程中能不能 new handler?为什么?

6. Handler、 Thread 和 HandlerThread 的差别

7.当Activity有多个Handler的时候,Message消息是否会混乱?怎么样区分当前消息由哪个Handler处理?

7.0.线程更新UI导致崩溃的原因?

7.1.ANR应用无响应

8.AsyncTask(异步任务)的工作原理

9.AsyncTask使用在哪些场景?它的缺陷是什么?如何解决?

10.Android中动画的类型:

11.理解Activity、View、Window三者之间的关系

11.0. Activity、Dialog、PopupWindow、Toast 与Window的关系

12.Android中Context详解:

13讲解一下Context

14.Android常用的数据存储方式(4种)

15.SharedPreference跨进程使用会怎么样?如何保证跨进程使用安全?

16. 数据库的操作类型有哪些,如何导入外部数据库?

16.1 .SQLite支持事务吗? 添加删除如何提高性能?

18.Android垃圾回收机制和程序优化System.gc( )

18.1 为什么图片需要用软引用,MVP模式中的view接口用弱引用

19.Android平台的优势和不足

20.0 Android中任务栈的分配

20. Activity组件生命周期、四种启动模式

21. Activity的启动过程(不要回答生命周期)

22.保存Activity状态

23.如何修改 Activity 进入和退出动画

24.Service组件

25.什么是 IntentService?有何优点?

26 是否使用过 IntentService,作用是什么, AIDL 解决了什么问题?

27.BoradcastReceiver组件

28.配置文件静态注册和在代码中动态注册两种方式的区别

29. ContentProvider(内容提供者)组件

30. Fragment

31.Fragment中add与replace的区别?

32.FragmentPagerAdapter 与 与 FragmentStatePagerAdapter 的区别与使用场景?

33.Activity静态添加Fragment

34. Activity动态加载Fragment

35.Intent

36.ViewPager

37关于Fragment中的控件的事件的监听

38. 使用View绘制视图

39.View的绘制流程

40.View,ViewGroup事件分发

41. Android的事件传递(分发)机制

42.0.Android中touch事件的传递机制是怎样的?

42.View的分发机制,滑动冲突

43.Android中跨进程通讯的几种方式

65.Android 线程间通信有哪几种方式(重要)

66.AIDL理解

67.AIDL 的全称是什么?如何工作?能处理哪些类型的数据?

68.什么是 AIDL?如何使用?

69. Android中页面的横屏与竖屏操作

70.横竖屏切换的Activity 生命周期变化?

71. 获取手机中屏幕的宽和高的方法

72.内存泄漏的相关原因

74. Android内存泄漏及管理

76. Android平台的虚拟机Dalvik

78. Android中的Binder机制

79. Android中的缓存机制

80. Android 中图片的三级缓存策略

81. Glide三级缓存

84.HybridApp WebView和JS交互

87.RecyclerView和ListView的区别

88.简述一下RecyclerView缓存机制?

89.recyclerView嵌套卡顿解决如何解决

90.Universal-ImageLoader,Picasso,Fresco,Glide对比

91.Xutils, OKhttp, Volley, Retrofit对比

92.请解释下 Android 程序运行时权限与文件系统权限的区别?

93.Framework 工作方式及原理,Activity 是如何生成一个 view 的,机制是什么?

94.Android 判断SD卡是否存在

95.Android与服务器交互的方式中的对称加密和非对称加密是什么?

96. 音视频相关类

97.SurfaceView和GLSurfaceView

99.说说JobScheduler

100.说说WorkManager

101.谈一谈startService和bindService的区别,生命周期以及使用场景?

102.Service如何进行保活?

103. 进程保活(不死进程)

104.热修复的原理

105.JNI

105.0谈谈对Android NDK的理解

106.Foundation(基础组件)

107.Architecture(架构组件)(非常重要)

108.Behavior(行为组件)

109.UI(界面组件)

110. Android设计模式之MVC

111 安卓mvc/mvp/mvvm

112.设计模式的六大原则

113.Android中的性能优化相关问题

114. Bitmap的使用及内存优化

115. app优化

116.性能优化(非常重要)

117. Android对HashMap做了优化后推出的新的容器类是什么?

118.谈谈你对安卓签名的理解

119.请解释安卓为啥要加签名机制?

120.权限管理系统(底层的权限是如何进行 grant 的)?

121. Kotlin 如何在 Android 上运行?

122. 为什么要使用 Kotlin?

123. 用var和val声明变量有什么区别?

124. 用val和const声明变量有什么区别?

125. Kotlin 中如何保证 null 安全?

126.安全调用(?.)和空值检查(!!)有什么区别?

127. Kotlin 中是否有像 java 一样的三元运算符?

128. Kotlin 中的 Elvis 运算符是什么?

129. 如何将 Kotlin 源文件转换为 Java 源文件?

130.你觉得Kotlin与Java混合开发时需要注意哪些问题?

131.@JvmStatic、@JvmOverloads、@JvmFiled 在 Kotlin 中有什么用?

132. Kotlin 中的数据类是什么?

133.Kotlin中的数据类型有隐式转换吗?为什么?

134.Kotlin中可以使用int、double、float等原始类型吗?

135. Kotlin 中的字符串插值是什么?

136. Kotlin 中的解构是什么意思?

137.在Kotlin中,何为解构?该如何使用?

139. 如何检查一个lateinit变量是否已经初始化?

140. Kotlin 中的 lateinit 和 lazy 有什么区别?

141.==操作符和===操作符有什么区别?

142. Kotlin 中的 forEach 是什么?

143. Kotlin 中的伴生对象是什么?

144.kotlin中Unit的应用以及和Java中void的区别?

155. Kotlin 中的 Java 静态方法等价物是什么?

156. Kotlin 中的 FlatMap 和 Map 有什么区别?

157. Kotlin中可以使用new关键字实例化一个类对象吗?

158. Kotlin 中的可见性修饰符是什么?

159. Kotlin中的可见性修饰符有哪些?相比于 Java 有什么区别?

160. 如何在 Kotlin 中创建 Singleton 类?

161. Kotlin 中的初始化块是什么?

162. Kotlin 中的构造函数有哪些类型?

163.主构造函数和次构造函数之间有什么关系吗?

164.构造函数中使用的默认参数类型是什么?

165.谈谈kotlin中的构造方法?有哪些注意事项?

166.Kotlin 中的扩展函数是什么

167.kotlin基础: From Java To Kotlin

167.常量与变量

168.null 声明

169.空判断

170.字符串拼接

180.换行

181.三元表达式

182.操作符

183. Kotlin 中什么时候使用 lateinit 关键字?

184.Kotlin 的延迟初始化: lateinit var 和 by lazy

185 Kotlin Tips:怎么用 Kotlin 去提高生产力(kotlin优势)

186. Kotlin数组和集合

187.Kotlin中的MutableList与List有什么区别?

5.5 Kotlin集合操作符

188.Kotlin 中集合遍历有哪几种方式?

189 说一下Kotlin的伴生对象(关键字companion)

190.Kotlin 顶层函数和属性

191. Kotlin 中的协程是什么?

192. Kotlin Coroutines 中的挂起函数是什么?

193. Kotlin Coroutines 中 Launch 和 Async 有什么区别?

194. Kotlin Coroutines 中的作用域是什么?

195. Kotlin Coroutines 中的异常处理是如何完成的?

196. 在 Kotlin 中如何在 switch 和 when 之间进行选择?

197. Kotlin 中的 open 关键字是做什么用的?

198. 什么是 lambdas 表达式?

199. Kotlin 中的高阶函数是什么?

200. Kotlin 中的扩展函数是什么?

201. Kotlin 中的中缀函数是什么?

202. Kotlin 中的内联函数是什么?

203. Kotlin 中的 noinline 是什么?

204. Kotlin 中的具体化类型是什么?

205. Kotlin 中的运算符重载是什么?

206. 解释在 Kotlin 中 let、run、with 和 apply 的用例。

207.kotlin中with、run、apply、let函数的区别?一般用于什么场景?

208. Kotlin 中的 pair 和 Triple 是什么?

209. Kotlin 中的标签是什么?

210. 使用密封类而不是枚举有什么好处?

211 协程是什么

212 kotlin中关键字data的理解?相对于普通的类有哪些特点?

213.谈谈Kotlin中的Sequence,为什么它处理集合操作更加高效?

214.说说 Kotlin中 的 Any 与Java中的 Object 有何异同?


1.理解线程间通信

线程是CPU调度的最小单位(进程是CPU分配资源的最小单位),在Android中主线程是不能够做耗时的操作的,子线程是不能更新UI的,而线程间的通信方式有很多,比如广播、接口回掉等,在Android中主要使用handler,handler通过调用sendMessage方法,将保存好的消息发送到MessageQueue中,而Looper对象不断地调用loop方法,从MessageQueue中取出message,交给handler处理,从而完成线程间通信。

2.工作者线程(workerThread)与主线程(UI线程)的理解

Android应用的主线程(UI线程)肩负着绘制用户界面,和及时响应用户操作的重任,为避免“用户点击按钮后没有反应”的状况,就要确保主线程时刻保持着较高的响应性,把耗时的任务移除主线程,交予工作者线程(即子线程)完成,常见的工作者线程有AsyncTask(异步任务)、IntentService、HandlerThread,他们本质上都是对线程或线程池的封装。

3.通过Handler在线程间通信的原理

Android中主线程是不能进行耗时操作的,子线程不能进行更新UI,所以就有了Handler,其作用就是实现线程之间的通信。

Handler在整个线程通信的过程中,主要有四个对象,Handler、Message、MessageQueue、Looper等,当应用创建时,就会在主线程中创建Handler对象,将要传递的信息保存到Message中,Handler通过调用sendMessage()方法将Message发送到MessageQueue中,Looper对象不断调用Loop( )方法从MessageQueue中取出Message交给handler进行处理,从而实现线程之间的通信。

4.子线程发消息到主线程进行更新 UI,除了 handler 和 AsyncTask,还有什么?

用 Activity 对象的 runOnUiThread 方法更新

在子线程中通过 runOnUiThread()方法更新UI:

如果在非上下文类中(Activity),可以通过传递上下文实现调用;

用 View.post(Runnabler)方法更新 UI

5.子线程中能不能 new handler?为什么?

不能,如果在子线程中直接 new Handler()会抛出异常 java.lang.RuntimeException: Can'tcreate handler inside thread that has not called

6. Handler、 Thread 和 HandlerThread 的差别

从Android中Thread(java.lang.Thread -> java.lang.Object)描述可以看出,Android的Thread没有对Java的Thread做任何封装,但是Android提供了一个继承自Thread的类HandlerThread(android.os.HandlerThread -> java.lang.Thread),这个类对Java的Thread做了很多便利Android系统的封装。

android.os.Handler可以通过Looper对象实例化,并运行于另外的线程中,Android提供了让Handler运行于其它线程的线程实现,也是就HandlerThread。HandlerThread对象start后可以获得其Looper对象,并且使用这个Looper对象实例Handler。

7.当Activity有多个Handler的时候,Message消息是否会混乱?怎么样区分当前消息由哪个Handler处理?

不会混乱,哪个Handler发送的消息,到时候也是这个handler处理。在发送消息的时候,会绑定target,这个target就是Handler本身,当需要handler调用dispatchMessage(msg)处理消息的时候,这个Handler就是发送消息时绑定的handler。

无论用哪一种方法发送消息,最终都会调用enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)来发送消息

7.0.线程更新UI导致崩溃的原因?

在触发绘制方法requestLayout中,有个checkThread方法:

void checkThread() {

        if (mThread != Thread.currentThread()) {

            throw new CalledFromWrongThreadException(

                    "Only the original thread that created a view hierarchy can touch its views.");

        }

    }

其中对mThread和当前线程进行了比较。而mThread是在ViewRootImpl实例化的时候赋值的。

所以崩溃的原因就是 view被绘制到界面时候的线程(也就是ViewRootImpl被创建时候的线程)和进行UI更新时候的线程不是同一个线程。

7.1.ANR应用无响应

ANR全名Application Not Responding, 也就是"应用无响应". 当操作在一段时间内系统无法处理时, 系统层面会弹出上图那样的ANR对话框,,造成ANR的主要原因是由于线程的任务在规定的时间内没有完成造成的;

产生原因:

(1)5s内无法响应用户输入事件(例如键盘输入, 触摸屏幕等).

(2)BroadcastReceiver在10s内无法结束

(3)Service 20s内无法结束(低概率)

解决方式:

(1)不要在主线程中做耗时的操作,而应放在子线程中来实现。如onCreate()和onResume()里尽可能少的去做创建操作。

(2)应用程序应该避免在BroadcastReceiver里做耗时的操作或计算。

(3)避免在Intent Receiver里启动一个Activity,因为它会创建一个新的画面,并从当前用户正在运行的程序上抢夺焦点。

(4)service是运行在主线程的,所以在service中做耗时操作,必须要放在子线程中。

8.AsyncTask(异步任务)的工作原理

AsyncTask是对Handler和线程池的封装,使用它可以更新用户界面,当然,这里的更新操作还是在主线程中完成的,但由于AsyncTask内部包含了一个Handler,所以可以发送消息给主线程,让他更新UI,另外AsyncTask内还包含了一个线程池,避免了不必要的创建和销毁线程的开销。

9.AsyncTask使用在哪些场景?它的缺陷是什么?如何解决?

AsyncTask 运用的场景就是我们需要进行一些耗时的操作,耗时操作完成后更新主线程,或者在操作过程中对主线程的UI进行更新。

缺陷:

AsyncTask中维护着一个长度为128的线程池,同时可以执行5个工作线程,还有一个缓冲队列,当线程池中已有128个线程,缓冲队列已满时,如果 此时向线程提交任务,将会抛出RejectedExecutionException。

解决:

由一个控制线程来处理AsyncTask的调用判断线程池是否满了,如果满了则线程睡眠否则请求AsyncTask继续处理。

10.Android中动画的类型:

帧动画:通过指定每一帧图片和播放的时间,有序地进行播放而形成动画效果;

补间动画:通过指定的view的初始状态、变化时间、方式等,通过一系列的算法去进行图形变换从而形成动画效果,主要有Alpha、Scale、Translate、Rotate四种效果;

属性动画:在Android3.0开始支持,通过不断改变view的属性,不断的重绘而形成动画效果;

11.理解Activity、View、Window三者之间的关系

使用一个比喻形容他们的关系,Activity像一个工匠(控制单元),Window像窗户(承载模型)、View像窗花(显示视图)、LayoutInflater像剪刀、XML配置像窗花图纸:

Activity构造的时候会初始化一个Window,准确的说应该是PhoneWindow;

这个PhoneWindow有一个”ViewRoot“,这个”ViewRoot”是一个View或者说是ViewGroup,是最初的根视图;

“ViewRoot”是通过addView方法来添加一个个View的,比如TextView、Button等;

这些view的事件的监听,是由WindowManagerService来接收消息,并且回调Activity函数,比如onClickListener、onKeyDown等;

11.0. Activity、Dialog、PopupWindow、Toast 与Window的关系

这是扩展的一题,简单的从创建方式的角度来说一说:

Activity。在Activity创建过程中所创建的PhoneWindow,是层级最小的Window,叫做应用Window,层级范围1-99。(层级范围大的Window可以覆盖层级小的Window)

Dialog。Dialog的显示过程和Activity基本相同,也是创建了PhoneWindow,初始化DecorView,并将Dialog的视图添加到DecorView中,最终通过addView显示出来。

但是有一点不同的是,Dialog的Window并不是应用窗口,而是子窗口,层级范围1000-1999,子Window的显示必须依附于应用窗口,也会覆盖应用级Window。这也就是为什么Dialog传入的上下文必须为Activity的Context了。

PopupWindow。PopupWindow的显示就有所不同了,它没有创建PhoneWindow,而是直接创建了一个View(PopupDecorView),然后通过WindowManager的addView方法显示出来了。

没有创建PhoneWindow,是不是就跟Window没关系了呢?

并不是,其实只要是调用了WindowManager的addView方法,那就是创建了Window,跟你有没有创建PhoneWindow无关。View就是Window的表现形式,只不过PhoneWindow的存在让Window形象更立体了一些。

所以PopupWindow也是通过Window展示出来的,而它的Window层级属于子Window,必须依附与应用窗口。

Toast。Toast和PopupWindow比较像,没有新建PhoneWindow,直接通过addView方法显示View即可。不同的是它属于系统级Window,层级范围2000-2999,所以无须依附于Activity。

四个比较下来,可以发现,只要想显示View,就会涉及到WindowManager的addView方法,也就用到了Window这个概念,然后会根据不同的分层依次显示覆盖到界面上。

不同的是,Activity和Dialog涉及到了布局比较复杂,还会有布局主题等元素,所以用到了PhoneWindow进行一个解耦,帮助他们管理View。而PopupWindow和Toast结构比较简单,所以直接新建一个类似DecorView的View,通过addView显示到界面。

12.Android中Context详解:

Context是一个抽象基类,译为上下文,也可理解为环境,是用户操作操作系统的一个过程,这个对象描述的是一个应用程序环境的全局信息,通过它可以访问应用程序的资源和相关的权限,简单的说Context负责Activity、Service、Intent、资源、Package和权限等操作。Context层次如下图:

第一层:Context抽象类;

第二层:一个ContextImpl的实现类,里面拥有一个PackageInfo类,PackageInfo类是关于整个包信息的类,一个ContextWraper是一个Context的包装类,里面有一个ContextImpl类的实例,通过整个实例去调用ContextImpl里面的方法;

第三层:Service和Application直接继承ContextWrapper,但是Activity需要引入主题,所以有了ContextThemeImpl类;

总结:在使用Context对象获取静态资源,创建单例对象或者静态方法时,多考虑Context的生命周期,不要使用Activity的Context,要使用生命周期较长的Application的Context对象,但并不是所有的情况都使用Application的Context,在创建Dialog、view等控件的时候,必须使用Activity的Context对象。

13讲解一下Context

Context是一个抽象基类。在翻译为上下文,也可以理解为环境,是提供一些程序的运行环境基础信息。Context下有两个子类,ContextWrapper是上下文功能的封装类,而ContextImpl则是上下文功能的实现类。而ContextWrapper又有三个直接的子类, ContextThemeWrapper、Service和Application。其中,ContextThemeWrapper是一个带主题的封装类,而它有一个直接子类就是Activity,所以Activity和Service以及Application的Context是不一样的,只有Activity需要主题,Service不需要主题。Context一共有三种类型,分别是Application、Activity和Service。这三个类虽然分别各种承担着不同的作用,但它们都属于Context的一种,而它们具体Context的功能则是由ContextImpl类去实现的,因此在绝大多数场景下,Activity、Service和Application这三种类型的Context都是可以通用的。不过有几种场景比较特殊,比如启动Activity,还有弹出Dialog。出于安全原因的考虑,Android是不允许Activity或Dialog凭空出现的,一个Activity的启动必须要建立在另一个Activity的基础之上,也就是以此形成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),因此在这种场景下,我们只能使用Activity类型的Context,否则将会出错。

getApplicationContext()和getApplication()方法得到的对象都是同一个application对象,只是对象的类型不一样。

Context数量 = Activity数量 + Service数量 + 1 (1为Application)

14.Android常用的数据存储方式(4种)

使用SharedPreference存储:保存基于xml文件存储的key-value键值对数据,通常用来存储一些简单的配置信息;

文件存储方式:Context提供了两个方法来打开数据文件的文件IO流;

SQLite存储数据:SQLite是轻量级的嵌入式数据库引擎,支持SQL语言;

网络存储数据:通过网络存储数据;

15.SharedPreference跨进程使用会怎么样?如何保证跨进程使用安全?

在两个应用的manifest配置中好相同的shartdUserId属性,A应用正常保存数据,B应用
createPackageContext("com.netease.nim.demo", CONTEXT_IGNORE_SECURITY)
获取context然后获取应用数据,为保证数据安全,使用加密存储

16. 数据库的操作类型有哪些,如何导入外部数据库?

(1) 增删改查

(2) 将外部数据库放在项目的res/raw目录下。因为安卓系统下数据库要放在data/data/packagename/databases的目录下,然后要做的就是将外部数据库导入到该目录下,操作方法是通过FileInputStream读取外部数据库,再用FileOutputStrean把读取到的东西写入到该目录下。

16.1 .SQLite支持事务吗? 添加删除如何提高性能?

在sqlite插入数据的时候默认一条语句就是一个事务,有多少条数据就有多少次磁盘操作 比如5000条记录也就是要5000次读写磁盘操作。

添加事务处理,把多条记录的插入或者删除作为一个事务

18.Android垃圾回收机制和程序优化System.gc( )

垃圾收集算法的核心思想:对虚拟机的可用内存空间,即堆空间中的对象进行识别,如果对象正在被引用,则称其为存活对象,反之,如果对象不再被引用,则为垃圾对象,可以回收其占据的空间,用于再分配。

JVM进行次GC的频率很高,但因为这种GC占用的时间极短,所以对系统产生的影响不大,但主GC对系统的影响很明显,触发主GC的条件主要有下:

当应用程序空闲时,即没有应用线程在运行时,GC会被调用,因为GC在优先级别最低的线程中进行,所以当应用繁忙时,GC就不会被调用;

Java堆内存不足时,主GC会被调用,当应用线程正在运行,并在运行过程中创建新对象时,若这时内存空间不足,JVM会强制调用主GC,以便回收内存用于新的分配,若GC一次之后仍无法满足,则会继续进行两次,若仍无法满足,则会报OOM错误;

System.gc( )函数的作用只是提醒虚拟机希望进行一次垃圾回收,但并不一定保证会进行,具体什么时候进行取决于虚拟机;

18.1 为什么图片需要用软引用,MVP模式中的view接口用弱引用

答:软引用对象是内存不足的时候会被回收,所以由于图片占用内存过大,我们选择再内存不足的时候就回收图片

       弱引用对象是GC回收的时候会被调用,同时当activity销毁的时候会进行GC。那么为什么MVP模式中的P层中的view要用弱引用呢,我们可以这样么考虑,我们希望activity关闭的同时view也被回收,如果activity关闭,那么会进行GC,那么弱引用对象就会回收。

19.Android平台的优势和不足

Android平台手机 5大优势:

开放性:Android平台首先就是其开放性,开发的平台允许任何移动终端厂商加入到Android联盟中来。显著的开放性可以使其拥有更多的开发者;

挣脱运营商的束缚:在过去很长的一段时间,手机应用往往受到运营商制约,使用什么功能接入什么网络,几乎都受到运营商的控制,而Android用户可以更加方便地连接网络,运营商的制约减少;

丰富的硬件选择:由于Android的开放性,众多的厂商会推出千奇百怪,功能特色各具的多种产品。功能上的差异和特色,却不会影响到数据同步、甚至软件的兼容;

开发商不受任何限制:Android平台提供给第三方开发商一个十分宽泛、自由的环境,不会受到各种条条框框的阻扰;

无缝结合的Google应用: Android平台手机将无缝结合这些优秀的Google服务如地图、邮件、搜索等;

Android平台手机几大不足:

安全和隐私:由于手机与互联网的紧密联系,个人隐私很难得到保守。除了上网过程中经意或不经意留下的个人足迹,Google这个巨人也时时站在你的身后,洞穿一切;

过分依赖开发商缺少标准配置:在Android平台中,由于其开放性,软件更多依赖第三方厂商,比如Android系统的SDK中就没有内置音乐播放器,全部依赖第三方开发,缺少了产品的统一性;

同类机型用户很少:在不少手机论坛都会有针对某一型号的子论坛,对一款手机的使用心得交流,并分享软件资源。而对于Android平台手机,由于厂商丰富,产品类型多样,这样使用同一款机型的用户越来越少,缺少统一机型的程序强化。

20.0 Android中任务栈的分配

Task实际上是一个Activity栈,通常用户感受的一个Application就是一个Task。从这个定义来看,Task跟Service或者其他Components是没有任何联系的,它只是针对Activity而言的。

Activity有不同的启动模式, 可以影响到task的分配

20. Activity组件生命周期、四种启动模式

在Activity的生命周期中,可以将Activity表现为3种状态:

激活态:当Acitivity位于屏幕前端,并可以获得用户焦点、收用户输入时,这种状态称为激活态,也可以称为运行态;

暂停态:当Activity在运行时被另一个Activity所遮挡并获取焦点,此时Activity仍然可见,也就是说另一个Activity是部分遮挡的,或者另一个Activity是透明的或半透明的,此时Activity处于暂停状态;

停止态:当Activity被另一个Activity完全遮挡不可见时处于停止状态,这个Activity仍然存在,它保留在内存中并保持所有状态和成员信息,但是当该设备内存不足时,该Activity可能会被系统杀掉以释放其占用的内存空间,当再次打开该Activity时,它会被重新创建;

Activity生命周期中的7个方法:

onCreate( ):当Activity被创建时调用;

onStart( ):当Activity被创建后将可见时调用;

onResume( ):(继续开始)当Activity位于设备最前端,对用户可见时调用;

onPause( ):(暂停)当另一个Activity遮挡当前Activity,当前Activity被切换到后台时调用;

onRestart( ):(重新启动)当另一个Activity执行完onStop()方法,又被用户打开时调用;

onStop( ):如果另一个Activity完全遮挡了当前Activity时,该方法被调用;

onDestory( ):当Activity被销毁时调用;

Activity的四种启动模式:standard、singleTop、singleTask和singleInstance,他们是在配置文件中通过android:LauchMode属性配置;

standard:默认的启动模式,每次启动会在任务栈中新建一个启动的Activity的实例;

SingleTop:如果要启动的Activity实例已位于栈顶,则不会重新创建该Activity的实例,否则会产生一个新的运行实例;

SingleTask:如果栈中有该Activity实例,则直接启动,中间的Activity实例将会被关闭,关闭的顺序与启动的顺序相同;

SingleInstance:该启动模式会在启动一个Activity时,启动一个新的任务栈,将该Activity实例放置在这个任务栈中,并且该任务栈中不会再保存其他的Activity实例;

Activity任务栈:即存放Activity任务的栈,每打开一个Activity时就会往Activity栈中压入一个Activity

任务,每当销毁一个Activity的时候,就会从Activity任务栈中弹出一个Activity任务,

由于安卓手机的限制,只能从手机屏幕获取当前一个Activity的焦点,即栈顶元素(

最上面的Activity),其余的Activity会暂居后台等待系统的调用;

关于任务栈的更多概念:

当程序打开时就创建了一个任务栈,用于存储当前程序的Activity,当前程序(包括被当前程序所调用的)所有的Activity都属于一个任务栈;

一个任务栈包含了一个Activity的集合,可以有序的选择哪个Activity和用户进行交互,只有任务栈顶的Activity才可以与用户进行交互;

任务栈可以移动到后台,并保留每一个Activity的状态,并且有序的给用户列出他们的任务,而且还不会丢失他们的状态信息;

退出应用程序时,当把所有的任务栈中所有的Activity清除出栈时,任务栈会被销毁,程序退出;

默认Acctivity启动方式的缺点:

每开启一次页面都会在任务栈中添加一个Activity,而只有任务栈中的Activity全部清除出栈时,任务栈被销毁,程序才会退出,这样就造成了用户体验差,需要点击多次返回才可以把程序退出了。

每开启一次页面都会在任务栈中添加一个Activity还会造成数据冗余重复数据太多,会导致内存溢出的问题(OOM)。

21. Activity的启动过程(不要回答生命周期)

app启动的过程有两种情况,第一种是从桌面launcher上点击相应的应用图标,第二种是在activity中通过调用startActivity来启动一个新的activity。

我们创建一个新的项目,默认的根activity都是MainActivity,而所有的activity都是保存在堆栈中的,我们启动一个新的activity就会放在上一个activity上面,而我们从桌面点击应用图标的时候,由于launcher本身也是一个应用,当我们点击图标的时候,系统就会调用startActivitySately(),一般情况下,我们所启动的activity的相关信息都会保存在intent中,比如action,category等等。我们在安装这个应用的时候,系统也会启动一个PackaManagerService的管理服务,这个管理服务会对AndroidManifest.xml文件进行解析,从而得到应用程序中的相关信息,比如service,activity,Broadcast等等,然后获得相关组件的信息。当我们点击应用图标的时候,就会调用startActivitySately()方法,而这个方法内部则是调用startActivty(),而startActivity()方法最终还是会调用startActivityForResult()这个方法。而在startActivityForResult()这个方法。因为startActivityForResult()方法是有返回结果的,所以系统就直接给一个-1,就表示不需要结果返回了。而startActivityForResult()这个方法实际是通过Instrumentation类中的execStartActivity()方法来启动activity,Instrumentation这个类主要作用就是监控程序和系统之间的交互。而在这个execStartActivity()方法中会获取ActivityManagerService的代理对象,通过这个代理对象进行启动activity。启动会就会调用一个checkStartActivityResult()方法,如果说没有在配置清单中配置有这个组件,就会在这个方法中抛出异常了。当然最后是调用的是Application.scheduleLaunchActivity()进行启动activity,而这个方法中通过获取得到一个ActivityClientRecord对象,而这个ActivityClientRecord通过handler来进行消息的发送,系统内部会将每一个activity组件使用ActivityClientRecord对象来进行描述,而ActivityClientRecord对象中保存有一个LoaderApk对象,通过这个对象调用handleLaunchActivity来启动activity组件,而页面的生命周期方法也就是在这个方法中进行调用。

22.保存Activity状态

onSaveInstanceState(Bundle)会在activity转入后台状态之前被调用,也就是onStop()方法之前,onPause方法之后被调用;

23.如何修改 Activity 进入和退出动画

可以通过两种方式

一 是通过定义 Activity的主题

通过设置主题样式在styles.xml中编辑如下代码:

  添加 themes.xml 文件:

  在 AndroidManifest.xml 中给指定的 Activity 指定 theme。

二 是通过覆写 Activity 的overridePendingTransition 方法。

覆写 overridePendingTransition方法

  overridePendingTransition(R.anim.fade, R.anim.hold);

24.Service组件

Service组件主要用于处理不干扰用户交互的后台操作,如更新应用、播放音乐等,Service组件既不会开启新的进程,也不会开启新的线程,它运行在应用程序的主线程中,Service实现的逻辑代码不能阻塞整个应用程序的运行,否则会引起应用程序抛出ANR异常。

Service组件常被用于实现以下两种功能(分别对应两种启动模式):

使用startService()方法启动Service组件,运行在系统的后台在不需要用户交互的前提下,实现某些功能;

使用bindService()方法启动Service组件,启动者与服务者之间建立“绑定关系”,应用程序可以与Service组件交互;

Service中常用的方法:

onBind(Intent intent):抽象方法绑定模式时执行的方法,通过Ibinder对象访问Service;

onCreate():Service组件创建时执行;

onDestroy():Service组件销毁时执行;

onStartCommand( Intent intent ,int flags ,int startId ):开始模式时执行的方法,每次执行startService()方法,该方法都会执行;

onUnBind( ):解除绑定时执行;

stop Self():停止Service组件;

Service组件的生命周期:Service有两种启动模式,startService方法或bindServce方法,启动方法决定了Service组件的生命周期和他所执行的生命周期方法:

通过statrtService方法启动Service组件,如果已经存在目标组件,则会直接调用onStartCommand方法,否则回调哦那Create()方法,创建Service对象,启动后的Service组件会一直运行在后台,与“启动者”无关联,其状态不会受“启动者”的影响,即使“启动者被销毁,Service组件还会继续运行,直到调用StopService()方法,或执行stopSelf()方法;

通过bindService()方法,启动Service组件,如果组件对象已经存在,则与之绑定,否则创建性的Service对象后再与之绑定,这种启动方法把Service组件与“启动者“绑定,Service返回Ibinder对象,启动者借助ServiceConnection对象实例实现与之交互,这种启动方式会将Service组件与”启动者“相关联,Service的状态会受到启动者的影响;

Service的启动模式详解

启动和停止Service组件的方法都位于context类中,再Activity中可以直接调用;

startService(Intent service):以Start模式启动Service组件,参数service是目标组件;

stopService(Intent service):停止start模式启动的Service组件,参数service是目标组件;

bindService(Intent service ,serviceConnection conn ,int flags):以Bind模式启动Service组件,参数service是目标组件,conn是与目标链接的对象,不可为NULL,flags是绑定模式;

unbindService(ServiceConnection conn):解除绑定模式启动的Service组件,conn是绑定时的链接对象;

25.什么是 IntentService?有何优点?

IntentService是 Service 的子类,比普通的 Service增加了额外的功能。先看 Service 本身存在两个问题:

Service 不会专门启动一条单独的进程,Service 与它所在应用位于同一个进程中;

Service 也不是专门一条新线程,因此不应该在Service 中直接处理耗时的任务;

IntentService 特征

会创建独立的 worker线程来处理所有的Intent请求;

会创建独立的 worker 线程来处理onHandleIntent()方法实现的代码,无需处理多线程问题;

所有请求处理完成后,IntentService会自动停止,无需调用 stopSelf()方法停止 Service;

为Service 的 onBind()提供默认实现,返回 null;

为 Service的 onStartCommand提供默认实现,将请求Intent添加到队列中;

26 是否使用过 IntentService,作用是什么, AIDL 解决了什么问题?

(1) IntentService继承自Service。由于Service运行在主线程,无法进行耗时操作。所以你需要在Service中开启一个子线程,并且在子线程中运行。为了简化这一操作,Android中提供了IntentService来进行这一处理。通过查看IntentService的源码可以看到,在onCreate中,我们开启了一个HandlerThread线程,之后获取HandlerThread线程中的Looper,并通过这个Looper创建了一个Handler。然后在onStart方法中通过这个Handler将intent与startId作为Message的参数进行发送到消息队列中,然后交由Handler中的handleMessage中进行处理。由于在onStart方法是在主线程内运行的,而Handler是通过工作者线程HandlerThread中的Looper创建的。所以也就是在主线程中发送消息,在工作者接收到消息后便可以进行一些耗时的操作。

(2) 进程间通信

27.BoradcastReceiver组件

广播机制是安卓系统中的通信机制,可以实现在应用程序内部或应用程序之间传递消息的作用,发出广播(或称广播)和接收广播是两个不同的动作,Android系统主动发出的广播称为系统广播,应用程序发出广播称为自定义广播,广播实质上是一个Intent对象,接收广播实质上是接收Intent对象。

接收广播需要自定义广播接收器类,继承自BoradcastReceiver类,BoradcastReceiver的生命周期很短,BoradcastReceiver对象在执行完onReceiver()方法后就会被销毁,在onReceiver方法中不能执行比较耗时的操作,也不能在onReceiver方法中开启子线程,因为当父线程被杀死后,他的子线程也会被杀死,从而导致线程不安全。

广播分为有序广播和无序广播

无序广播:通过sendBoradcast()方法发送的广播,普通广播对于接收器来说是无序的,没有优先级,每个接收器都无需等待即可以接收到广播,接收器之间相互是没有影响的,这种广播无法被终止,即无法阻止其他接收器的接收动作。

有序广播:通过sendOrderedBroadcast()方法发送的广播,有序广播对于接收器来说是有序的,有优先级区分的,接收者有权终止广播,使后续接收者无法接收到广播,并且接收者接收到广播后可以对结果对象进行操作。

注册广播接收器的方式:配置文件静态注册和在代码中动态注册。

配置文件中静态注册:BoradcastReceiver组件使用标签配置,写在application标签内部,receiver标签内的标签用于设置广播接收器能够响应的广播动作。

使用代码动态注册:使用context中的registerReceiver(BoradcastReceiver receiver ,IntentFileter filter)方法,参数receiver是需要注册的广播接收器,参数filter是用于选择相匹配的广播接收器,使用这种方法注册广播接收器,最后必须解除注册,解除注册使用context的unregisterReceiver(BoradcastReceiverreceiver)方法,参数receiver为要解除注册的广播接收器。

28.配置文件静态注册和在代码中动态注册两种方式的区别

静态注册(也称常驻型注册):这种广播接收器需要在Androidmainfest.xml中进行注册,这种方式注册的广播,不受页面生命周期的影响,即使退出了页面,也可以收到广播,这种广播一般用于开机自启动,由于这种注册方式的广播是常驻型广播,所以会占用CPU资源。

动态注册:是在代码中注册的,这种注册方式也叫非常驻型注册,会受到页面生命周期的影响,退出页面后就不会收到广播,我们通常将其运用在更新UI方面,这种注册方式优先级较高,最后需要解绑(解除注册),否则会导致内存泄漏)。

要点:使用广播机制更新UI的思路,在需要更新的Activity内定义一个继承自BroadcastReceiver的 内部类,在Activty中动态注册该广播接收器,通过广播接收器的的onReceiver()方法来更新UI。

29. ContentProvider(内容提供者)组件

Android系统将所有的数据都规定为私有,无法直接访问应用程序之外的数据,如果需要访问其他程序的数据或向其他程序提供数据,需要用到ContentProvider抽象类,该抽象类为存储和获取数据提供了统一的接口,ContentProvider对数据进行了封装,也在使用时不必关心数据的存储方式。

URI(统一资源标识符):一个Uri一般有三部分组成,分别是访问协议、唯一性标识字符串(或访问权限字符串)、资源部份。

ContentProvider实现对外部程序数据操作的思路:

在一个程序中自定义ContentProvider类(继承自ContentProvider),并实现其中的抽象方法,在配置文件中使用标签对其进行注册(标签中有3个很重要的属性:name为指明自定义的ContentProvider的全路径,authories为访问标识,exported是否允许外部应用访问),在另一个需要操作该程序数据的程序中,通过context类的getContentResolver方法获得ContentResolver类的实例对象,ContentResolver类执行增、删、改、查操作的方法与ContentProvider类的几乎一致,通过调用ContentResolver类实现类的方法来实现数据操作(在这些方法中仍然会用到Uri参数,其应与被操作数据的所在程序中的定义的Uri一致)。

30. Fragment

Android3.0引入Fragment技术,译为“碎片、片段”,在Activity中可以通过FragmentManager来添加、移除和管理所加入的Fragment,每个Fragment都有自己的布局、生命周期、交互事件的处理,由于Fragment是嵌入到Activity中的,所以Fragment的生命周期又和Activity的生命周期有密切的关联。

Fragment的生命周期的方法:

onAttach( ):当Fagment和Activity产生关联时被调用;

onCreate( ):当Fragment被创建时调用;

onCreateView():创建并返回与Fragment相关的view界面;

onViewCreate():在onCreateView执行完后立即执行;

onActivityCreated( ):通知Fragment,他所关联的Activity已经完成了onCreate的调用;

onStart( ):让Fragment准备可以被用户所见,该方法和Activity的onStart()方法相关联;

onResume():Fragment可见,可以和用户交互,该方法和Activity的onResume方法相关联;

onPause():当用户离开Fragment时调用该方法,此操作是由于所在的Activity被遮挡或者是在Activity中的另一个Fragment操作所引起的;

onStop():对用户而言,Fragment不可见时调用该方法,此操作是由于他所在的Activity不再可见或者是在Activity中的一个Fragment操作所引起的;

onDestroyView():ment清理和它的view相关的资源;

onDestroy():最终清理Fragment的状态;

onDetach():Fragment与Activity不再产生关联;

Fragment加载布局文件是在onCreateView()方法中使用LayoutInflater(布局加载器)的inflate( )方法加载布局文件。

Fragment中传递数据:当Activity加载Fragment的时候,往其内部传递参数,官方推荐使用Fragment的setArguments(Bundle bundle)方式传递参数,具体方法为在Activity中,对要传递参数的Fragment调用setArguments(Bundle bundle)方法,在接收参数的Fragment中调用context的getArguments()方法获取Bundle参数;

管理Fragment:通过调用context的getFragmentManager( )方法或者getsupportFragmentManager( )方法(这两个方法需要导入的包不同)来获取FragmentManager,使用FragmentManager对象,主要可以调用如下方法:

findFragmentById/findFragmentByTag:获取Activity中已经存在的Fragment;

getFragments( ):获取所有加入Activity中的Fragment;

begainTransaction( ):获取一个FragmentTransaction对象,用来执行Fragment的事物;

popBackStack():从Activity的后退栈中弹出Fragment;

addOnBackChagedListerner:注册一个侦听器以监视后退栈的变化;

FragmentTransaction:

在Activity中对Fragment进行添加、删除、替换以及执行其他的动作将引起Fragment的变化,叫做一个事务,事务是通过FragmentTransaction来执行的,可以用add、remove、replace、show、hide( )等方法构成事务,最后使用commit提交事务,参考代码:

getSupportFragmentManager( ).beginTransaction()

             .add(R.id.main_container,tFragment)

            .add(R.id.main_container,eFragment)

             .hide(eFragment).commit( );

其中R.id.main_container为布局中容纳Fragment的view的ID。

31.Fragment中add与replace的区别?

add不会重新初始化fragment,replace每次都会;
添加相同的fragment时,replace不会有任何变化,add会报IllegalStateException 异常;
replace remove 掉相同 id 的所有 fragment,然后在add 当前的这个 fragment,而 add 是覆盖前一个fragment。所以如果使用 add 一般会伴随 hide()show(),避免布局重叠;
使用 add,如果应用放在后台,或以其他方式被系统销毁,再打开时,hide()中引用的 fragment 会销毁,所以依然会出现布局重叠 bug,可以使用 replace 或使用 add时,添加一个 tag 参数;

32.FragmentPagerAdapter 与 与 FragmentStatePagerAdapter 的区别与使用场景?

FragmentPagerAdapter 的每个 Fragment 会持久的保存在 FragmentManager 中,只要用户可以返回到页面中,它都不会被销毁。因此适用于那些数据 相对静态的页,Fragment 数量也比较少的那种;FragmentStatePagerAdapter 只保留当前页面,当页面不可见时,该 Fragment 就会被消除,释放其资源。因此适用于那些 数据动态性较大、 占用内存较多,多 Fragment 的情况;

33.Activity静态添加Fragment

在Activity中加入静态的Fragment,只需要在Activity的layout布局文件中,添加Fragment标签,并指定特点的Fragment类。为了方便理解,这里将创建步骤写下:

创建Fragment类

当然需要在onCreateView()方法中 通过inflate.inflate(fragment布局文件,container,false) 方法绑定布局。

创建Activity类

在Activity的layout.xml文件中加入标签< Fragment >

Activity.xml

<XXXLayout>

         ...

         <Fragment 

         android:name="Fragment的包路径 "

         其他的标签用来设置布局位置

         />

         ...

</XXXlayout>

这样就实现了静态的添加,在Activity加载完布局文件进行界面展示的时候,就可以看见Fragment的界面。

34. Activity动态加载Fragment

和静态注册相似:创建Fragment并完成UI界面的修改。

在Activity的布局文件中使用 FrameLayout 标签替换Fragment

Activity.xml

<XXXLayout>

         ...

         <FrameLayout 

         amdroid:id="@+id/xxx"通过id动态获取布局

         其他的标签用来设置布局位置

         />

         ...

</XXXlayout>

为了实现动态添加需要在Activity中使用以下关键代码

         val fragmentManager = supportFragmentManaager

         val transation = fragmentManager.beginTransation()

         transation.replace(R.id.FrameLayout,Fragment)

         transation.commit()

动态添加步骤总结如下:

获取Activity的FragmentManager。

通过调用Activity的getSupportFragmentManaager()方法获得

开启事务transation ,调用FragmenManager的beginTransation()方法开启

添加Fragment实例到< frameLayout>中去,调用replace(R.id.FrameLayout,Fragment)

提交事务即可。commit()方法实现。

35.Intent

pendingIntent的flag种类

一共有四种:FLAG_ONE_SHOT, FLAG_NO_CREATE, FLAG_CANCEL_CURRENT, FLAG_UPDATE_CURRENT,都不使用可以传入0

FLAG_UPDATE_CURRENT:

如果要创建的PendingIntent已经存在了,那么在保留原先PendingIntent的同时,将原先PendingIntent封装的Intent中的extra部分替换为现在新创建的PendingIntent的intent中extra的内容

FLAG_CANCEL_CURRENT:

如果要创建的PendingIntent已经存在了,那么在创建新的PendingIntent之前,原先已经存在的PendingIntent中的intent将不能使用

FLAG_NO_CREATE:

如果要创建的PendingIntent尚未存在,则不创建新的PendingIntent,直接返回null

FLAG_ONE_SHOT:

相同的PendingIntent只能使用一次,且遇到相同的PendingIntent时不会去更新PendingIntent中封装的Intent的extra部分的内容

36.ViewPager

ViewPager是google SDK自带的一个附加包(android.support.V4)中的一个类,可视为一个可以实现一种卡片式左右滑动的View容器,使用该类类似于ListView,需要用到自定义的适配器PageAdapter,区别在于每次要获取一个view的方式,ViewPager准确的说应该是一个ViewGroup。

PagerAdapter是ViewPager的支持者,ViewPager调用它来获取所需显示的页面,而PagerAdapter也会在数据变化时,通知ViewPager,这个类也是FragmentPagerAdapter和FragmentStatePagerAdapter的基类。

FragmentPageAdapter和FragmentStatePagerAdapter的区别

FragmentPageAdapter:和PagerAdapter一样,只会缓存当前Fragment以及左边一个和右边一个,如果左边或右边不存在Fragment则不缓存;

FragmentStatePagerAdapter:当Fragment对用户不可见时,整个Fragment会被销毁,只会保存Fragment的状态,而在页面需要重新显示的时候,会生成新的页面;

综上,FragmentPagerAdapter适合页面较少的场合,而FragmentStatePagerAdapter则适合页面较多或页面的内容非常复杂(需要占用大量内存)的情况。

当实现一个PagerAdapter时,需要重写相关方法:

getCount( ):获得viewPager中有多少个view ;

destroyItem(viewGroup ,interesting,object):移除一个给定位置的页面;

instantiateItem(ViewGroup ,int ):将给定位置的view添加到viewgroup(容器中),创建并显示出来,返回一个代表新增页面的Object(key),key和每个view要有一一对应的关系;

isviewFromObject( ):判断instantiateItem(ViewGroup,int )函数所返回的key和每一个页面视图是否是代表的同一个视图;

综合使用ViewPager、Fragment和FragmentPagerAdapter:

自定义一个继承FragmentPagerAdapter的子类,重写其相关方法,当在实例化该子类时,可以传入Fragmnt集合(即要使用到的全部Fragmnet的集合),将ViewPager与该自定义的适配器实例绑定,为ViewPager设置OnPagerListener( )监听事件,重写OnPagerChangeListener的onPageSelected( )方法,实现页面的翻转。

37关于Fragment中的控件的事件的监听

在Fragment中的onActivityCreated( )生命周期方法中通过context的getActivity()方法获取到Fragment类相关联的Activity,并就此Activity通过findViewById( )方法来获取相关组件,再为组件添加监听事件。

38. 使用View绘制视图

View类是Android平台中各种控件的父类,是UI(用户界面)的基础构件,View相当于屏幕上的一块矩形区域,其重复绘制这个区域和处理事件,View是所有Weight类(组件类)的父类,其子类ViewGroup是所有Layout类的父类,如果需要自定义控件,也要继承View,实现onDraw方法和事件处理方法。

View绘制的流程:OnMeasure()——>OnLayout()——>OnDraw()

OnMeasure():测量视图大小,从顶层父View到子View递归调用measure方法,measure方法又回调OnMeasure。

OnLayout():确定View的位置,进行页面的布局,从顶层父View向子View的递归调用View.Layout方法的过程,即父View根据上一步measure子View所得到的布局大小和布局参数,将子View放到合适的位置上。

OnDraw():绘制视图,ViewGroup创建一个Canvas对象,调用OnDraw()方法,但OnDraw方法是个空方法,需要自己根据OnMeasure和OnLayout获取得到参数进行自定义绘制。

注意:在Activity的生命周期中没有办法获取View的宽高,原因就是View没有测量完。

Canvas类:Canvas类位于android.graphics包中,它表示一块画布,可以使用该类提供的方法,绘制各种图形图像,Canvas对象的获取有两种方式:一种是重写View.onDraw方法时,在该方法中Canvas对象会被当作参数传递过来,在该Canvas上绘制的图形图像会一直显示在View中,另一种是借助位图创建Canvas,参考代码:

Bitmap  bitmap = Bitmap.CreatBitmap(100,100,Bitmap.Config,ARGB_888);

Canvas  canvas = new Canvas(bitmap);

39.View的绘制流程

自定义控件:

1、组合控件。这种自定义控件不需要我们自己绘制,而是使用原生控件组合成的新控件。如标题栏。

2、继承原有的控件。这种自定义控件在原生控件提供的方法外,可以自己添加一些方法。如制作圆角,圆形图片。

3、完全自定义控件:这个View上所展现的内容全部都是我们自己绘制出来的。比如说制作水波纹进度条。

View的绘制流程:OnMeasure()——>OnLayout()——>OnDraw()

第一步:OnMeasure():测量视图大小。从顶层父View到子View递归调用measure方法,measure方法又回调OnMeasure。

第二步:OnLayout():确定View位置,进行页面布局。从顶层父View向子View的递归调用view.layout方法的过程,即父View根据上一步measure子View所得到的布局大小和布局参数,将子View放在合适的位置上。

第三步:OnDraw():绘制视图。ViewRoot创建一个Canvas对象,然后调用OnDraw()。六个步骤:

①、绘制视图的背景;

②、保存画布的图层(Layer);

③、绘制View的内容;

④、绘制View子视图,如果没有就不用;

⑤、还原图层(Layer);

⑥、绘制滚动条。

40.View,ViewGroup事件分发

[图片上传中...(image-862be6-1542630926849-0)]

1. Touch事件分发中只有两个主角:ViewGroup和View。ViewGroup包含onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent三个相关事件。View包含dispatchTouchEvent、onTouchEvent两个相关事件。其中ViewGroup又继承于View。

2.ViewGroup和View组成了一个树状结构,根节点为Activity内部包含的一个ViwGroup。

3.触摸事件由Action_Down、Action_Move、Aciton_UP组成,其中一次完整的触摸事件中,Down和Up都只有一个,Move有若干个,可以为0个。

4.当Acitivty接收到Touch事件时,将遍历子View进行Down事件的分发。ViewGroup的遍历可以看成是递归的。分发的目的是为了找到真正要处理本次完整触摸事件的View,这个View会在onTouchuEvent结果返回true。

5.当某个子View返回true时,会中止Down事件的分发,同时在ViewGroup中记录该子View。接下去的Move和Up事件将由该子View直接进行处理。由于子View是保存在ViewGroup中的,多层ViewGroup的节点结构时,上级ViewGroup保存的会是真实处理事件的View所在的ViewGroup对象:如ViewGroup0-ViewGroup1-TextView的结构中,TextView返回了true,它将被保存在ViewGroup1中,而ViewGroup1也会返回true,被保存在ViewGroup0中。当Move和UP事件来时,会先从ViewGroup0传递至ViewGroup1,再由ViewGroup1传递至TextView。

6.当ViewGroup中所有子View都不捕获Down事件时,将触发ViewGroup自身的onTouch事件。触发的方式是调用super.dispatchTouchEvent函数,即父类View的dispatchTouchEvent方法。在所有子View都不处理的情况下,触发Acitivity的onTouchEvent方法。

7.onInterceptTouchEvent有两个作用:1.拦截Down事件的分发。2.中止Up和Move事件向目标View传递,使得目标View所在的ViewGroup捕获Up和Move事件。

41. Android的事件传递(分发)机制

基础概念:

事件分发的对象:点击事件(Touch事件),当用户触摸屏幕时(view或viewGroup派生的控件),将产生点击事件(Touch事件),Touch事件的相关细节(发生触摸的位置、时间等)被封装程MotionEvent对象;

事件的类型:

MotionEvent . ACTION_DOWN 按下view(所有事件的开始)

MotionEvent .ACTION_UP 抬起view(与DOWN对应)

MotionEvent .ACTION_MOVE 滑动view

MotionEvent .ACTION_CANCEL 结束事件(非人为的原因)

事件列:从手指触摸至离开屏幕,这个过程中产生的一系列事件为事件列,一般情况下,事件列都是以DOWN事件开始,UP事件结束,中间有无数的MOVE事件;

事件分发的本质:将点击事件(MotionEvent)传递到某个具体的View&处理的整个过程,即事件的传递过程=分发过程;

事件在哪些对象之间传递:Activity、viewGroup、view等对象;

事件分发过程中协作完成的方法:

dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()方法;

Android事件传递机制跟布局的层次密切相关,一个典型的布局包括根节点ViewGroup、子ViewGroup、以及最末端的view(如下图):

在这种结构中最上层的是RootViewGroup,下层是子view,当我们点击子view的时候,点击事件从上层往下层依次传递(即下沉传递Activity——ViewGroup——View),传递的过程中调用dispatchTouchEvent和onInterceptTouchEvent函数,当事件传递到被点击的子view之后,停止事件的传递,开始改变方向(即冒泡响应View ——ViewGroup——Activity),依次向上层响应,响应的过程调用onTouch或onTouchEvent方法。

传递过程中的协作方法:

public boolean dispatchTouchEvent(MotionEvent ev):事件分发处理函数,通常会在Activity层根据UI的情况,把事件传递给相应的ViewGroup;

public boolean onIntereptTouchEvent(MotionEvent ev):对分发的事件进行拦截(注意拦截ACTION_DOWN和其他ACTION的差异),有两种情况:

第一种情况:如果ACTION_DOWN的事件没有被拦截,顺利找到了TargetView,那么后续的MOVE和UP都能够下发,如果后续的MOVE与UP下发时还有继续拦截的话,事件只能传递到拦截层,并且发出ACTION_CANCEL;

第二种情况:如果ACTION_DOWN的事件下发时被拦截,导致没有找到TargetView,那么后续的MOVE和UP都无法向下派发了,在Activity层就终止了传递。

public boolean onTouchEvent(MotionEvent ev):响应处理函数,如果设置对应的Listener的话,这里还会与OnTouch、onClick、onLongClick相关联,具体的执行顺序是:

onTouch()——>onTouchEvent()——>onClick()——>onLongClick(),是否能够顺序执行,取决于每个方法返回值是true还是false。

42.0.Android中touch事件的传递机制是怎样的?

1.Touch事件传递的相关API有dispatchTouchEvent、onTouchEvent、onInterceptTouchEvent

2.Touch事件相关的类有View、ViewGroup、Activity

3.Touch事件会被封装成MotionEvent对象,该对象封装了手势按下、移动、松开等动作

4.Touch事件通常从Activity#dispatchTouchEvent发出,只要没有被消费,会一直往下传递,到最底层的View。

5.如果Touch事件传递到的每个View都不消费事件,那么Touch事件会反向向上传递,最终交由Activity#onTouchEvent处理.

6.onInterceptTouchEvent为ViewGroup特有,可以拦截事件.

7.Down事件到来时,如果一个View没有消费该事件,那么后续的MOVE/UP事件都不会再给它

42.View的分发机制,滑动冲突

View的事件传递顺序有3个重要的方法,dispatchTouchEvent()是否消耗了本次事件,onInterceptTouchEvent()是否拦截了本次事件,onTouchEvent()是否处理本次事件,滑动冲突分为同方向滑动冲突,例如ScrollViewListView,同方向滑动冲突,可以计算ListView高度而动态设置ListView的高度,ScrollView高度可变。例如ViewPagerListView,不同方向滑动冲突,一个是横向滑动一个是竖直滑动,不同方向滑动可以判断滑动的xy轴是横向还是竖直滑动,如果判断得到是横向滑动,就拦截ListView的事件,竖则反之。

43.Android中跨进程通讯的几种方式

Android 跨进程通信,像intent,contentProvider,广播,service都可以跨进程通信。

intent:这种跨进程方式并不是访问内存的形式,它需要传递一个uri,比如说打电话。

contentProvider:这种形式,是使用数据共享的形式进行数据共享。

service:远程服务,aidl

广播

65.Android 线程间通信有哪几种方式(重要)

1.共享内存(变量)

2.文件,数据库

3.Handler

4.Java 里的 wait(),notify(),notifyAll()

66.AIDL理解

此处延伸:简述Binder

AIDL: 每一个进程都有自己的Dalvik VM实例,都有自己的一块独立的内存,都在自己的内存上存储自己的数据,执行着自己的操作,都在自己的那片狭小的空间里过完自己的一生。而aidl就类似与两个进程之间的桥梁,使得两个进程之间可以进行数据的传输,跨进程通信有多种选择,比如 BroadcastReceiver , Messenger 等,但是 BroadcastReceiver 占用的系统资源比较多,如果是频繁的跨进程通信的话显然是不可取的;Messenger 进行跨进程通信时请求队列是同步进行的,无法并发执行。

Binde机制简单理解:

在Android系统的Binder机制中,是有Client,Service,ServiceManager,Binder驱动程序组成的,其中Client,service,Service Manager运行在用户空间,Binder驱动程序是运行在内核空间的。而Binder就是把这4种组件粘合在一块的粘合剂,其中核心的组件就是Binder驱动程序,Service Manager提供辅助管理的功能,而Client和Service正是在Binder驱动程序和Service Manager提供的基础设施上实现C/S 之间的通信。其中Binder驱动程序提供设备文件/dev/binder与用户控件进行交互,

Client、Service,Service Manager通过open和ioctl文件操作相应的方法与Binder驱动程序进行通信。而Client和Service之间的进程间通信是通过Binder驱动程序间接实现的。而Binder Manager是一个守护进程,用来管理Service,并向Client提供查询Service接口的能力

67.AIDL 的全称是什么?如何工作?能处理哪些类型的数据?

AIDL全称 Android Interface Definition Language(AndRoid 接口描述语言) 是一种接口描述语言; 编译器可以通过 aidl文件生成一段代码,通过预先定义的接口达到两个进程内部通信进程跨界对象访问的目的。需要完成两件事情:

1.引入AIDL的相关类.;

2.调用aidl产生的 class

理论上, 参数可以传递基本数据类型和 String, 还有就是 Bundle的派生类, 不过在Eclipse中,目前的 ADT不支持Bundle 做为参数。

68.什么是 AIDL?如何使用?

aidl是 Android interface definition Language 的英文缩写,意思 Android 接口定义语言。

使用aidl可以帮助我们发布以及调用远程服务,实现跨进程通信。

将服务的 aidl 放到对应的 src目录,工程的 gen目录会生成相应的接口类

我们通过 bindService(Intent,ServiceConnect,int)方法绑定远程服务,在 bindService中 有 一 个 ServiceConnect接 口 , 我 们 需 要 覆 写 该 类 的onServiceConnected(ComponentName,IBinder)方法,这个方法的第二个参数IBinder对象其实就是已经在 aidl中定义的接口,因此我们可以将IBinder 对象强制转换为aidl中的接口类。我们通过IBinder 获取到的对象(也就是 aidl文件生成的接口)其实是系统产生的代理对象,该代理对象既可以跟我们的进程通信, 又可以跟远程进程通信, 作为一个中间的角色实现了进程间通信。

69. Android中页面的横屏与竖屏操作

在配置文件中为Activity设置属性 android:screenOrientation = “ string ” ; 其中string为属性值,landscape(横屏)或portrait(竖屏);

在代码中设置:

                 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);为防止切换后重新启动当前Activity,应在配置文件中添加android:configChanges=“keyboardHidden|orientation”属性,并在Activity中重写onConfigurationChanged方法。

70.横竖屏切换的Activity 生命周期变化?

不设置 Activity 的 android:configChanges 时,切屏会销毁当前Activity,然后重新加载调用各个生命周期,切横屏时会执行一次,切竖屏时会执行两次;onPause()→onStop()→onDestory()→onCreate()→onStart()→onResume()
设置 Activity 的 android:configChanges=" orientation",经过机型测试
在 Android5.1 即 即 API 3 23 级别下,切屏还是会重新调用各个生命周期,切横、竖屏时只会执行一次
在 Android9 即 即 API 8 28 级别下,切屏不会重新调用各个生命周期,只会执行 onConfigurationChanged方法
官方纠正后,原话如下
如果您的应用面向 Android 2 3.2 即 即 API 级别 3 13 或更
高级别(按照 minSdkVersion 和 targetSdkVersion)

71. 获取手机中屏幕的宽和高的方法

通过Context的getWindowManager()方法获得WindowManager对象,再从WindowManager对象中通过getDefaultDisplay获得Display对象,再从Display对象中获得屏幕的宽和高。

72.内存泄漏的相关原因

如果一个无用的对象(不需要再使用的对象)仍然被其他对象持有引用,造成该对象无法被系统回收,以致该对象在堆中所占有的内存单元无法被释放而造成内存空间浪费,这种情况就是内存泄漏,常见的内存泄露的场景有:

单例模式:因为单例的静态特性使得他的生命周期同应用的生命周期一样长,如果一个对象已经没有用处了,但是单例还持有他的引用,那么在某个应用程序的生命周期他都不能正常回收,从而导致内存泄漏;

静态变量导致内存泄漏:静态变量存储在方法区,他的生命周期从类加载开始,到整个进程结束,一旦静态变量初始化,他所持有的引用只有等到进程结束才会释放;

非静态内部类导致内存泄漏:非静态内部类(包括匿名内部类)默认就会持有外部类的引用,当非静态内部类对象的生命周期比外部对象的生命周期长时,就会导致内存泄漏,通常在Android开发中,如果要使用内部类,但又要规避内存泄漏,一般会采用静态内部类+弱引用的方式;

未取消注册或回掉导致内存泄漏:比如在Activity中注册广播,如果在Activity销毁后不取消注册,那么这个广播会一直存在于系统中,同非静态内部类一样持有Activity引用,导致内存泄漏,因此在注册广播后一定要取消注册;

74. Android内存泄漏及管理

内存溢出(out of memory):是指程序在申请内存时,没有足够的内存空间供其使用,出现out of Memory 的错误,比如申请了一个integer,但给它存了long才能存下的数,这就是内存溢出,通俗来讲,内存溢出就是内存不够用。

内存泄漏(Memory Leak):是指程序使用new关键字向系统申请内存后,无法释放已经申请的内存空间,一次内存泄漏可以忽略,但多次内存泄漏堆积后会造成很严重的后果,会占用光内存空间。

以发生的方式来看,内存泄漏可以分为以下几类:

常发性内存泄漏:发生内存泄漏的代码会被执行多次,每次执行都会导致一块内存泄漏;

偶发性内存泄漏:发生内存泄露的代码只有在某些特定的环境或情况下才会发生;

一次性内存泄漏:发生内存泄漏的代码只会被执行一次,或是由于算法上的缺陷,导致总会有一块且仅有一块内存发生泄漏;

隐式内存泄漏:程序在运行过程中不停的分配内存,但是直到程序结束的时候才会释放内存;

常见造成内存泄漏的原因:

单例模式引起的内存泄漏:由于单例的静态特性使得单例的生命周期和程序的生命周期一样长,如果一个对象(如Context)已经不再使用,而单例对象还持有对象的引用就会造成这个对象不能正常回收;

Handler引起的内存泄漏:子线程执行网络任务,使用Handler处理子线程发送的消息,由于Hnadler对象是非静态匿名内部类的实例对象,持有外部类(Activity)的引用,在Handler Message中,Looper线程不断轮询处理消息,当Activity退出时还有未处理或正在处理的消息时,消息队列中的消息持有Handler对象引用,Handler又持有Activity,导致Activity的内存和资源不能及时回收;

线程造成内存泄漏:匿名内部类Runnable和AsyncTask对象执行异步任务,当前Activity隐式引用,当前Activity销毁之前,任务还没有执行完,将导致Activity的内存和资源不能及时回收;

资源对象未关闭造成的内存泄漏:对于使用了BroadcastReceiver、File、Bitmap等资源,应该在Activity销毁之前及时关闭或注销它们,否则这些资源将不会回收,造成内存泄漏;

内存泄漏的检测工具:LeakCanary

LeakCanary是Square公司开源的一个检测内存泄漏的函数库,可以在开发的项目中集成,在Debug版本中监控Activity、Fragment等的内存泄漏,LeakCanaty集成到项目之后,在检测内存泄漏时,会发送消息到系统通知栏,点击后会打开名称DisplayLeakActivity的页面,并显示泄露的跟踪信息,Logcat上面也会有对应的日志输出。

76. Android平台的虚拟机Dalvik

Dalvik概述:Dalvik是Google公司自己设计用于Android平台的Java虚拟机。它可以支持已转换为.dex(即Dalvik Executable)格式的Java应用程序的运行,.dex格式是专为Dalvik设计的一种压缩格式,可以减少整体文件尺寸,提高I/o操作的类查找速度所以适合内存和处理器速度有限的系统。

Dalvik虚拟机(DVM)和Java 虚拟机(JVM)首要差别:Dalvik 基于寄存器,而JVM 基于栈。性能有很大的提升。基于寄存器的虚拟机对于更大的程序来说,在它们编译的时候,花费的时间更短。

寄存器的概念:寄存器是中央处理器内的组成部分。寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令、数据和位址。在中央处理器的控制部件中,包含的寄存器有指令寄存器(IR)和程序计数器(PC),在中央处理器的算术及逻辑部件中,包含的寄存器有累加器(ACC)。

栈的概念:栈是线程独有的,保存其运行状态和局部自动变量的(所以多线程中局部变量都是相互独立的,不同于类变量)。栈在线程开始的时候初始化(线程的Start方法,初始化分配栈),每个线程的栈互相独立。每个函数都有自己的栈,栈被用来在函数之间传递参数。操作系统在切换线程的时候会自动的切换栈,就是切换SS/ESP寄存器。栈空间不需要在高级语言里面显式的分配和释放。

77.DVM进程的设计规则:

每个应用程序都运行在它自己的Linux空间。在需要执行该应用程序时Android将启动该进程,当不再需要该应用程序,并且系统资源分配不够时,则系统终止该进程。

每个应用程序都有自己的(DVM),所以任一应用程序的代码与其他应用程序的代码是相互隔离的。

默认情况下,每个应用程序都给分配一个唯一的Linux用户ID。所以应用程序的文件只能对该应用程序可见。

所以说每个应用程序都拥有一个独立的DVM,而每个DVM在Linux中又是一个进程,所以说DVM进程和Linux进程可以说是一个概念。

Android 应用程序的编译:Android所有类都通过JAVA编译器编译,然后通过Android SDK的“dex文件转换工具”转换为“dex”的字节文件,再由DVM载入执行。

Android ART模式简介:Android4.4引入ART模式来代替Dalvik虚拟机。ART是AndroidRuntime的缩写,它提供了以AOT(Ahead-Of-Time)的方式运行Android应用程序的机制。所谓AOT是指在运行前就把中间代码静态编译成本地代码,这就节省了JIT运行时的转换时间。因此,和采用JIT的Dalvik相比,ART模式在总体性能有了很大的提升,应用程序不但运行效率更高,耗电量更低,而且占用的内存也更少;ART和dalvik相比,系统的性能得到了显著提升,同时占用的内存更少,因此能支持配置更低的设备。但是ART模式下编译出来的文件会比以前增大10%-20%,系统需要更多的存储空间,同时因为在安装时要执行编译,应用的安装时间也比以前更长了;ART和dalvik相比,系统的性能得到了显著提升,同时占用的内存更少,因此能支持配置更低的设备。但是ART模式下编译出来的文件会比以前增大10%-20%,系统需要更多的存储空间,同时因为在安装时要执行编译,应用的安装时间也比以前更长了。

78. Android中的Binder机制

Binder是Android系统进程间通信(IPC)方式之一。Linux已经拥有的进程间通信IPC手段包括(Internet Process Connection):管道(Pipe)、信号(Signal)和跟踪(Trace)、插口(Socket)、报文队列(Message)、共享内存(Share Memory)和信号量(Semaphore)。Binder可以提供系统中任何程序都可以访问的全局服务。Android的Binder的框架如下:

上图中涉及到Binder模型的4类角色:Binder驱动,ServiceManager,Server和Client。 因为后面章节讲解Binder时,都是以MediaPlayerService和MediaPlayer为代表进行讲解的;这里就使用MediaPlayerService代表了Server,而MediaPlayer则代表了Client。

Binder机制的目的是实现IPC(Inter-ProcessCommunication),即实现进程间通信。在上图中,由于MediaPlayerService是Server的代表,而MediaPlayer是Client的代表;因此,对于上图而言,Binder机制则表现为"实现MediaPlayerService和MediaPlayer之间的通信"。

79. Android中的缓存机制

移动开发本质上就是手机和服务器之间进行通信,需要从服务端获取数据。反复通过网络获取数据是比较耗时的,特别是访问比较多的时候,会极大影响了性能,Android中可通过缓存机制来减少频繁的网络操作,减少流量、提升性能。

实现原理:把不需要实时更新的数据缓存下来,通过时间或者其他因素 来判别是读缓存还是网络请求,这样可以缓解服务器压力,一定程度上提高应用响应速度,并且支持离线阅读。

Bitmap的缓存:在许多的情况下(像 ListView, GridView 或 ViewPager 之类的组件 )我们需要一次性加载大量的图片,在屏幕上显示的图片和所有待显示的图片有可能需要马上就在屏幕上无限制的进行滚动、切换。像ListView, GridView 这类组件,它们的子项当不可见时,所占用的内存会被回收以供正在前台显示子项使用。垃圾回收器也会释放你已经加载了的图片占用的内存。如果你想让你的UI运行流畅的话,就不应该每次显示时都去重新加载图片。保持一些内存和文件缓存就变得很有必要了。

使用内存缓存:通过预先消耗应用的一点内存来存储数据,便可快速的为应用中的组件提供数据,是一种典型的以空间换时间的策略。LruCache 类(Android v4 Support Library 类库中开始提供)非常适合来做图片缓存任务,它可以使用一个LinkedHashMap 的强引用来保存最近使用的对象,并且当它保存的对象占用的内存总和超出了为它设计的最大内存时会把不经常使用的对象成员踢出以供垃圾回收器回收。给LruCache 设置一个合适的内存大小,需考虑如下因素:

还剩余多少内存给你的activity或应用使用屏幕上需要一次性显示多少张图片和多少图片在等待显示 手机的大小和密度是多少(密度越高的设备需要越大的 缓存) 图片的尺寸(决定了所占用的内存大小) 图片的访问频率(频率高的在内存中一直保存)保存图片的质量(不同像素的在不同情况下显示);

使用磁盘缓存:内存缓存能够快速的获取到最近显示的图片,但不一定就能够获取到。当数据集过大时很容易把内存缓存填满(如GridView )。你的应用也有可能被其它的任务(比如来电)中断进入到后台,后台应用有可能会被杀死,那么相应的内存缓存对象也会被销毁。当你的应用重新回到前台显示时,你的应用又需要一张一张的去加载图片了。磁盘文件缓存能够用来处理这些情况,保存处理好的图片,当内存缓存不可用的时候,直接读取在硬盘中保存好的图片,这样可以有效的减少图片加载的次数。读取磁盘文件要比直接从内存缓存中读取要慢一些,而且需要在一个UI主线程外的线程中进行,因为磁盘的读取速度是不能够保证的,磁盘文件缓存显然也是一种以空间换时间的策略。

使用SQLite进行缓存:网络请求数据完成后,把文件的相关信息(如url(一般作为唯一标示),下载时间,过期时间)等存放到数据库。下次加载的时候根据url先从数据库中查询,如果查询到并且时间未过期,就根据路径读取本地文件,从而实现缓存的效果。

文件缓存:思路和一般缓存一样,把需要的数据存储在文件中,下次加载时判断文件是否存在和过期(使用File.lastModified()方法得到文件的最后修改时间,与当前时间判断),存在并未过期就加载文件中的数据,否则请求服务器重新下载。

80. Android 中图片的三级缓存策略

三级缓存: 内存缓存,优先加载,速度最快; 本地缓存,次优先加载,速度快;网络缓存,最后加载,速度慢,浪费流量 ;

三级缓存策略,最实在的意义就是 减少不必要的流量消耗,增加加载速度。

三级缓存的原理:

首次加载的时候通过网络加载,获取图片,然后保存到内存和 SD 卡中。

之后运行 APP 时,优先访问内存中的图片缓存。

如果内存没有,则加载本地 SD 卡中的图片。

具体的缓存策略可以是这样的:内存作为一级缓存,本地作为二级缓存,网络加载为最后。其中,内存使用 LruCache ,其内部通过 LinkedhashMap 来持有外界缓存对象的强引用;对于本地缓存,使用 DiskLruCache。加载图片的时候,首先使用 LRU 方式进行寻找,找不到指定内容,按照三级缓存的方式,进行本地搜索,还没有就网络加载。

81. Glide三级缓存

内存缓存,磁盘缓存、网络缓存(由于网络缓存严格来说不算是缓存的一种,故也称为二级缓存)。缓存的资源分为两种:原图(SOURCE)、处理图(RESULT)(默认)。

内存缓存:默认开启的,可以通过调用skipMemoryCache(true)来设置跳过内存缓存,缓存最大空间:每个进程可用的最大内存*0.4。(低配手机0.33)

磁盘缓存:分为四种:ALL(缓存原图)、NONE(什么都不缓存)、SOURCE(只缓存原图)、RESULT(之后处理图),通过diskCacheStrategy(DiskCacheStrategy.ALL)来设置,缓存大小250M。

84.HybridApp WebView和JS交互

Android与JS通过WebView互相调用方法,实际上是:

Android去调用JS的代码

1. 通过WebView的loadUrl(),使用该方法比较简洁,方便。但是效率比较低,获取返回值比较困难。

2. 通过WebView的evaluateJavascript(),该方法效率高,但是4.4以上的版本才支持,4.4以下版本不支持。所以建议两者混合使用。

JS去调用Android的代码

1. 通过WebView的addJavascriptInterface()进行对象映射 ,该方法使用简单,仅将Android对象和JS对象映射即可,但是存在比较大的漏洞。

漏洞产生原因是:当JS拿到Android这个对象后,就可以调用这个Android对象中所有的方法,包括系统类(java.lang.Runtime 类),从而进行任意代码执行。

解决方式:

(1)Google 在Android 4.2 版本中规定对被调用的函数以 @JavascriptInterface进行注解从而避免漏洞攻击。

(2)在Android 4.2版本之前采用拦截prompt()进行漏洞修复。

2. 通过 WebViewClient 的shouldOverrideUrlLoading ()方法回调拦截 url 。这种方式的优点:不存在方式1的漏洞;缺点:JS获取Android方法的返回值复杂。(ios主要用的是这个方式)

(1)Android通过 WebViewClient 的回调方法shouldOverrideUrlLoading ()拦截 url

(2)解析该 url 的协议

(3)如果检测到是预先约定好的协议,就调用相应方法

3. 通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS对话框alert()、confirm()、prompt() 消息

这种方式的优点:不存在方式1的漏洞;缺点:JS获取Android方法的返回值复杂。

87.RecyclerView和ListView的区别

RecyclerView可以完成ListView,GridView的效果,还可以完成瀑布流的效果。同时还可以设置列表的滚动方向(垂直或者水平);

RecyclerView中view的复用不需要开发者自己写代码,系统已经帮封装完成了。

RecyclerView可以进行局部刷新。

RecyclerView提供了API来实现item的动画效果。

在性能上:

如果需要频繁的刷新数据,需要添加动画,则RecyclerView有较大的优势。

如果只是作为列表展示,则两者区别并不是很大。

88.简述一下RecyclerView缓存机制?

RecyclerView在Android的应用上可以说已经取代了listview,其灵活,组装式设置,多缓存机制可以适应多列表在Android开发中各种各样的需求。

对于RecyclerView的缓存机制一直都想稍微的屡下思路,简单点说RecyclerView对比listview缓存机制中就是多了两层缓存的支持,listview是两级缓存,RecyclerView为四级缓存(当然在大部分情况下是三级缓存)。

89.recyclerView嵌套卡顿解决如何解决

设置预加载的数量LinearLayoutManager.setInitialPrefetchItemCount(4),默认是预加载2个,
设置子项缓存,
设置自带滑动冲突解决属性rv.setHasFixedSize(true); rv.setNestedScrollingEnabled(false);
可以完美解决,不过Google不推荐RecyClerView嵌套使用,需要嵌套尽量找类似于ExpandableListView 第三方控件来解决

90.Universal-ImageLoader,Picasso,Fresco,Glide对比

90.1 Fresco

是 Facebook 推出的开源图片缓存工具,主要特点包括:两个内存缓存加上 Native 缓存构成了三级缓存,

优点:

1. 图片存储在安卓系统的匿名共享内存, 而不是虚拟机的堆内存中, 图片的中间缓冲数据也存放在本地堆内存, 所以, 应用程序有更多的内存使用, 不会因为图片加载而导致oom, 同时也减少垃圾回收器频繁调用回收 Bitmap 导致的界面卡顿, 性能更高。

2. 渐进式加载 JPEG 图片, 支持图片从模糊到清晰加载。

3. 图片可以以任意的中心点显示在 ImageView, 而不仅仅是图片的中心。

4. JPEG 图片改变大小也是在 native 进行的, 不是在虚拟机的堆内存, 同样减少 OOM。

5. 很好的支持 GIF 图片的显示。

缺点:

1. 框架较大, 影响 Apk 体积

2. 使用较繁琐

90.2 Universal-ImageLoader

(估计由于HttpClient被Google放弃,作者就放弃维护这个框架)

优点:

1.支持下载进度监听

2.可以在 View 滚动中暂停图片加载,通过 PauseOnScrollListener 接口可以在 View 滚动中暂停图片加载。

3.默认实现多种内存缓存算法 这几个图片缓存都可以配置缓存算法,不过 ImageLoader 默认实现了较多缓存算法,如 Size 最大先删除、使用最少先删除、最近最少使用、先进先删除、时间最长先删除等。

4.支持本地缓存文件名规则定义

90.3 Picasso 优点

1. 自带统计监控功能。支持图片缓存使用的监控,包括缓存命中率、已使用内存大小、节省的流量等。

2.支持优先级处理。每次任务调度前会选择优先级高的任务,比如 App 页面中 Banner 的优先级高于 Icon 时就很适用。

3.支持延迟到图片尺寸计算完成加载

4.支持飞行模式、并发线程数根据网络类型而变。 手机切换到飞行模式或网络类型变换时会自动调整线程池最大并发数,比如 wifi 最大并发为 4,4g 为 3,3g 为 2。 这里 Picasso 根据网络类型来决定最大并发数,而不是 CPU 核数。

5.“无”本地缓存。无”本地缓存,不是说没有本地缓存,而是 Picasso 自己没有实现,交给了 Square 的另外一个网络库 okhttp 去实现,这样的好处是可以通过请求 Response Header 中的 Cache-Control 及 Expired 控制图片的过期时间。

90.4 Glide 优点

1. 不仅仅可以进行图片缓存还可以缓存媒体文件。Glide 不仅是一个图片缓存,它支持 Gif、WebP、缩略图。甚至是 Video,所以更该当做一个媒体缓存。

2. 支持优先级处理。

3. 与 Activity/Fragment 生命周期一致,支持 trimMemory。Glide 对每个 context 都保持一个 RequestManager,通过 FragmentTransaction 保持与 Activity/Fragment 生命周期一致,并且有对应的 trimMemory 接口实现可供调用。

4. 支持 okhttp、Volley。Glide 默认通过 UrlConnection 获取数据,可以配合 okhttp 或是 Volley 使用。实际 ImageLoader、Picasso 也都支持 okhttp、Volley。

5. 内存友好。Glide 的内存缓存有个 active 的设计,从内存缓存中取数据时,不像一般的实现用 get,而是用 remove,再将这个缓存数据放到一个 value 为软引用的 activeResources map 中,并计数引用数,在图片加载完成后进行判断,如果引用计数为空则回收掉。内存缓存更小图片,Glide 以 url、view_width、view_height、屏幕的分辨率等做为联合 key,将处理后的图片缓存在内存缓存中,而不是原始图片以节省大小与 Activity/Fragment 生命周期一致,支持 trimMemory。图片默认使用默认 RGB_565 而不是 ARGB_888,虽然清晰度差些,但图片更小,也可配置到 ARGB_888。

6.Glide 可以通过 signature 或不使用本地缓存支持 url 过期

91.Xutils, OKhttp, Volley, Retrofit对比

Xutils这个框架非常全面,可以进行网络请求,可以进行图片加载处理,可以数据储存,还可以对view进行注解,使用这个框架非常方便,但是缺点也是非常明显的,使用这个项目,会导致项目对这个框架依赖非常的严重,一旦这个框架出现问题,那么对项目来说影响非常大的。、

OKhttp:Android开发中是可以直接使用现成的api进行网络请求的。就是使用HttpClient,HttpUrlConnection进行操作。okhttp针对Java和Android程序,封装的一个高性能的http请求库,支持同步,异步,而且okhttp又封装了线程池,封装了数据转换,封装了参数的使用,错误处理等。API使用起来更加的方便。但是我们在项目中使用的时候仍然需要自己在做一层封装,这样才能使用的更加的顺手。

Volley:Volley是Google官方出的一套小而巧的异步请求库,该框架封装的扩展性很强,支持HttpClient、HttpUrlConnection, 甚至支持OkHttp,而且Volley里面也封装了ImageLoader,所以如果你愿意你甚至不需要使用图片加载框架,不过这块功能没有一些专门的图片加载框架强大,对于简单的需求可以使用,稍复杂点的需求还是需要用到专门的图片加载框架。Volley也有缺陷,比如不支持post大数据,所以不适合上传文件。不过Volley设计的初衷本身也就是为频繁的、数据量小的网络请求而生。

Retrofit:Retrofit是Square公司出品的默认基于OkHttp封装的一套RESTful网络请求框架,RESTful是目前流行的一套api设计的风格, 并不是标准。Retrofit的封装可以说是很强大,里面涉及到一堆的设计模式,可以通过注解直接配置请求,可以使用不同的http客户端,虽然默认是用http ,可以使用不同Json Converter 来序列化数据,同时提供对RxJava的支持,使用Retrofit + OkHttp + RxJava + Dagger2 可以说是目前比较潮的一套框架,但是需要有比较高的门槛。

Volley VS OkHttp

Volley的优势在于封装的更好,而使用OkHttp你需要有足够的能力再进行一次封装。而OkHttp的优势在于性能更高,因为 OkHttp基于NIO和Okio ,所以性能上要比 Volley更快。IO 和 NIO这两个都是Java中的概念,如果我从硬盘读取数据,第一种方式就是程序一直等,数据读完后才能继续操作这种是最简单的也叫阻塞式IO,还有一种是你读你的,程序接着往下执行,等数据处理完你再来通知我,然后再处理回调。而第二种就是 NIO 的方式,非阻塞式, 所以NIO当然要比IO的性能要好了,而 Okio是 Square 公司基于IO和NIO基础上做的一个更简单、高效处理数据流的一个库。理论上如果Volley和OkHttp对比的话,更倾向于使用 Volley,因为Volley内部同样支持使用OkHttp,这点OkHttp的性能优势就没了, 而且 Volley 本身封装的也更易用,扩展性更好些。

OkHttp VS Retrofit

毫无疑问,Retrofit 默认是基于 OkHttp 而做的封装,这点来说没有可比性,肯定首选 Retrofit。

Volley VS Retrofit

这两个库都做了不错的封装,但Retrofit解耦的更彻底,尤其Retrofit2.0出来,Jake对之前1.0设计不合理的地方做了大量重构, 职责更细分,而且Retrofit默认使用OkHttp,性能上也要比Volley占优势,再有如果你的项目如果采用了RxJava ,那更该使用 Retrofit 。所以这两个库相比,Retrofit更有优势,在能掌握两个框架的前提下该优先使用 Retrofit。但是Retrofit门槛要比Volley稍高些,要理解他的原理,各种用法,想彻底搞明白还是需要花些功夫的,如果你对它一知半解,那还是建议在商业项目使用Volley吧。

92.请解释下 Android 程序运行时权限与文件系统权限的区别?

apk程序是运行在虚拟机上的,对应的是Android独特的权限机制,只有体现到文件系统上时才

使用 linux 的权限设置。

linux文件系统上的权限

-rwxr-x--x system system 4156 2010-04-30 16:13 test.apk

代表的是相应的用户/用户组及其他人对此文件的访问权限,与此文件运行起来具有的权限完全不相关。比如上面的例子只能说明 system 用户拥有对此文件的读写执行权限;system 组的用户对此文件拥有读、执行权限;其他人对此文件只具有执行权限。而 test.apk运行起来后可以干哪些事情,跟这个就不相关了。千万不要看apk 文件系统上属于system/system 用户及用户组,或者root/root用户及用户组,就认为apk 具有system 或 root权限

Android 的权限规则

Android中的apk必须签名

基于 UserID 的进程级别的安全机制

默认 apk生成的数据对外是不可见的

AndroidManifest.xml 中的显式权限声明

93.Framework 工作方式及原理,Activity 是如何生成一个 view 的,机制是什么?

所有的框架都是基于反射 和 配置文件(manifest)的。

普通的情况:

Activity创建一个 view 是通过 ondraw画出来的, 画这个view之前呢,还会调用 onmeasure方法来计算显示的大小.

特殊情况:

Surfaceview是直接操作硬件的,因为 或者视频播放对帧数有要求,onDraw 效率太低,不够使,Surfaceview 直接把数据写到显存。

94.Android 判断SD卡是否存在

  /**

     * 判断SD是否挂载

     */

    public static boolean isSDCardMount() {

        return Environment.getExternalStorageState().equals(

                Environment.MEDIA_MOUNTED);

    }

95.Android与服务器交互的方式中的对称加密和非对称加密是什么?

对称加密,就是加密和解密数据都是使用同一个key,这方面的算法有DES。

非对称加密,加密和解密是使用不同的key。发送数据之前要先和服务端约定生成公钥和私钥,使用公钥加密的数据可以用私钥解密,反之。这方面的算法有RSA。ssh 和ssl都是典型的非对称加密。

96. 音视频相关类

总体来说,分为几个类
视频录制方面,Camear摄像头录制视频类,MediaProjection屏幕录制视频类
编码方面,MediaCodec,MediaRecorder
预览方面,SurfaceView,GLSurfaceView,TextureView,VideoView

97.SurfaceView和GLSurfaceView

SurfaceView

继承View,提供可以嵌入到View结构树中的独立绘图层。通过SurfaceHolder来对Surface进行管理。

当窗体显示时你需要实现SurfaceHolder.Callback.surfaceCreated(SurfaceHolder),

当窗体隐藏后需要实现SurfaceHolder.Callback.surfaceDestroyed(SurfaceHolder).

使用SurfaceView的目的是为了开启子线程对屏幕进行渲染。

优势如下:

因为是在子线程中进行绘制更新,可以避免程序卡顿和ANR的问题。

可以提供 主动刷新不停的进行绘制

刷新界面比View快

适用于2D游戏开发

GLSurfaceView

继承自SurfaceView专门用于OpenGL rendering使用。

特新如下:

1>提供并且管理一个独立的Surface。

2> 提供并且管理一个EGL display,它能让opengl把内容渲染到上述的Surface上。

3> 支持用户自定义渲染器(Render),通过setRenderer设置一个自定义的Renderer。

4> 让渲染器在独立的GLThread线程里运作,和UI线程分离。

5> 支持按需渲染(on-demand)和连续渲染(continuous)两种模式。

6> 另外还针对OpenGL调用进行追踪和错误检查。

适用于3D游戏视图的开发。

99.说说JobScheduler

任务调度JobScheduler,Android5.0被推出。(可能有的朋友感觉比较陌生,其实他也是通过Service实现的,这个待会再说)

它能做的工作就是可以在你所规定的要求下进行自动任务执行。比如规定时间、网络为WIFI情况、设备空闲、充电时等各种情况下后台自动运行。

所以Google让它来替代后台Service的一部分功能,使用案例:

首先,创建一个JobService:

public class MyJobService extends JobService {

    @Override

    public boolean onStartJob(JobParameters params) {

        return false;

    }

    @Override

    public boolean onStopJob(JobParameters params) {

        return false;

    }

}

然后,注册这个服务(因为JobService也是Service)

<service android:name=".MyJobService"

    android:permission="android.permission.BIND_JOB_SERVICE" />

最后,创建一个JobInfo并执行

JobScheduler scheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); 

 ComponentName jobService = new ComponentName(this, MyJobService.class);

 JobInfo jobInfo = new JobInfo.Builder(ID, jobService)

         .setMinimumLatency(5000)// 任务最少延迟时间

         .setOverrideDeadline(60000)// 任务deadline,当到期没达到指定条件也会开始执行

         .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)// 网络条件,默认值NETWORK_TYPE_NONE

         .setRequiresCharging(true)// 是否充电

         .setRequiresDeviceIdle(false)// 设备是否空闲

         .setPersisted(true) //设备重启后是否继续执行

         .setBackoffCriteria(3000,JobInfo.BACKOFF_POLICY_LINEAR) //设置退避/重试策略

         .build(); 

 scheduler.schedule(jobInfo);

简单说下原理:

JobSchedulerService是在SystemServer中启动的服务,然后会遍历没有完成的任务,通过Binder找到对应的JobService,执行onStartJob方法,完成任务。具体可以看看参考链接的分析。

所以也就知道了,在5.0之后,如果有需要后台任务执行,特别是需要满足一定条件触发的任务,比如网络电量等等情况,就可以使用JobScheduler。

有的人可能要问了,5.0之前怎么办呢?

可以使用GcmNetworkManager或者BroadcastReceiver等处理部分情况下的任务需求。

Google也是考虑到了这一点,所以将5.0之后的JobScheduler和5.0之前的GcmNetworkManager、GcmNetworkManager、AlarmManager等和任务相关的API相结合,设计出了WorkManager。

100.说说WorkManager

WorkManager 是一个 API,可供您轻松调度那些即使在退出应用或重启设备后仍应运行的可延期异步任务。

作为Jetpack的一员,并不算很新的内容,它的本质就是结合已有的任务调度相关的API,然后根据版本需求等来执行这些任务,官网有一张图:

所以WorkManager到底能做什么呢?

1、对于一些任务约束能很好的执行,比如网络、设备空闲状态、足够存储空间等条件下需要执行的任务。

2、可以重复、一次性、稳定的执行任务。包括在设备重启之后都能继续任务。

3、可以定义不同工作任务的衔接关系。比如设定一个任务接着一个任务。

总之,它是后台执行任务的一大利器。

101.谈一谈startService和bindService的区别,生命周期以及使用场景?

生命周期上的区别

执行startService时,Service会经历onCreate->onStartCommand。当执行stopService时,直接调用onDestroy方法。调用者如果没有stopService,Service会一直在后台运行,下次调用者再起来仍然可以stopService。

执行bindService时,Service会经历onCreate->onBind。这个时候调用者和Service绑定在一起。调用者调用unbindService方法或者调用者Context不存在了(如Activity被finish了),Service就会调用onUnbind->onDestroy。这里所谓的绑定在一起就是说两者共存亡了。

多次调用startService,该Service只能被创建一次,即该Service的onCreate方法只会被调用一次。但是每次调用startService,onStartCommand方法都会被调用。Service的onStart方法在API 5时被废弃,替代它的是onStartCommand方法。

第一次执行bindService时,onCreate和onBind方法会被调用,但是多次执行bindService时,onCreate和onBind方法并不会被多次调用,即并不会多次创建服务和绑定服务。

调用者如何获取绑定后的Service的方法

onBind回调方法将返回给客户端一个IBinder接口实例,IBinder允许客户端回调服务的方法,比如得到Service运行的状态或其他操作。我们需要IBinder对象返回具体的Service对象才能操作,所以说具体的Service对象必须首先实现Binder对象。

既使用startService又使用bindService的情况呢?

如果一个Service又被启动又被绑定,则该Service会一直在后台运行。首先不管如何调用,onCreate始终只会调用一次。对应startService调用多少次,Service的onStart方法便会调用多少次。Service的终止,需要unbindService和stopService同时调用才行。不管startService与bindService的调用顺序,如果先调用unbindService,此时服务不会自动终止,再调用stopService之后,服务才会终止;如果先调用stopService,此时服务也不会终止,而再调用unbindService或者之前调用bindService的Context不存在了(如Activity被finish的时候)之后,服务才会自动停止。

那么,什么情况下既使用startService,又使用bindService呢?

如果你只是想要启动一个后台服务长期进行某项任务,那么使用startService便可以了。如果你还想要与正在运行的Service取得联系,那么有两种方法:一种是使用broadcast,另一种是使用bindService。前者的缺点是如果交流较为频繁,容易造成性能上的问题,而后者则没有这些问题。因此,这种情况就需要startService和bindService一起使用了。

另外,如果你的服务只是公开一个远程接口,供连接上的客户端(Android的Service是C/S架构)远程调用执行方法,这个时候你可以不让服务一开始就运行,而只是bindService,这样在第一次bindService的时候才会创建服务的实例运行它,这会节约很多系统资源,特别是如果你的服务是远程服务,那么效果会越明显(当然在Servcie创建的是偶会花去一定时间,这点需要注意)。

本地服务与远程服务

本地服务依附在主进程上,在一定程度上节约了资源。本地服务因为是在同一进程,因此不需要IPC,也不需要AIDL。相应bindService会方便很多。缺点是主进程被kill后,服务变会终止。

远程服务是独立的进程,对应进程名格式为所在包名加上你指定的android:process字符串。由于是独立的进程,因此在Activity所在进程被kill的是偶,该服务依然在运行。缺点是该服务是独立的进程,会占用一定资源,并且使用AIDL进行IPC稍微麻烦一点。

对于startService来说,不管是本地服务还是远程服务,我们需要做的工作都一样简单。

102.Service如何进行保活?

利用系统广播拉活

利用系统service拉活

利用Native进程拉活<Android5.0以后失效> fork进行监控主进程,利用native拉活

利用JobScheduler机制拉活<Android5.0以后>

利用账号同步机制拉活

103. 进程保活(不死进程)

此处延伸:进程的优先级是什么

当前业界的Android进程保活手段主要分为** 黑、白、灰 **三种,其大致的实现思路如下:

黑色保活:不同的app进程,用广播相互唤醒(包括利用系统提供的广播进行唤醒)

白色保活:启动前台Service

灰色保活:利用系统的漏洞启动前台Service

黑色保活

所谓黑色保活,就是利用不同的app进程使用广播来进行相互唤醒。举个3个比较常见的场景:

场景1:开机,网络切换、拍照、拍视频时候,利用系统产生的广播唤醒app

场景2:接入第三方SDK也会唤醒相应的app进程,如微信sdk会唤醒微信,支付宝sdk会唤醒支付宝。由此发散开去,就会直接触发了下面的 场景3

场景3:假如你手机里装了支付宝、淘宝、天猫、UC等阿里系的app,那么你打开任意一个阿里系的app后,有可能就顺便把其他阿里系的app给唤醒了。(只是拿阿里打个比方,其实BAT系都差不多)

白色保活

白色保活手段非常简单,就是调用系统api启动一个前台的Service进程,这样会在系统的通知栏生成一个Notification,用来让用户知道有这样一个app在运行着,哪怕当前的app退到了后台。如下方的LBE和QQ音乐这样:

灰色保活

灰色保活,这种保活手段是应用范围最广泛。它是利用系统的漏洞来启动一个前台的Service进程,与普通的启动方式区别在于,它不会在系统通知栏处出现一个Notification,看起来就如同运行着一个后台Service进程一样。这样做带来的好处就是,用户无法察觉到你运行着一个前台进程(因为看不到Notification),但你的进程优先级又是高于普通后台进程的。那么如何利用系统的漏洞呢,大致的实现思路和代码如下:

思路一:API < 18,启动前台Service时直接传入new Notification();

思路二:API >= 18,同时启动两个id相同的前台Service,然后再将后启动的Service做stop处理

熟悉Android系统的童鞋都知道,系统出于体验和性能上的考虑,app在退到后台时系统并不会真正的kill掉这个进程,而是将其缓存起来。打开的应用越多,后台缓存的进程也越多。在系统内存不足的情况下,系统开始依据自身的一套进程回收机制来判断要kill掉哪些进程,以腾出内存来供给需要的app。这套杀进程回收内存的机制就叫 Low Memory Killer ,它是基于Linux内核的 OOM Killer(Out-Of-Memory killer)机制诞生。

进程的重要性,划分5级:

前台进程 (Foreground process)

可见进程 (Visible process)

服务进程 (Service process)

后台进程 (Background process)

空进程 (Empty process)

了解完 Low Memory Killer,再科普一下oom_adj。什么是oom_adj?它是linux内核分配给每个系统进程的一个值,代表进程的优先级,进程回收机制就是根据这个优先级来决定是否进行回收。对于oom_adj的作用,你只需要记住以下几点即可:

进程的oom_adj越大,表示此进程优先级越低,越容易被杀回收;越小,表示进程优先级越高,越不容易被杀回收

普通app进程的oom_adj>=0,系统进程的oom_adj才可能<0

有些手机厂商把这些知名的app放入了自己的白名单中,保证了进程不死来提高用户体验(如微信、QQ、陌陌都在小米的白名单中)。如果从白名单中移除,他们终究还是和普通app一样躲避不了被杀的命运,为了尽量避免被杀,还是老老实实去做好优化工作吧。

所以,进程保活的根本方案终究还是回到了性能优化上,进程永生不死终究是个彻头彻尾的伪命题!

104.热修复的原理

我们知道Java虚拟机 —— JVM 是加载类的class文件的,而Android虚拟机——Dalvik/ART VM 是加载类的dex文件,

而他们加载类的时候都需要ClassLoader,ClassLoader有一个子类BaseDexClassLoader,而BaseDexClassLoader下有一个

数组——DexPathList,是用来存放dex文件,当BaseDexClassLoader通过调用findClass方法时,实际上就是遍历数组,

找到相应的dex文件,找到,则直接将它return。而热修复的解决方法就是将新的dex添加到该集合中,并且是在旧的dex的前面,

所以就会优先被取出来并且return返回。

105.JNI

(1)安装和下载Cygwin,下载 Android NDK

(2)在ndk项目中JNI接口的设计

(3)使用C/C++实现本地方法

(4)JNI生成动态链接库.so文件

(5)将动态链接库复制到java工程,在java工程中调用,运行java工程即可

105.0谈谈对Android NDK的理解

NDK是一系列工具的集合.NDK提供了一系列的工具,帮助开发者快速开发C或C++的动态库,并能自动将so和java应用一起打包成apk.这些工具对开发者的帮助是巨大的.NDK集成了交叉编译器,并提供了相应的mk文件隔离CPU,平台,ABI等差异,开发人员只需要简单修改 mk文件(指出"哪些文件需要编译","编译特性要求"等),就可以创建出so.

NDK可以自动地将so和Java应用一起打包,极大地减轻了开发人员的打包工作.NDK提供了一份稳定,功能有限的API头文件声明.

Google明确声明该API是稳定的,在后续所有版本中都稳定支持当前发布的API.从该版本的NDK中看出,这些 API支持的功能非常有限,包含有:C标准库(libc),标准数学库(libm ),压缩库(libz),Log库(liblog).

106.Foundation(基础组件)

AppCompat  

Android KTX

Mutidex

Test

基础组件提供了跨领域的功能,如向后兼容性、测试和Kotlin语言支持

107.Architecture(架构组件)(非常重要)

架构组件这个比较关键,是我们要学习的重点包括:

Data Binding 数据绑定

Lifecycle 管理Activity和Fragment生命周期

LiveData 感知数据变化更新ui

Navigation 字意为导航。多Fragment转场,栈管理

Paging 分页处理

Room 数据库管理

ViewModel ui界面的数据管理

WorkManager 后台工作管理

108.Behavior(行为组件)

行为组件可帮助开发者的应用与标准 Android 服务(如通知、权限、分享和 Google 助理)相集成。

CameraX:帮助开发者简化相机应用的开发工作。它提供一致且易于使用的 API 界面,适用于大多数 Android 设备,并可向后兼容至 Android 5.0(API 级别 21)

DownloadManager(下载管理器):可处理长时间运行的HTTP下载,并在出现故障或在连接更改和系统重新启动后重试下载。

Media & playback(媒体&播放):用于媒体播放和路由(包括 Google Cast)的向后兼容 API

Notifications(通知):提供向后兼容的通知 API,支持 Wear 和 Auto。

Permissions(权限):用于检查和请求应用权限的兼容性 API。

Preferences(偏好设置):提供了用户能够改变应用的功能和行为能力。

Sharing(共享):提供适合应用操作栏的共享操作。

Slices(切片):创建可在应用外部显示应用数据的灵活界面元素。

109.UI(界面组件)

界面组件可提供各类view和辅助程序,让应用不仅简单易用,还能带来愉悦体验。它包含如下组件库:

Animation & Transitions(动画&过度):提供各类内置动画,也可以自定义动画效果。

Emoji(表情符号):使用户在未更新系统版本的情况下也可以使用表情符号。

Fragment:组件化界面的基本单位。

Layout(布局):xml书写的界面布局或者使用Compose完成的界面。

Palette(调色板):从调色板中提取出有用的信息。

所以,重点是AAC架构(Adroid Architecture Components,即Android系统架构组件)

Lifecycle 能够帮我们轻松的管理 Activity/Fragment 的生命周期问题,能够让我们以一种更加解耦的方式处理生命周期的变化问题,以及轻松避免内存泄露;

原理:

1.Lifecycle 库通过在 SupportActivity 的 onCreate 中注入 ReportFragment 来感知发生命周期;

2.Lifecycle 抽象类,是 Lifecycle 库的核心类之一,它是对生命周期的抽象,定义了生命周期事件以及状态,通过它我们可以获取当前的生命周期状态,同时它也奠定了观察者模式的基调;

LiveData :基于观察者模式,并且感知生命周期、可观察的数据持有类,它被设计成 ViewModel 的一个成员变量;可以以一个 更解耦 的方式来共享数据。

1.LiveData 的实现基于观察者模式;

2.LiveData 跟 LifecycleOwner 绑定,能感知生命周期变化,并且只会在 LifecycleOwner 处于 Active 状态(started/resumed)下通知数据改变(避免不必要的数据刷新);

3.LiveData 会自动在 destroyed的状态下移除 Observer ,取消订阅,所以不用担心内存泄露;

ViewModel管理跟UI相关的数据, 并且能够感知生命周期;另外 ViewModel 能够在配置改变的情况下让数据得以保留。ViewModel 重在以感知生命周期的方式 管理界面相关的数据。

我们知道类似旋转屏幕等配置项改变会导致我们的 Activity 被销毁并重建,此时 Activity 持有的数据就会跟随着丢失,而ViewModel 则并不会被销毁,从而能够帮助我们在这个过程中保存数据,而不是在 Activity 重建后重新去获取。并且 ViewModel 能够让我们不必去担心潜在的内存泄露问题,同时 ViewModel 相比于用onSaveInstanceState() 方法更有优势,比如存储相对大的数据,并且不需要序列化以及反序列化。

ViewModel 原理总结:

通过注入一个 retainInstance 为true 的 HolderFragment ,利用 Fragment 的特性来保证在 Activity 配置改变后依然能够存活下来,并且保证了 HolderFragment 内部的 ViewModelStore 的存活,最终保证了 ViewModelStore 内部储存的 ViewModel 缓存存活,从而实现 ViewModel 在 Activity 配置改变的情况下不销毁的功能。

ViewModel 的使用注意事项:

不要持有 Activity :ViewModel 不会因为 Activity 配置改变而被销毁,所以绝对不要持有那些跟 Activity 相关的类,比如Activity 里的某个 View,让 ViewModel 持有 Activity 会导致内存泄露,还要注意的是连 Lifecycle 也不行;

不能访问 UI :ViewModel 应该只负责管理数据,不能去访问 UI,更不能持有它;

110. Android设计模式之MVC

MVC即Model-View-Controller,M是模型,V是视图,C是控制器,MVC模式下系统框架的类库被划分为模型(Model)、视图(View)、控制器(Controller),模型对象负责建立数据结构和相应的行为操作处理,视图负责在屏幕上渲染出相应的图形信息,展示给用户看,控制器对象负责截获用户的按键和屏幕触摸事件,协调Model对象和View对象。

用户与视图交互,视图接收并反馈用户的动作,视图把用户的请求传给相应的控制器,由控制器决定调用哪个模型,然后由模型调用相应的业务逻辑对用户请求进行加工处理,如果需要返回数据,模型会把相应的数据返回给控制器,由控制器调用相应的视图,最终由视图格式化和渲染返回的数据,一个模型可以有多个视图,一个视图可以有多个控制器,一个控制器可以有多个模型。

Model(模型):Model是一个应用系统的核心部分,代表了该系统实际要实现的所有功能处理,比如在视频播放器中,模型代表了一个视频数据库及播放视频的程序和函数代码,Model在values目录下通过xml文件格式生成,也可以由代码动态生成,View和Model是通过桥梁Adapter来连接起来的;

View(视图):View是软件应用传给用户的一个反馈结果,它代表了软件应用中的图形展示,声音播放、触觉反馈等,视图的根节点是应用程序的自身窗口,View在Layout目录中通过xml文件格式生成,用findViewById()获取,也可以通过代码动态生成;

Controller(控制器):Controller在软件应用中负责对外部事件的响应,包括:键盘敲击、屏幕触摸、电话呼入等,Controller实现了一个事件队列,每一个外部事件均在事件队列中被唯一标识,框架依次将事件从队列中移出并派发出去;

111 安卓mvc/mvp/mvvm

MVC:Model-View-Controller,是一种分层解偶的框架,Model层提供本地数据和网络请求,View层处理视图,Controller处理逻辑,存在问题是Controller层和View层的划分不明显,Model层和View层的存在耦合。

MVP:Model-View-Presenter,是对MVC的升级,Model层和View层与MVC的意思一致,但Model层和View层不再存在耦合,而是通过Presenter层这个桥梁进行交流。

MVVM:Model-View-ViewModel,不同于上面的两个框架,ViewModel持有数据状态,当数据状态改变的时候,会自动通知View层进行更新。

MVC和MVP的区别是什么?

MVP是MVC的进一步解耦,简单来讲,在MVC中,View层既可以和Controller层交互,又可以和Model层交互;而在MVP中,View层只能和Presenter层交互,Model层也只能和Presenter层交互,减少了View层和Model层的耦合,更容易定位错误的来源。

MVVM和MVP的最大区别在哪?

MVP中的每个方法都需要你去主动调用,它其实是被动的,而MVVM中有数据驱动这个概念,当你的持有的数据状态发生变更的时候,你的View你可以监听到这个变化,从而主动去更新,这其实是主动的。

严格来说这三种都不是设计模式,只能算是框架,或者一种思想。每种模式也没有严格定义,不同的人有不同的理解

112.设计模式的六大原则

单一职责:合理分配类和函数的职责

开闭原则:开放扩展,关闭修改

里式替换:继承

依赖倒置:面向接口

接口隔离:控制接口的粒度

迪米特:一个类应该对其他的类了解最少

113.Android中的性能优化相关问题

由于手机硬件的限制,在Android手机中过多的使用内存,会容易导致oom(out of memory 内存溢出),过多的使用CPU资源,会导致手机卡顿,甚至导致ANR(Application Not Responding 应用程序无响应),Android主要从以下几部分对此进行优化:

布局优化,使用herarchyviewer(视图层次窗口)工具删除无用的空间和层级;

选择性能较低的viewgroup,比如在可以选择RelativeLayout也可以使用LinearLayout的情况下,优先使用LinearLayout,因为相对来说RelativeLayout功能较为复杂,会占用更多的CPU资源;

使用标签重用布局、减少层级、进行预加载(用的时候才加载);

绘制优化:指view在ondraw方法中避免大量的耗时的操作,由于onDraw方法可能会被频繁的调用;

ondraw方法中不要创建新的局部变量,ondraw方法被频繁的调用,很容易引起GC;

ondraw方法不要做耗时的操作;

线程优化:使用线程池来管理和复用线程,避免程序中出现大量的Thread,同时可以控制线 的并发数,避免相互抢占资源,而导致线程阻塞;

114. Bitmap的使用及内存优化

位图是相对于矢量图而言的,也称为点阵图,位图由像素组成,图像的清晰度由单位长度内的像素的多少来决定的,在Android系统中,位图使用Bitmap类来表示,该类位于android.graphics包中,被final所修饰,不能被继承,创建Bitmap对象可使用该类的静态方法createBitmap,也可以借助BitmapFactory类来实现。Bitmap可以获取图像文件信息,如宽高尺寸等,可以进行图像剪切、旋转、缩放等操作.

BitmapFactory是创建Bitmap的工具类,能够以文件、字节数组、输入流的形式创建位图对象,BitmapFactory类提供的都是静态方法,可以直接调用,BitmapFactory. Options类是BitmapFactory的静态内部类,主要用于设定位图的解析参数。在解析位图时,将位图进行相应的缩放,当位图资源不再使用时,强制资源回收,可以有效避免内存溢出。

缩略图:不加载位图的原有尺寸,而是根据控件的大小呈现图像的缩小尺寸,就是缩略图。

将大尺寸图片解析为控件所指的尺寸的思路:

实例化BitmapFactory . Options对象来获取解析属性对象,设置BitmapFactory. Options的属性inJustDecodeBounds为true后,再解析位图时并不分配存储空间,但可以计算出原始图片的宽度和高度,即outWidth和outHeight,将这两个数值与控件的宽高尺寸相除,就可以得到缩放比例,即inSampleSize的值,然后重新设置inJustDecodeBounds为false,inSampleSize为计算所得的缩放比例,重新解析位图文件,即可得到原图的缩略图。

获取控件宽高属性的方法:可以利用控件的getLayoutParams()方法获得控件的LayoutParams对象,通过LayoutParams的Width和Height属性来得到控件的宽高,同样可以利用控件的setLayoutParams( )方法来动态的设置其宽高,其中LayoutParams是继承于Android.view.viewGroup.LayoutParams,LayoutParams类是用于child view(子视图)向parent view(父视图)传递布局(Layout)信息包,它封装了Layout的位置、宽、高等信息。

Bitmap的内存优化:

及时回收Bitmap的内存:Bitmap类有一个方法recycle( ),用于回收该Bitmap所占用的内存,当保证某个Bitmap不会再被使用(因为Bitamap被强制释放后,再次使用它会抛出异常)后,能够在Activity的onStop()方法或onDestory()方法中将其回收,回收方法:

if ( bitmap !=null && !bitmap.isRecycle ( ) ){

        bitmap.recycle( );

        bitmap=null ;

        }

   System.gc( ) ;

System.gc()方法可以加快系统回收内存的到来;

捕获异常:为了避免应用在分配Bitmap内存时出现OOM异常以后Crash掉,需在对Bitmap实例化的过程中进行OutOfMemory异常的捕获;

缓存通用的Bitmap对象:缓存分为硬盘缓存和内存缓存,将常用的Bitmap对象放到内存中缓存起来,或将从网络上获取到的数据保存到SD卡中;

压缩图片:即以缩略图的形式显示图片。

115. app优化

app优化:(工具:Hierarchy Viewer 分析布局 工具:TraceView 测试分析耗时的)

App启动优化

布局优化

响应优化

内存优化

电池使用优化

网络优化

App启动优化(针对冷启动)

App启动的方式有三种:

冷启动:App没有启动过或App进程被killed, 系统中不存在该App进程, 此时启动App即为冷启动。

热启动:热启动意味着你的App进程只是处于后台, 系统只是将其从后台带到前台, 展示给用户。

介于冷启动和热启动之间, 一般来说在以下两种情况下发生:

(1)用户back退出了App, 然后又启动. App进程可能还在运行, 但是activity需要重建。

(2)用户退出App后, 系统可能由于内存原因将App杀死, 进程和activity都需要重启, 但是可以在onCreate中将被动杀死锁保存的状态(saved instance state)恢复。

优化:

Application的onCreate(特别是第三方SDK初始化),首屏Activity的渲染都不要进行耗时操作,如果有,就可以放到子线程或者IntentService中

布局优化

尽量不要过于复杂的嵌套。可以使用<include>,<merge>,<ViewStub>

响应优化

Android系统每隔16ms会发出VSYNC信号重绘我们的界面(Activity)。

页面卡顿的原因:

(1)过于复杂的布局.

(2)UI线程的复杂运算

(3)频繁的GC,导致频繁GC有两个原因:1、内存抖动, 即大量的对象被创建又在短时间内马上被释放.2、瞬间产生大量的对象会严重占用内存区域。

内存优化:参考内存泄露和内存溢出部分

电池使用优化(使用工具:Batterystats & bugreport)

(1)优化网络请求

(2)定位中使用GPS, 请记得及时关闭

网络优化(网络连接对用户的影响:流量,电量,用户等待)可在Android studio下方logcat旁边那个工具Network Monitor检测

API设计:App与Server之间的API设计要考虑网络请求的频次, 资源的状态等. 以便App可以以较少的请求来完成业务需求和界面的展示.

Gzip压缩:使用Gzip来压缩request和response, 减少传输数据量, 从而减少流量消耗.

图片的Size:可以在获取图片时告知服务器需要的图片的宽高, 以便服务器给出合适的图片, 避免浪费.

网络缓存:适当的缓存, 既可以让我们的应用看起来更快, 也能避免一些不必要的流量消耗.

24、图片优化

(1)对图片本身进行操作。尽量不要使用setImageBitmap、setImageResource、BitmapFactory.decodeResource来设置一张大图,因为这些方法在完成decode后,

最终都是通过java层的createBitmap来完成的,需要消耗更多内存.

(2)图片进行缩放的比例,SDK中建议其值是2的指数值,值越大会导致图片不清晰。

(3)不用的图片记得调用图片的recycle()方法

116.性能优化(非常重要)

9.1 性能优化分析工具学习

System Trace

Hierarchy Viewer

TraceView

9.2 布局优化

布局优化相对比较容易,优化可以先从布局来展开。使用 Hierarchy Viewer 和开发者模

式中关于布局绘制的选项,可以查到一些问题然后进行修改。

布局嵌套过深:层级嵌套过深的话,深度遍历各个节点会非常消耗时间,这也是布局优化余地最大的一个点了。很多过深的层级是不必要的。如果布局真的很复杂,不深度嵌套没法实现想要的效果。可以尝试约束布局 Constraintlayout 。

使用合适的布局: 三种常见的 ViewGroup 的绘制速度:FrameLayout >LinerLayout > RelativeLayout。当然,如果用 RelativeLayout 可以避免布局嵌套的话是值得的。可以根据这些去决定选用什么样的布局。

使用 include 标签: 在布局文件中,标签可以指定插入一段布局文件到当前布局。这样的话既提高了布局复用,也减少了我们的代码书写。另外,merge标签可以和include的标签一起使用从而减少布局层级。

ViewStub 延时加载: 有些布局,比如网络出错的布局,没必要在所有时候都加载出来。使用 ViewStub 可以实现按需加载。ViewStub 本身没有宽高,加载起来几乎不消耗什么资源。当对他setVisibility(View.VISIBLE)的时候会调用它引用的真实布局填充到当前位置,从而实现了延时加载,节省了正常加载的时间。

移除 Activity 默认背景 只要我们不需要 Activity 的默认背景,就可以移除掉,以减少 Activity 启动时的渲染时间,提升启动效率。移动方法见下:

<style name="Theme.NoBackground" parent="Theme.AppCompat.Light.NoActionBar">

    <item name="android:windowNoTitle">true</item>

    <item name="android:windowBackground">@null</item>

</style>

9.3 线程优化

线程调度原理

任意时刻,只有一个线程占用CPU,处于运行状态

多线程并发,轮流获取CPU使用权

JVM 负责线程的调度:按照特定的机制分配CPU使用权

线程的创建和销毁会带来比较大的性能开销。因此线程优化也很有必要。查看项目中是否存在随意 new thread,线程缺乏管理的情况。使用 AsyncTask 或者线程池对线程进行管理,可以提升 APP 的性能。另外,我比较推荐使用 Rxjava 来实现异步操作,既方便又优雅。

9.4 网络优化

连接复用:节省连接建立时间,如开启 keep-alive。于Android来说默认情况下HttpURLConnection和HttpClient都开启了keep-alive。

请求合并:即将多个请求合并为一个进行请求,比较常见的就是网页中的CSS Image Sprites。同一个页面数据尽量放到一个接口中去处理。

减少请求数据的大小:对于post请求,body可以做gzip压缩的,header也可以做数据压缩(不过只支持http 2.0)。 返回数据的body也可以做gzip压缩,body数据体积可以缩小到原来的30%左右(也可以考虑压缩返回的json数据的key数据的体积,尤其是针对返回数据格式变化不大的情况,支付宝聊天返回的数据用到了)。

根据用户的当前的网络质量来判断下载什么质量的图片(电商用的比较多)。

使用HttpDNS优化DNS:DNS存在解析慢和DNS劫持等问题,DNS 不仅支持 UDP,它还支持 TCP,但是大部分标准的 DNS 都是基于 UDP 与 DNS 服务器的 53 端口进行交互。HTTPDNS 则不同,顾名思义它是利用 HTTP 协议与 DNS 服务器的 80 端口进行交互。不走传统的 DNS 解析,从而绕过运营商的 LocalDNS 服务器,有效的防止了域名劫持,提高域名解析的效率。

大量数据的加载采用分页的方式;上传图片时,在必要的时候压缩图片。

9.5 Apk 包体优化

Apk 组成结构:

整体优化

分离应用的独立模块,以插件的形式加载

解压APK,重新用 7zip 进行压缩

用 apksigner 签名工具 替代 java 提供的 jarsigner 签名工具

资源优化

可以只用一套资源图片,一般采用 xhdpi 下的资源图片

通过扫描文件的 MD5 值,找出名字不同,内容相同的图片并删除

通过 Lint 工具扫描工程资源,移除无用资源

通过 Gradle 参数配置 shrinkResources=true(资源缩减)

对 png 图片压缩;图片资源考虑采用 WebP 格式

避免使用帧动画,可使用 Lottie 动画库

优先考虑能否用 shape 代码、.9 图、svg 矢量图、VectorDrawable 类来替换传统的图片

代码优化

启用混淆以移除无用代码(minifyEnabled true )

开启代码压缩( minifyEnabled true //打开代码压缩)

剔除 R 文件

用注解替代枚举

.arsc文件优化

移除未使用的备用资源来优化 .arsc 文件

android {

    defaultConfig {

        ...

        resConfigs "zh", "zh_CN", "zh_HK", "en"

    }

}

so库打包优化

只提供对主流架构的支持,比如 arm,对于 mips 和 x86 架构可以考虑不提供支持

android {

    defaultConfig {

        ...

        ndk {

            abiFilters  "armeabi-v7a"

        }

    }

}

9.6 内存优化(非常重要)

首先需要了解ava 内存回收机制——GC机制,Java 对象引用方式 —— 强引用、软引用、弱引用和虚引用。

基础知识:

9.6.1 Android 内存管理机制

1.针对进程的内存策略

进程的内存分配策略为:由 ActivityManagerService 集中管理所有进程的内存分配。

进程的内存回收策略为:首先Application Framework 决定回收的类型,当进程的内存空间紧张时会按照进程优先级由低到高的顺序自动回收进程及内存。

Android将进程分为5个优先级,具体如下:

真正执行回收进程的操作的是 Linux 内核。

梳理一下整体流程:

ActivityManagerService 对所有进程进行评分。

更新评分到 Linux 内核。

由 Linux 内核完成真正的内存回收。

2.针对对象、变量的内存策略

Android的对于对象、变量的内存策略同 Java

内存管理 = 对象 / 变量的内存分配 + 内存释放

内存分配策略

对象,变量的内存分配有系统负责,共有三种:静态分配、栈式分配、堆式分配,分别面向静态变量,动态变量和对象实例。

内存释放策略

对象,变量的内存释放由Java的垃圾回收器GC负责。

内存分配注意:(非常重要)

成员变量全部存储在堆中(包括基本数据类型,引用及引用的对象实体)—因为他们属于类,类对象最终还是要被new出来的。

局部变量的基本数据类型和引用存储于栈当中,引用的对象实体存储在堆中。—–因为他们属于方法当中的变量,生命周期会随着方法一起结束。

public class Sample {   

    // 该类的实例对象的成员变量s1、mSample1及指向的对象都存放在堆内存中

    int s1 = 0;

    Sample mSample1 = new Sample();  

       

        // 方法中的局部变量s2、mSample2存放在 栈内存

        // 变量mSample2所指向的对象实例存放在 堆内存

    public void method() {       

        int s2 = 0;

        Sample mSample2 = new Sample();

    }

}

         // 变量mSample3的引用存放在栈内存中

    // 变量mSample3所指向的对象实例存放在堆内存

         // 该实例的成员变量s1、mSample1也存放在堆内存中

    Sample mSample3 = new Sample();

9.6.2 Android的内存泄漏、内存溢出、内存抖动概念

内存泄露

即 ML (Memory Leak),指 程序在申请内存后,当该内存不需再使用但却无法被释放,归还给 程序的现象。对应用程序的影响:容易使得应用程序发生内存溢出,即OOM(out of Memory)

发生内存泄露的本质原因:

本质原因:持有引用者的生命周期>被引用者的生命周期

解释:本该回收的对象(该对象已经不再被使用),由于某些原因(如被另一个正在使用的对象引用)不能被回收。

内存抖动

优化方案

尽量避免频繁创建大量、临时的小对象

9.6.3 如何避免OOM(内存泄漏优化)。

1.减小对象的内存占用

1)使用更加轻量的数据结构

例如,我们可以考虑使用ArrayMap/SparseArray而不是HashMap等传统数据结构,下图演示了HashMap的简要工作原理,相比起Android系统专门为移动操作系统编写的ArrayMap容器,在大多数情况下,都显示效率低下,更占内存。通常的HashMap的实现方式更加消耗内存,因为它需要一个额外的实例对象来记录Mapping操作。另外,SparseArray更加高效在于他们避免了对key与value的autobox自动装箱,并且避免了装箱后的解箱。

可以参考Android性能优化典范 - 第3季

2)避免在Android里面使用Enum

3)减小Bitmap对象的内存占用

Bitmap是一个极容易消耗内存的大胖子,减小创建出来的Bitmap的内存占用是很重要的,通常来说有下面2个措施:

inSampleSize:缩放比例,在把图片载入内存之前,我们需要先计算出一个合适的缩放比例,避免不必要的大图载入。

decode format:解码格式,选择ARGB_8888/RBG_565/ARGB_4444/ALPHA_8,存在很大差异。

4)使用更小的图片

在设计给到资源图片的时候,我们需要特别留意这张图片是否存在可以压缩的空间,是否可以使用一张更小的图片。尽量使用更小的图片不仅仅可以减少内存的使用,还可以避免出现大量的InflationException。

2.内存对象的重复利用

1)复用系统自带的资源

Android系统本身内置了很多的资源,例如字符串/颜色/图片/动画/样式以及简单布局等等,这些资源都可以在应用程序中直接引用。这样做不仅仅可以减少应用程序的自身负重,减小APK的大小,另外还可以一定程度上减少内存的开销,复用性更好。但是也有必要留意Android系统的版本差异性,对那些不同系统版本上表现存在很大差异,不符合需求的情况,还是需要应用程序自身内置进去。

2)注意在ListView/GridView等出现大量重复子组件的视图里面对ConvertView的复用

3)Bitmap对象的复用

在ListView与GridView等显示大量图片的控件里面需要使用LRU的机制来缓存处理好的Bitmap。

4)避免在onDraw方法里面执行对象的创建

类似onDraw等频繁调用的方法,一定需要注意避免在这里做创建对象的操作,因为他会迅速增加内存的使用,而且很容易引起频繁的gc,甚至是内存抖动。

5)StringBuilder

在有些时候,代码中会需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用StringBuilder来替代频繁的“+”。

3.避免对象的内存泄露

内存对象的泄漏,会导致一些不再使用的对象无法及时释放,这样一方面占用了宝贵的内存空间,很容易导致后续需要分配内存的时候,空闲空间不足而出现OOM。显然,这还使得每级Generation的内存区域可用空间变小,gc就会更容易被触发,容易出现内存抖动,从而引起性能问题。(LeakCanary开源控件,可以很好的帮助我们发现内存泄露的情况)

1)注意Activity的泄漏

通常来说,Activity的泄漏是内存泄漏里面最严重的问题,它占用的内存多,影响面广,我们需要特别注意以下两种情况导致的Activity泄漏:

内部类引用导致Activity的泄漏:非静态(匿名)内部类会默认持有外部类引用。

Handler导致的Activity泄漏(最经典的场景),如果Handler中有延迟的任务或者是等待执行的任务队列过长,都有可能因为Handler继续执行而导致Activity发生泄漏。此时的引用关系链是Looper -> MessageQueue -> Message -> Handler -> Activity。为了解决这个问题,可以在UI退出之前,执行remove Handler消息队列中的消息与runnable对象(removeCallbacksAndMessages(null)–同时清空消息队列 ,结束Handler生命周期)。或者是使用Static + WeakReference的方式来达到断开Handler与Activity之间存在引用关系的目的。为了保证Handler中消息队列中的所有消息都能被执行,此处推荐使用 静态内部类 + 弱引用的方式。

线程造成的内存泄漏

在 Activity 内定义了一个匿名的 AsyncTask 对象,就有可能发生内存泄漏。如果 Activity 被销毁之后 AsyncTask 仍然在执行,那就会阻止垃圾回收器回收Activity 对象,进而导致内存泄漏,直到执行结束才能回收 Activity。

同样的,使用 Thread 和 TimerTask 也可能导致 Activity 泄漏。只要它们是通过匿名类创建的,尽管它们在单独的线程被执行,它们也会持有对 Activity 的强引用,进而导致内存泄漏。

总结:

内部类引起的泄漏不仅仅会发生在Activity上,其他任何内部类出现的地方,都需要特别留意!我们可以考虑尽量使用static类型的内部类,同时使用WeakReference的机制来避免因为互相引用而出现的泄露。

2)考虑使用Application Context而不是Activity Context

对于大部分非必须使用Activity Context的情况(Dialog的Context就必须是Activity Context),我们都可以考虑使用Application Context而不是Activity的Context,这样可以避免不经意的Activity泄露。(Activity Context被传递到其他实例中,这可能导致Activity自身被引用而发生泄漏)

3)注意临时Bitmap对象的及时回收

4)注意监听器的注销

在Android程序里面存在很多需要register与unregister的监听器,我们需要确保在合适的时候及时unregister那些监听器。自己手动add的listener,需要记得及时remove这个listener。

5)注意WebView的泄漏

Android中的WebView存在很大的兼容性问题,不仅仅是Android系统版本的不同对WebView产生很大的差异,另外不同的厂商出货的ROM里面WebView也存在着很大的差异。更严重的是标准的WebView存在内存泄露的问题,看这里WebView causes memory leak - leaks the parent Activity。所以通常根治这个问题的办法是为WebView开启另外一个进程,通过AIDL与主进程进行通信,WebView所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放。

6)注意Cursor对象是否及时关闭

在程序中我们经常会进行查询数据库的操作,但时常会存在不小心使用Cursor之后没有及时关闭的情况。这些Cursor的泄露,反复多次出现的话会对内存管理产生很大的负面影响,我们需要谨记对Cursor对象的及时关闭。

9.6.4 常用的内存检查工具。

(1)Memory Monitor 工具:

它是Android Studio自带的一个内存监视工具,它可以很好地帮助我们进行内存实时分析。通过点击Android Studio右下角的Memory Monitor标签,打开工具可以看见较浅蓝色代表free的内存,而深色的部分代表使用的内存从内存变换的走势图变换,可以判断关于内存的使用状态,例如当内存持续增高时,可能发生内存泄漏;当内存突然减少时,可能发生GC等,如下图所示。

(2)LeakCanary工具:

LeakCanary是Square公司基于MAT开发的一款监控Android内存泄漏的开源框架。其工作的原理是: 监测机制利用了Java的WeakReference和ReferenceQueue,通过将Activity包装到WeakReference中,被WeakReference包装过的Activity对象如果被回收,该WeakReference引用会被放到ReferenceQueue中,通过监测ReferenceQueue里面的内容就能检查到Activity是否能够被回收(在ReferenceQueue中说明可以被回收,不存在泄漏;否则,可能存在泄漏,LeakCanary是执行一遍GC,若还未在ReferenceQueue中,就会认定为泄漏)。

如果Activity被认定为泄露了,就抓取内存dump文件(Debug.dumpHprofData);之后通过HeapAnalyzerService.runAnalysis进行分析内存文件分析;接着通过HeapAnalyzer (checkForLeak—findLeakingReference—findLeakTrace)来进行内存泄漏分析。最后通过DisplayLeakService进行内存泄漏的展示。

(3)Android Lint 工具:

Android Lint Tool 是Android Sutido种集成的一个Android代码提示工具,它可以给你布局、代码提供非常强大的帮助。硬编码会提示以级别警告,例如:在布局文件中写了三层冗余的LinearLayout布局、直接在TextView中写要显示的文字、字体大小使用dp而不是sp为单位,就会在编辑器右边看到提示。

9.7 电量优化

Battery Historian(电量使用记录分析工具)

Battery Historian是Android 5.0开始引入的新API。通过下面的指令,可以得到设备上的电量消耗信息。

电量优化的一些建议:

1.充电时执行任务

 if (!checkForPower()) {

        Toast.makeText(view.getContext(), "当前非充电状态", Toast.LENGTH_SHORT).show();

        return;

    }

    /**

         * 是否充电

         * AC --- 交流电

         * USB

         * WireLess -- 无线充电

         *

         * @return

         */

        private boolean checkForPower() {

            IntentFilter mIntentFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);

            Intent intent = registerReceiver(null, mIntentFilter);

            int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);

            boolean isUsb = plugged == BatteryManager.BATTERY_PLUGGED_USB;

            boolean isAc = plugged == BatteryManager.BATTERY_PLUGGED_AC;

            boolean isWireless = false;

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {

                //api >= 17

                isWireless = plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS;

            }

            return (isUsb || isAc || isWireless);

        }

2.连接Wifi后执行任务

我们知道wifi网络传输的电量消耗要比移动网络少很多,应该尽量减少移动网络下的数据传输,多在WiFi环境下传输数据,所以我们可以把一些不需要实时性的任务留到连接wifi后在执行

3.wake_lock

系统为了节省电量,CPU在没有任务忙的时候就会自动进入休眠。有任务需要唤醒CPU高效执行的时候,就会给CPU加wake_lock锁。但是使用wake_lock结束时需要释放锁,如果忘记释放,会使得CPU一直执行消耗电量,所以推荐使用带超时的wake lock或者WakefulBroadcastReceiver

 wakeLock.acquire(timeout);

4.大量高频次的CPU唤醒及操作集中处理

5.定位

定位完成,及时关闭

如果需要实时定位,减少更新频率

根据实际情况,选择gsp定位还是网络定位,降低电量消耗

6.网络优化可以促进电量优化

117. Android对HashMap做了优化后推出的新的容器类是什么?

SparseArray

它要比 HashMap 节省内存,某些情况下比HashMap性能更好,按照官方问答的解释,主要是因为SparseArray不需要对key和value进行auto-boxing(将原始类型封装为对象类型,比如把int类型封装成Integer类型),结构比HashMap简单(SparseArray内部主要使用两个一维数组来保存数据,一个用来存key,一个用来存value)不需要额外的额外的数据结构(主要是针对HashMap中的HashMapEntry而言的)。

118.谈谈你对安卓签名的理解

每个应用都必须签名

应用可以被不同的签名文件签名(如果有源代码或者反编译后重新编译)

同一个应用如果签名不同则不能覆盖安装

119.请解释安卓为啥要加签名机制?

为什么要签名

发送者的身份认证:由于开发商可能通过使用相同的 Package Name 来混淆替换已经安装的程序,以此保证签名不同的包不被替换

保证信息传输的完整性:签名对于包中的每个文件进行处理,以此确保包中内容不被替换

防止交易中的抵赖发生:Market(应用市场)对软件的要求

给apk签名可以带来以下好处:

应用程序升级:能无缝升级到新的版本,必须要同一个证书进行签名并且包名称要相同。(如果证书不同,可能会被系统认为是不同的应用)

应用程序模块化:Android系统可以允许同一个证书签名的多个应用程序在一个进程里运行(系统实际把他们作为一个单个的应用程序),此时就可以把我们的应用程序以模块的方式进行部署,而用户可以独立的升级其中的一个模块

代码或者数据共享:Android提供了基于签名的权限机制,那么一个应用程序就可以为另一个以相同证书签名的应用程序公开自己的功能。

签名的说明

所有的应用程序都必须有数字证书:Android 系统不会安装一个没有数字证书的应用程序

Android 程序包使用的数字证书可以是自签名的:不需要一个权威的数字证书机构签名认证

使用一个合适的私钥生成的数字证书来给程序签名:如果要正式发布一个 Android 应用,必须使用一个合适的私钥生成的数字证书来给程序签名,而不能使用 adt 插件或者 ant 工具生成的调试证书来发布

数字证书都是有有效期的:Android 只是在应用程序安装的时候才会检查证书的有效期。如果程序已经安装在系统中,即使证书过期也不会影响程序的正常功能

120.权限管理系统(底层的权限是如何进行 grant 的)?

permission 的初始化:是指 permission 的向系统申请,系统进行检测并授权,并建立相应的数据结构。

权限检查:Android framework 中提供了一些接口用来对外来的访问(包括自己)进行权限检查 。 这些接口 主要通过 ContextWrapper 提供,具体实现在 ContextImpl 中 。ContextImpl.java 中提供的 API ,其实都是由 ActivityManagerService 中的如下几个接口进行的封装。

其中有一个checkPermission() // 主要用于一般的 permission 检查

checkPermission 的实现分析

如果传入的 permission 名称为 null ,那么返回 PackageManager.PERMISSION_DENIED 。

判断调用者 uid 是否符合要求 。

如果通过 2 的检查后,再 调用 PackageManagerService.checkUidPermission ,判断 这个 uid 是否拥有相应的权限

权限校验之后,应给分发了

121. Kotlin 如何在 Android 上运行?

就像 Java 一样,Kotlin 代码也被编译成 Java 字节码,并在运行时由 Java 虚拟机即 JVM 执行。当一个名为 Kotlin 的文件Main.kt被编译后,它最终会变成一个类,然后生成该类的字节码。字节码文件的名称将是MainKt.class,并且该文件将由 JVM 执行。

122. 为什么要使用 Kotlin?

Kotlin 简洁

Kotlin 是空值安全的

Kotlin 是可互操作的

123. 用var和val声明变量有什么区别?

如果你想声明一些可变(可变)变量,那么你可以使用var. 对于不可变变量,使用valieval变量一旦赋值就不能改变。

124. 用val和const声明变量有什么区别?

声明的变量本质val上const都是不可变的。但是const变量的值必须在编译时知道,而val变量的值也可以在运行时分配。

125. Kotlin 中如何保证 null 安全?

使用 Kotlin 的主要优势之一是 null 安全性。在 Java 中,如果您访问一些 null 变量,那么您将获得一个NullPointerException. 因此,Kotlin 中的以下代码将产生编译时错误:

var name: String = "码农乐园"

name = null //error

因此,要将空值分配给变量,您需要将name变量声明为可空字符串,然后在访问此变量期间,您需要使用安全调用运算符,即?.

var name: String? = "码农乐园"

print(name?.length) // ok

name = null // ok

126.安全调用(?.)和空值检查(!!)有什么区别?

安全调用运算符 ie?.用于检查变量的值是否为空。如果为 null 则返回 null ,否则返回所需的值。

var name: String? = "码农乐园"

println(name?.length) // 8

name = null

println(name?.length) // null

如果要在变量值为 null 时抛出 NullPointerException,则可以使用 null 检查或!!运算符。

var name: String? = "码农乐园"

println(name?.length) // 8

name = null

println(name!!.length) // KotlinNullPointerException

127. Kotlin 中是否有像 java 一样的三元运算符?

不,我们在 Kotlin 中没有三元运算符,但您可以通过 if-else 或 Elvis 运算符来使用三元运算符的功能。

128. Kotlin 中的 Elvis 运算符是什么?

在 Kotlin 中,您可以使用 null 安全属性将 null 值分配给变量。要检查一个值是否具有空值,那么您可以使用 if-else 或可以使用 Elvis 运算符?:,例如:

var name:String? = "码农乐园"

val nameLength = name?.length ?: -1

println(nameLength)

上面使用的 Elvis 运算符(?:)将返回 name 的长度,如果 value 不为 null,否则如果 value 为 null,则返回-1。

129. 如何将 Kotlin 源文件转换为 Java 源文件?

将 Kotlin 源文件转换为 Java 源文件的步骤:

在 IntelliJ IDEA / Android Studio 中打开您的 Kotlin 项目。

然后导航到工具 > Kotlin > 显示 Kotlin 字节码。

现在单击反编译按钮以从字节码中获取您的 Java 代码。

130.你觉得Kotlin与Java混合开发时需要注意哪些问题?

kotlin调用java的时候,如果java返回值可能为null 那就必须加上@nullable@nullable否则kotlin无法识别,也就不会强制你做非空处理,一旦java返回了null 那么必定会出现null指针异常,加上@nullable注解@nullable之后kotlin就能识别到java方法可能会返回null,编译器就能会知道,并且强制你做非null处理,这也就是kotlin的空安全

131.@JvmStatic、@JvmOverloads、@JvmFiled 在 Kotlin 中有什么用?

@JvmStatic:这个注解用来告诉编译器该方法是静态方法,可以在Java代码中使用。

@JvmOverloads:要在 Java 代码中使用 Kotlin 代码中作为参数传递的默认值,我们需要使用@JvmOverloads注解。

@JvmField:要在不使用任何 getter 和 setter 的情况下从 Java 代码访问 Kotlin 类的字段,我们需要@JvmField在 Kotlin 代码中使用。

二.Kotlin 中注解 @JvmOverloads 的作用?

在Kotlin中@JvmOverloads注解的作用就是:在有默认参数值的方法中使用@JvmOverloads注解,则Kotlin就会暴露多个重载方法。如果没有加注解@JvmOverloads则只有一个方法,kotlin调用的话如果没有传入的参数用的是默认值。

@JvmOverloads fun f(a: String, b: Int=0, c:String="abc"){

}

// 相当于Java三个方法 不加这个注解就只能当作第三个方法这唯一一种方法

void f(String a)

void f(String a, int b)

// 加不加注解,都会生成这个方法

void f(String a, int b, String c)

132. Kotlin 中的数据类是什么?

数据类是那些仅用于存储一些数据的类。在 Kotlin 中,它被标记为数据。以下是相同的示例:

data class Developer(val name: String, val age: Int)

当我们将一个类标记为数据类时,您不必像我们在 Java 中那样实现或创建以下函数:hashCode()、equals()、toString()、copy()。编译器会在内部自动创建这些,因此也可以生成干净的代码。虽然,数据类需要满足的其他要求很少。

133.Kotlin中的数据类型有隐式转换吗?为什么?

kotlin有隐式转换,在计算过程中一般都转换成其中的较大类型,因为防止精度丢失。

134.Kotlin中可以使用int、double、float等原始类型吗?

在 Kotlin 中,我们不能直接使用原始类型。我们可以使用 Int、Double 等类作为基元的对象包装器。但是编译后的字节码具有这些原始类型。

135. Kotlin 中的字符串插值是什么?

如果您想在字符串中使用某个变量或执行某些操作,则可以使用字符串插值。您可以使用$符号来使用字符串中的某些变量,也可以在{}符号之间执行一些操作。

var name = "码农乐园"

print("Hello! I am learning from $name")

136. Kotlin 中的解构是什么意思?

解构是一种从存储在(可能是嵌套的)对象和数组中的数据中提取多个值的便捷方式。它可用于接收数据的位置(例如分配的左侧)。有时将一个对象分解为多个变量是很方便的,例如:

val (name, age) = developer

现在,我们可以独立使用姓名和年龄,如下所示:

println(name)

println(age)

从 MindOrks 博客了解有关 Kotlin 解构的更多信息。

137.在Kotlin中,何为解构?该如何使用?

在kotlin中所谓的解构就是将一个类对象中的参数拆开来,成为一个一个单独的变量,从而来使用这些单独的变量进行操作。

使用方式:

1.常规使用方式:

val (name, age) = person

println(name)

println(age)

2.还可以在for需要中获取Map的key、value值

for ((key, value) in map) {

   // 使用该 key、value 做些事情

}

3.如果在解构声明中你不需要某个变量,那么可以用下划线取代其名称:

val (_, status) = getResult()

 4.在 lambda 表达式中解构

map.mapValues { entry -> "${entry.value}!" }

map.mapValues { (key, value) -> "$value!" }

139. 如何检查一个lateinit变量是否已经初始化?

您可以在方法的帮助下使用它之前检查 lateinit 变量是否已初始化isInitialized。如果 lateinit 属性已初始化,则此方法将返回 true,否则将返回 false。例如:

class Person {

lateinit var name: String

fun initializeName() {

 println(this::name.isInitialized)

 name = "MindOrks" // initializing name

 println(this::name.isInitialized)

 }

}

fun main(args: Array<String>) {

 Person().initializeName()

}

140. Kotlin 中的 lateinit 和 lazy 有什么区别?

lazy 只能用于 val 属性,而 lateinit 只能用于 var,因为它不能编译为 final 字段,因此不能保证不变性。

如果您希望您的属性以一种事先可能未知的方式从外部初始化,请使用 lateinit。

141.==操作符和===操作符有什么区别?

是的。==运算符用于比较变量中存储的值,运算===符用于检查变量的引用是否相等。但是在原始类型的情况下,===操作符也会检查值而不是引用。

// primitive example

val int1 = 10

val int2 = 10

println(int1 == int2) // true

println(int1 === int2) // true

// wrapper example

val num1 = Integer(10)

val num2 = Integer(10)

println(num1 == num2) // true

println(num1 === num2) //false

142. Kotlin 中的 forEach 是什么?

在 Kotlin 中,要像在 Java 中一样使用 for-each 循环的功能,我们使用forEach函数。以下是相同的示例:

var listOfMindOrks = listOf("码农乐园", "阿福", "dom")

listOfMindOrks.forEach {

 Log.d(TAG,it)

}

143. Kotlin 中的伴生对象是什么?

在 Kotlin 中,如果您想编写一个函数或任何可以在没有类实例的情况下调用的类成员,那么您可以将其编写为类中伴随对象的成员。

要创建伴生对象,您需要companion在对象声明前添加关键字。

以下是 Kotlin 中伴随对象的示例:

class ToBeCalled {

companion object Test {

fun callMe() = println("You are calling me :)")

 }

}

fun main(args: Array<String>) {

 ToBeCalled.callMe()

}

144.kotlin中Unit的应用以及和Java中void的区别?

在java中,必须指定返回类型,即void不能省略,但是在kotlin中,如果返回为unit,可以省略。

java中void为一个关键字,但是在kotlin中unit是一个类

九.Kotlin 中 infix 关键字的原理和使用场景?

infix可以自定义操作符,比如1 to 2 这种的, 1 add 2,让程序更加语义化

155. Kotlin 中的 Java 静态方法等价物是什么?

要在 Kotlin 中实现类似于 Java 静态方法的功能,我们可以使用:

伴生对象

包级函数

目的

156. Kotlin 中的 FlatMap 和 Map 有什么区别?

FlatMap 用于将列表的所有项目组合成一个列表。

Map 用于根据特定条件转换列表。

23. Kotlin 中的 List 和 Array 类型有什么区别?

如果您有一个具有固定大小的数据列表,那么您可以使用数组。但是如果列表的大小可以变化,那么我们必须使用可变列表。

157. Kotlin中可以使用new关键字实例化一个类对象吗?

不,在 Kotlin 中,我们不必使用new关键字来实例化类对象。要实例化一个类对象,我们只需使用:

var varName = ClassName()

158. Kotlin 中的可见性修饰符是什么?

可见性修饰符或访问说明符或访问修饰符是一个概念,用于定义编程语言中某事物的范围。在 Kotlin 中,我们有四个可见性修饰符:

private:在包含声明的特定类或文件中可见。

protected:在特定类或文件中可见,并且在声明它的特定类的子类中可见。

internal:在该特定模块中的任何地方都可见。

公开:对所有人可见。

注意:默认情况下,Kotlin 中的可见性修饰符是public。

159. Kotlin中的可见性修饰符有哪些?相比于 Java 有什么区别?

kotlin存在四种可见性修饰符,默认是public。 private、protected、internal、public

1.private、public是和java中的一样的,protected java中同一个包可见,kotlin中不可见。

2.不同的是java中默认是default修饰符(包可见),而kotlin存在internal修饰符(模块内部可见)。

3.kotlin可以直接在文件顶级声明方法、变量等。其中protected不能用来修饰在文件顶级声明的类、方法、变量等。

构造方法默认是public修饰,可以使用可见性修饰符修饰constructor关键字来改变构造方法的可见性。

160. 如何在 Kotlin 中创建 Singleton 类?

单例类是这样定义的类,即只能创建该类的一个实例,并在我们只需要该类的一个实例的情况下使用,例如日志记录、数据库连接等。

要在 Kotlin 中创建 Singleton 类,您需要使用 object 关键字。

object AnySingletonClassName

注意:不能在对象中使用构造函数,但可以使用 init。

161. Kotlin 中的初始化块是什么?

init块是在主构造函数执行后立即执行的初始化块。一个类文件可以有一个或多个将串行执行的初始化块。如果你想在主构造函数中执行一些操作,那么在 Kotlin 中是不可能的,为此你需要使用init块。

162. Kotlin 中的构造函数有哪些类型?

主构造函数:这些构造函数是在类头中定义的,你不能在其中执行一些操作,这与 Java 的构造函数不同。

辅助构造函数:这些构造函数是在类体内使用构造函数关键字声明的。您必须从辅助构造函数显式调用主构造函数。此外,不能在辅助构造函数中声明类的属性。Kotlin 中可以有多个二级构造函数。

163.主构造函数和次构造函数之间有什么关系吗?

是的,当使用辅助构造函数时,您需要显式调用主构造函数。

164.构造函数中使用的默认参数类型是什么?

默认情况下,val 中构造函数的参数类型。但是您可以将其显式更改为 var 。

165.谈谈kotlin中的构造方法?有哪些注意事项?

1.概要简述

kotlin中构造函数分为主构造和次级构造两类

使用关键词constructor标记次级构造函数,部分情况可省略

init关键词用于初始化代码块,注意与构造函数的执行顺序,类成员的初始化顺序

继承,扩展时候的构造函数调用逻辑

特殊的类如data class、object/componain object、sealed class等构造函数情况与继承问题

构造函数中的形参声明情况

2.详细说明

主/次 构造函数

kotlin中任何class(包括object/data class/sealed class)都有一个默认的无参构造函数

如果显式的声明了构造函数,默认的无参构造函数就失效了。

主构造函数写在class声明处,可以有访问权限修饰符private,public等,且可以省略constructor关键字。

若显式的在class内声明了次级构造函数,就需要委托调用主构造函数。

若在class内显式的声明处所有构造函数(也就是没有了所谓的默认主构造),这时候可以不用依次调用主构造函数。例如继承View实现自定义控件时,三四个构造函数同时显示声明。

init初始化代码块

kotlin中若存在主构造函数,其不能有代码块执行,init起到类似作用,在类初始化时侯执行相关的代码块。

init代码块优先于次级构造函数中的代码块执行。

即使在类的继承体系中,各自的init也是优先于构造函数执行。

在主构造函数中,形参加有var/val,那么就变成了成员属性的声明。这些属性声明是早于init代码块的。

特殊类

object/companion object是对象示例,作为单例类或者伴生对象,没有构造函数。

data class要求必须有一个含有至少一个成员属性的主构造函数,其余方面和普通类相同。

sealed class只是声明类似抽象类一般,可以有主构造函数,含参无参以及次级构造等。

166.Kotlin 中的扩展函数是什么

kotlin中的扩展函数,实际上是在自己的类中添加了一个static final方法,将需要扩展的类,在使用的时候作为第一个参数传入方法中,然后使用:

fun String.hello(str: String): String{

    return "hello" + str + this.length

}

fun String.hello1(str: String): String{

    return "hello$str"

}

经过反编译之后生成字节码如下:

public final class ExtensionTestKt {

   @NotNull

   public static final String hello(@NotNull String $receiver, @NotNull String str) {

      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");

      Intrinsics.checkParameterIsNotNull(str, "str");

      return "hello" + str + $receiver.length();

   }

   @NotNull

   public static final String hello1(@NotNull String $receiver, @NotNull String str) {

      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");

      Intrinsics.checkParameterIsNotNull(str, "str");

      return "hello" + str;

   }

}

kotlin中的扩展函数,实际上就是通过给类添加public static final函数的做法来实现,这样做可以减少utils类的使用。

扩展函数是静态解析的,是采用静态分派的过程来处理。这意味着调用的扩展函数是由函数调用所在的表达式的类型来决定的,而不是由表达式运行时求值结果决定的。这意思其实就是在使用该扩展函数的时候,如果类本身和其子类都进行了同一个函数的扩展,这函数是不会有重写关系的,在使用的时候,只会根据需要使用该方法的对象的实际类型来决定是调用了哪个,就是相当于调用静态方法。而不是动态分派。

167.kotlin基础: From Java To Kotlin

167.常量与变量

//java

String name = "niubi";         final String name = "niubi";

//kotlin

var name = "niubi"              val name = "niubi"

168.null 声明

//java

String name= null;

//kotlin

var name:String?=null

169.空判断

//Java

if (text != null) {

        int length = text.length();

}

//Kotlin

text?.let {

 val length = text.length

}// or simply val length = text?.length

170.字符串拼接

//Java

String firstName = "firstName ";

String lastName = "lastName ";

String message = "My name is: "+ firstName + " " + lastName;

//kotlin

val firstName = "firstName "

val lastName = "lastName "

val message = "My name is: $firstName $lastName"

180.换行

//Java

String text = "First Line\n" +

             "Second Line\n" +

             "Third Line";

//Kotlin

val text = """

        |First Line

        |Second Line

        |Third Line

        """.trimMargin()

181.三元表达式

//Java

String text = x > 5 ? "x > 5" : "x <= 5";

//Kotlin

val text = if (x > 5)

              "x > 5"

           else "x <= 5"

182.操作符

//java

final int andResult  = a & b;

final int orResult   = a | b;

final int xorResult  = a ^ b;

final int rightShift = a >> 2;

final int leftShift  = a << 2;

//Kotlin

val andResult  = a and b

val orResult   = a or b

val xorResult  = a xor b

val rightShift = a shr 2

val leftShift  = a shl 2

。。。。等等

183. Kotlin 中什么时候使用 lateinit 关键字?

lateinit是后期初始化。

通常,声明为非空类型的属性必须在构造函数中初始化。然而,这通常并不方便。

例如,可以通过依赖注入来初始化属性,或者在单元测试的 setup 方法中初始化属性。在这种情况下,您不能在构造函数中提供非 null 初始化程序,但您仍然希望在引用类主体内的属性时避免 null 检查。要处理这种情况,您可以使用 lateinit 修饰符标记该属性。

184.Kotlin 的延迟初始化: lateinit var 和 by lazy

private var name0: String //报错

private var name1: String = "xiaoming"//不报错

private var name2: String? = null //不报错

可是有的时候,我并不想声明一个类型可空的对象,而且我也没办法在对象一声明的时候就为它初始化,那么这时就需要用到 Kotlin 提供的延迟初始化。Kotlin 中有两种延迟初始化的方式:

一种是 lateinit var,一种是 by lazy。

lateinit var

lateinit var 只能用来修饰类属性,不能用来修饰局部变量,并且只能用来修饰对象,不能用来修饰基本类型(因为基本类型的属性在类加载后的准备阶段都会被初始化为默认值)。

lateinit var 的作用:让编译期在检查时不要因为属性变量未被初始化而报错。

Kotlin 相信当开发者显式使用 lateinit var 关键字的时候,他一定也会在后面

某个合理的时机将该属性对象初始化的(然而,谁知道呢,也许他用完才想起还

没初始化)。

by lazy

by lazy的作用:真正做到了声明的同时也指定了延迟初始化时的行为,在属性被第一次被使用的时候能自动初始化。

by lazy 本身是一种属性委托。属性委托的关键字是 by。by lazy 的写法如下:

//用于属性延迟初始化

val name: Int by lazy { 1 }

//用于局部变量延迟初始化

 public fun foo() {

          val bar:String by lazy { "hello" }

          println(bar)

}

by lazy 要求属性声明为 val,即不可变变量,在 java 中相当于被 final 修饰。这意味着该变量一旦初始化后就不允许再被修改值了(基本类型是值不能被修改,对象类型是引用不能被修改)。{}内的操作就是返回唯一一次初始化的结果。

by lazy 可以使用于类属性或者局部变量。

实现原理:(了解附加属性)

当一个属性 name 需要 by lazy 时,具体是怎么实现的:

生成一个该属性的附加属性:name$$delegate;

在构造器中,将使用 lazy(()->T)创建的 Lazy 实例对象赋值给 name$$delegate;

当该属性被调用,即其 getter 方法被调用时返回 name$delegate.getVaule(),而

name$delegate.getVaule()方法的返回结果是对象name$delegate内部的_value属性

值,在 getVaule()第一次被调用时会将_value 进行初始化,往后都是直接将_value 的

值返回,从而实现属性值的唯一一次初始化。

185 Kotlin Tips:怎么用 Kotlin 去提高生产力(kotlin优势)

Tip1- 更简洁的字符串

kotlin除了有单个双引号的字符串,还对字符串加强,引入了三个引号,“”"中可以包含换行、反斜杠等等特殊字符;同时,Kotlin中引入了字符串模版,方便字符串的拼接,可以用$符号拼接变量和表达式。注意,在kotlin中,美元符号$是特殊字符,在字符串中不能直接显示,必须经过转义,方法1是用反斜杠,方法二是${‘$’}

Tip2- Kotlin中大多数控制结构都是表达式

首先,需要弄清楚一个概念语句和表达式,然后会介绍控制结构表达式的优点:简洁

语句和表达式是什么?

表达式有值,并且能作为另一个表达式的一部分使用

语句总是包围着它的代码块中的顶层元素,并且没有自己的值

Kotlin与Java的区别

Java中,所有的控制结构都是语句,也就是控制结构都没有值

Kotlin中,除了循环(for、do和do/while)以外,大多数控制结构都是表达式(if/when等)

Example1:if语句

java中,if 是语句,没有值,必须显式的return

public int max(int a, int b) {

    if (a > b) {

        return a;

    } else {

        return b;

    }

}

kotlin中,if 是表达式,不是语句,因为表达式有值,可以作为值return出去,类似于java中的三目运算符a > b ? a : b

fun max(a: Int, b: Int): Int {

    return if (a > b) a else b

}

上面的if中的分支最后一行语句就是该分支的值,会作为函数的返回值。这其实跟java中的三元运算符类似,

public int max2(int a, int b) {

    return a > b ? a : b;

}

上面是java中的三元运算符,kotlin中if是表达式有值,完全可以替代,故kotlin中已没有三元运算符了,用if来替代。

上面的max函数还可以简化成下面的形式

fun max2(a: Int, b: Int) = if (a > b) a else b

Example2:when语句

Kotlin中的when非常强大,完全可以取代Java中的switch和if/else,同时,when也是表达式,when的每个分支的最后一行为当前分支的值

// java中的switch

         public String getPoint(char grade) {

             switch (grade) {

                 case 'A':

                     return "GOOD";

                 default:

                     return "UN_KNOW";

             }

         }

java中的switch有太多限制,我们再看看Kotlin怎样去简化的

fun getPoint(grade: Char) = when (grade) {

    'A' -> "GOOD"

    else -> "UN_KNOW"

}

同样的,when语句还可以取代java中的if/else if

Tip3- 更好调用的函数:显式参数名(命名参数)/默认参数值

Kotlin的函数更加好调用,主要是表现在两个方面:

1,显式的标示参数名,可以方便代码阅读;

2,函数可以有默认参数值,可以大大减少Java中的函数重载。

@JvmOverloads

在java与kotlin的混合项目中,会发现用kotlin实现的带默认参数的函数,在java中去调用的化就不能利用这个特性了。这时候可以在kotlin的函数前添加注解@JvmOverloads,添加注解后翻译为class的时候kotlin会帮你去生成多个函数实现函数重载。

Tip4-扩展函数和属性

以项目中StringExt.kt中部分代码示例:

//扩展属性:获取String最后一个字符

val String.lastChar: Char

    get() = get(length - 1)

//扩展函数:字符串保留2位小数

fun String.keep2Decimal(): String {

    val format = DecimalFormat()

    format.maximumFractionDigits = 2

    format.isGroupingUsed = false  //不对数字串进行分组

    return format.format(this.toDouble())

}

var name:String="123.321"

//只需import完了就跟使用自己的属性一样方便了。

//使用扩展属性

val lastChar = name.lastChar

//使用扩展函数

val keep2Decimal = name.keep2Decimal()

Kotlin为什么能实现扩展函数和属性这样的特性?

在Kotlin中要理解一些语法,只要认识到Kotlin语言最后需要编译为class字节码,Java也是编译为class执行,也就是可以大致理解为Kotlin需要转成Java一样的语法结构,Kotlin就是一种强大的语法糖而已,Java不具备的功能Kotlin也不能越界的。

上面的扩展函数转成Java后的代码

/*

* 扩展函数会转化为一个静态的函数,同时这个静态函数的第一个参数就是该类的实例对象

* */

/*

* 获取的扩展属性会转化为一个静态的get函数,同时这个静态函数的第一个参数就是该类的实例对象

* */

public static final char getLastChar(@NotNull StringBuilder $receiver) {

    Intrinsics.checkParameterIsNotNull($receiver, "$receiver");

    return $receiver.charAt($receiver.length() - 1);

}

对于扩展函数,转化为Java的时候其实就是一个静态的函数

对于扩展属性也类似,获取的扩展属性会转化为一个静态的get函数

可以看到扩展函数和扩展属性适用的地方和缺陷,有两点:

扩展函数和扩展属性内只能访问到类的公有方法和属性,私有的和protected是访问不了的

扩展函数不能被override,因为Java中它是静态的函数

Tip5- 懒初始化by lazy 和 延迟初始化lateinit

上面已有详细介绍。

Tip6- 不用再手写findViewById

利用kotlin-android-extensions插件,activity中import对应的布局即可。插件会自动根据布局的id生成对应的View成员。直接拿id用即可。如: tip6Tv.text = “XXXX”

原理是什么?插件帮我们做了什么?

在编译阶段,插件会帮我们生成视图缓存,视图由一个Hashmap结构的_$_findViewCache变量缓存,会根据对应的id先从缓存里查找,缓存没命中再去真正调用findViewById查找出来,再存在HashMap中。另外在onDestroyView会清掉缓存。

Fragment需要注意,不能在onCreateView方法里用view,不然会出现空指针异常,需要在onViewCreate里,原理是插件用了getView来findViewById.故在onViewCreated中getView还是空的,原理就好理解了。

Tip7- 利用局部函数抽取重复代码

Kotlin中提供了函数的嵌套,在函数内部还可以定义新的函数。这样我们可以在函数中嵌套这些提前的函数,来抽取重复代码。

Java写法

 private void checkIsBlank(){

     if (TextUtils.isEmpty(textviewOne.getText())){

         throw new IllegalArgumentException("");

     }

     if (TextUtils.isEmpty(textviewTwo.getText())){

         throw new IllegalArgumentException("");

     }

     if (TextUtils.isEmpty(editText.getText())){

         throw new IllegalArgumentException("");

     }

 }

Java优化后代码

 private void checkIsBlank(){

     checkTextView(textviewOne);

     checkTextView(textviewTwo);

     checkTextView(editText);

 }

 private void checkTextView(TextView view){

     if (TextUtils.isEmpty(view.getText())){

         throw new IllegalArgumentException("");

     }

 }

Kotlin写法

fun checkIsBlank(){

    fun checkTextView(view: TextView){

        if (view.text.isNullOrBlank())

            throw IllegalArgumentException("")

    }

    checkTextView(textviewOne)

    checkTextView(textviewTwo)

    checkTextView(editText)

}

Kotlin的写法和Java优化后代码相比,代码量并没有减少,那为什么我们推荐使用局部函数,而不推荐把重复代码提取成一个独立的函数呢?那是因为,在当前代码文件中,我们只有checkIsBlank一个函数使用到了这段重复的代码,别的函数并没有任何相关逻辑代码,所以使用局部函数的话,不仅让重复代码的用途和用处更明确了,函数相关性也大大提高了。

Tip8- 使用数据类来快速实现model类

//Kotlin会为类的参数自动实现get set方法

class User(val name: String, val age: Int, val gender: Int, var address: String)

//用data关键词来声明一个数据类,除了会自动实现get set,还会自动生成equals hashcode toString

data class User2(val name: String, val age: Int, val gender: Int, var address: String)

Tip9- 用类委托来快速实现装饰器模式

通过继承的实现容易导致脆弱性,例如如果需要修改其他类的一些行为,这时候Java中的一种策略是采用装饰器模式:创建一个新类,实现与原始类一样的接口并将原来的类的实例作为一个成员变量。

与原始类拥有相同行为的方法不用修改,只需要直接转发给原始类的实例。如下所示:

* 常见的装饰器模式,为了修改部分的函数,却需要实现所有的接口函数

* */

class CountingSet<T>(val innerSet: MutableCollection<T> = HashSet<T>()) : MutableCollection<T> {

    var objectAdded = 0

    override val size: Int

        get() = innerSet.size

    /*

    * 需要修改的方法

    * */

    override fun add(element: T): Boolean {

        objectAdded++

        return innerSet.add(element)

    }

    /*

    * 需要修改的方法

    * */

    override fun addAll(elements: Collection<T>): Boolean {

        objectAdded += elements.size

        return innerSet.addAll(elements)

    }

    override fun contains(element: T): Boolean {

        return innerSet.contains(element)

    }

    override fun containsAll(elements: Collection<T>): Boolean {

        return innerSet.containsAll(elements)

    }

    override fun isEmpty(): Boolean {

        return innerSet.isEmpty()

    }

    override fun clear() {

        innerSet.clear()

    }

    override fun iterator(): MutableIterator<T> {

        return innerSet.iterator()

    }

    override fun remove(element: T): Boolean {

        return innerSet.remove(element)

    }

    override fun removeAll(elements: Collection<T>): Boolean {

        return innerSet.removeAll(elements)

    }

    override fun retainAll(elements: Collection<T>): Boolean {

        return innerSet.retainAll(elements)

    }

}`

如上所示,想要修改HashSet的某些行为函数add和addAll,需要实现MutableCollection接口的所有方法,将这些方法转发给innerSet去具体的实现。虽然只需要修改其中的两个方法,其他代码都是模版代码。

只要是重复的模版代码,Kotlin这种全新的语法糖就会想办法将它放在编译阶段再去生成。

这时候可以用到类委托by关键字,如下所示:

/*

* 通过by关键字将接口的实现委托给innerSet成员变量,需要修改的函数再去override就可以了

* */

class CountingSet2<T>(val innerSet: MutableCollection<T> = HashSet<T>()) : MutableCollection<T> by innerSet {

    var objectAdded = 0

    override fun add(element: T): Boolean {

        objectAdded++

        return innerSet.add(element)

    }

    override fun addAll(elements: Collection<T>): Boolean {

        objectAdded += elements.size

        return innerSet.addAll(elements)

    }

}

通过by关键字将接口的实现委托给innerSet成员变量,需要修改的函数再去override就可以了,通过类委托将10行代码就可以实现上面接近100行的功能,简洁明了,去掉了模版代码。

Tip10- Lambda表达式简化OnClickListener

Tip11- kotlin常见内联扩展函数来简化代码

内联扩展函数之let

let扩展函数的实际上是一个作用域函数

场景一: 针对一个可null的对象统一做判空处理。

场景二: 明确一个变量所处特定的作用域范围内。

内联函数之with

适用于调用同一个类的多个方法时,可以省去类名重复,直接调用类的方法即可。经常用于Android中RecyclerView中onBinderViewHolder中,数据model的属性映射到UI上

使用前:

使用后:

内联扩展函数之run

适用于let,with函数任何场景。因为run函数是let,with两个函数结合体,准确来说它弥补了let函数在函数体内必须使用it参数替代对象,在run函数中可以像with函数一样可以省略,直接访问实例的公有属性和方法,另一方面它弥补了with函数传入对象判空问题,在run函数中可以像let函数一样做判空处理

借助上个例子,

使用前:

使用后:

内联扩展函数之apply

整体作用功能和run函数很像,唯一不同点就是它返回的值是对象本身,而run函数是一个闭包形式返回,返回的是最后一行的值。正是基于这一点差异它的适用场景稍微与run函数有点不一样。

apply一般用于一个对象实例初始化的时候,需要对对象中的属性进行赋值。或者动态inflate出一个XML的View的时候需要给View绑定数据也会用到,这种情景非常常见。特别是在我们开发中会有一些数据model向View model转化实例化的过程中需要用到。

 menuAdapter = HomeMenuAdapter().apply {

            bindToRecyclerView(recyclerView_menu)

            setOnItemClickListener { _, _, position ->

                when (position) {

                    3 -> ActivityManager.start(ActivateActivity::class.java)

                }

            }

        }

内联扩展函数之also

also函数的结构实际上和let很像唯一的区别就是返回值的不一样,let是以闭包的形式返回,返回函数体内最后一行的值,如果最后一行为空就返回一个Unit类型的默认值。而also函数返回的则是传入对象的本身

适用于let函数的任何场景,also函数和let很像,只是唯一的不同点就是let函数最后的返回值是最后一行的返回值而also函数的返回值是返回当前的这个对象。一般可用于多个扩展函数链式调用

总结:(extension指是否为扩展函数)

Tip11- 高阶函数简化代码

高阶函数:以另一个函数作为参数或者返回值的函数

186. Kotlin数组和集合

5.4.1 kotlin数组

kotlin为数组增加了一个Array类,为元素是基本类型的数组增加了xxArray类(其中xx也就是Byte,Short, Int等基本类型)

Kotlin创建数组大致有如下两种方式:

1.使用arrayOf(), arrayOfNulls(),emptyArray()工具函数。

        var model1=CommonModel()

    var model2=CommonModel()

    var model3=CommonModel()

    //创建包含指定元素的数组,相当于java数组的静态初始化

    var a= arrayOf(model1,model2,model3)

    var b= intArrayOf(1,2,3)

    //创建指定长度为3,元素为null的数组,相当于java数组动态初始化

    var c= arrayOfNulls<Int>(3)

2.使用Array(size: Int, init:(Int) -> T)

第一个参数就是数组的大小。第二个参数是一个函数, init:(Int) -> T 代表这这个方法返回的类型是T只能有一个参数类型是Int型。Int就是该array的所对应的索引。

         private fun arrInit(): (Int) -> Int = { it * 2 }

    var array1 = Array<Int>(5, arrInit())

    var array2 = Array<Int>(5, {it})

5.4.2 kotlin集合

kotlin集合类同样有两个接口派生:Collection和Map。但Kotlin的结合被分成两个大类,可变集合和不可变集合。只有可变集合才可以添加修改,删除等处理操作。不可变集合只能读取元素。

kotlin只提供了HashSet,HashMap, LinkedHashSet, LinkedHashMap, ArrayList这5个集合实现类,而且他们都是可变集合,那么说好的不可变集合呢。kotlin的不可变集合类并没有暴露出来,我们只能通过函数来创建不可变集合。

list集合

创建一个不可变的list

val mList = listOf<Int>(1, 2, 3)

创建一个可变的list

val mList = mutableListOf<Int>(1, 2, 3)

emptyList()——创建一个空集合

listOfNotNull ()—— 创建的集合中不能插入null值

Map

创建一个不可变的Map

val mList = mapOf(Pair("key1", 1), Pair("key2", 2))

或者

//推荐

val mList = mapOf("key1" to 1, "key2" to 2)

创建一个可变的Map

val mList = mutableMapOf("key1" to 1, "key2" to 2)

此外还有

emptyMap()——创建一个空map

hashMapOf()——创建一个hashMap

linkedMapOf()——创建一个linkedMap

sortedMapOf()——创建一个sortedMap

187.Kotlin中的MutableList与List有什么区别?

List:有序接口,只能读取,不能更改元素;

MutableList:有序接口,可以读写与更改、删除、增加元素。

源码分析MutableList就相当于Java中的ArrayList,List是kotlin自己重写的EmptyList,EmptyList中没有提供add方法remove方法等修改元素操作的方法。

5.5 Kotlin集合操作符

Kotlin中关于集合的操作符有六类:

总数操作符

过滤操作符

映射操作符

顺序操作符

生产操作符

元素操作符

总数操作符

any —— 判断集合中 是否有满足条件 的元素;

all —— 判断集合中的元素 是否都满足条件;

none —— 判断集合中是否 都不满足条件,是则返回true;

count —— 查询集合中 满足条件 的 元素个数;

reduce —— 从 第一项到最后一项进行累计 ;

reduceRight —— 从 最后一下到第一项进行累计;

fold —— 与reduce类似,不过有初始值,而不是从0开始累计;

foldRight —— 和reduceRight类似,有初始值,不是从0开始累计;

forEach —— 循环遍历元素,元素是it,可对每个元素进行相关操作;

forEachIndexed —— 循环遍历元素,同时得到元素index(下标);

max —— 查询最大的元素,如果没有则返回null;

maxBy —— 获取方法处理后返回结果最大值对应的那个元素的初始值,如果没有则返回null;

min —— 查询最小的元素,如果没有则返回null;

minBy —— 获取方法处理后返回结果最小值对应那个元素的初始值,如果没有则返回null;

sumBy —— 获取 方法处理后返回结果值 的 总和;

dropWhile —— 返回从第一项起,去掉满足条件的元素,直到不满足条件的一项为止

过滤操作符

过滤后会返回一个处理后的列表结果,但不会改变原列表!!!

filter —— 过滤 掉所有 满足条件 的元素

filterNot —— 过滤所有不满足条件的元素

filterNotNull —— 过滤NULL

take —— 返回从第一个开始的n个元素

takeLast —— 返回从最后一个开始的n个元素

takeWhile —— 返回不满足条件的下标前面的所有元素的集合

drop —— 返回 去掉前N个元素后 的列表

dropLastWhile —— 返回从最后一项起,去掉满足条件的元素,直到不满足条件的一项为止

slice —— 过滤掉 非指定下标 的元素,即保留下标对应的元素过滤list中

指定下标的元素(比如这里只保留下标为1,3,4的元素)

映射操作符

map —— 将集合中的元素通过某个 方法转换 后的结果存到一个集合中;

mapIndexed —— 除了得到 转换后的结果 ,还可以拿到Index(下标);

mapNotNull —— 执行方法 转换前过滤掉 为 NULL 的元素

flatMap —— 合并两个集合,可以在合并的时候做些小动作;

groupBy —— 将集合中的元素按照某个条件分组,返回Map;

顺序操作符

reversed —— 相反顺序

sorted —— 自然排序(升序)

sortedBy —— 根据方法处理结果进行自然(升序)排序

sortedDescending —— 降序排序

sortedByDescending —— 根据方法处理结果进行降序排序

生产操作符

zip —— 两个集合按照下标组合成一个个的Pair塞到集合中返回

partition —— 根据判断条件是否成立,拆分成两个 Pair

plus —— 合并两个List,可以用"+"替代

unzip —— 将包含多个Pair的List 转换成 含List的Pair

元素操作符

contains —— 判断集合中是否有指定元素,有返回true

elementAt —— 查找下标对应的元素,如果下标越界会抛IndexOutOfBoundsException

elementAtOrElse —— 查找下标对应元素,如果越界会根据方法返回默认值(最大下标经方法后的值)

elementAtOrNull —— 查找下标对应元素,越界会返回Null

first —— 返回符合条件的第一个元素,没有 抛NoSuchElementException

firstOrNull —— 返回符合条件的第一个元素,没有 返回null

indexOf —— 返回指定下标的元素,没有 返回-1

indexOfFirst —— 返回第一个符合条件的元素下标,没有 返回-1

indexOfLast —— 返回最后一个符合条件的元素下标,没有 返回-1

last —— 返回符合条件的最后一个元素,没有 抛NoSuchElementException

lastIndexOf —— 返回符合条件的最后一个元素,没有 返回-1

lastOrNull —— 返回符合条件的最后一个元素,没有 返回null

single —— 返回符合条件的单个元素,如有没有符合或超过一个,抛异常

singleOrNull —— 返回符合条件的单个元素,如有没有符合或超过一个,返回null

188.Kotlin 中集合遍历有哪几种方式?

for,foreach,while,do while,递归,还有集合的高阶方法

189 说一下Kotlin的伴生对象(关键字companion)

在Java中可以通过static关键字声明静态的属性或方法。但是在Kotlin中并没有延续这个关键字,而是使用伴生对象实现,在class内部声明一个companion object代码块,其内部的成员变量和方法都将被编译为静态的。

class TestStatic {

    //伴生对象

    companion object Factory {

        val str: String = ""

        fun create(): TestStatic {

            println(this)

            return TestStatic()

        }

    }

}

Factory为最终生成的静态内部类类名,通常来说Factory名字可以省略,如果省略,类名为默认的Companion。

Kotlin中的也可写静态代码块,只需在companion object中嵌套一个init代码块。

companion object {

    //静态代码块

    init {

        val a = "adc"

    }

}

注意事项

目录

1.理解线程间通信

2.工作者线程(workerThread)与主线程(UI线程)的理解

3.通过Handler在线程间通信的原理

4.子线程发消息到主线程进行更新 UI,除了 handler 和 AsyncTask,还有什么?

5.子线程中能不能 new handler?为什么?

6. Handler、 Thread 和 HandlerThread 的差别

7.当Activity有多个Handler的时候,Message消息是否会混乱?怎么样区分当前消息由哪个Handler处理?

7.0.线程更新UI导致崩溃的原因?

7.1.ANR应用无响应

8.AsyncTask(异步任务)的工作原理

9.AsyncTask使用在哪些场景?它的缺陷是什么?如何解决?

10.Android中动画的类型:

11.理解Activity、View、Window三者之间的关系

11.0. Activity、Dialog、PopupWindow、Toast 与Window的关系

12.Android中Context详解:

13讲解一下Context

14.Android常用的数据存储方式(4种)

15.SharedPreference跨进程使用会怎么样?如何保证跨进程使用安全?

16. 数据库的操作类型有哪些,如何导入外部数据库?

16.1 .SQLite支持事务吗? 添加删除如何提高性能?

18.Android垃圾回收机制和程序优化System.gc( )

18.1 为什么图片需要用软引用,MVP模式中的view接口用弱引用

19.Android平台的优势和不足

20.0 Android中任务栈的分配

20. Activity组件生命周期、四种启动模式

21. Activity的启动过程(不要回答生命周期)

22.保存Activity状态

23.如何修改 Activity 进入和退出动画

24.Service组件

25.什么是 IntentService?有何优点?

26 是否使用过 IntentService,作用是什么, AIDL 解决了什么问题?

27.BoradcastReceiver组件

28.配置文件静态注册和在代码中动态注册两种方式的区别

29. ContentProvider(内容提供者)组件

30. Fragment

31.Fragment中add与replace的区别?

32.FragmentPagerAdapter 与 与 FragmentStatePagerAdapter 的区别与使用场景?

33.Activity静态添加Fragment

34. Activity动态加载Fragment

35.Intent

36.ViewPager

37关于Fragment中的控件的事件的监听

38. 使用View绘制视图

39.View的绘制流程

40.View,ViewGroup事件分发

41. Android的事件传递(分发)机制

42.0.Android中touch事件的传递机制是怎样的?

42.View的分发机制,滑动冲突

43.Android中跨进程通讯的几种方式

65.Android 线程间通信有哪几种方式(重要)

66.AIDL理解

67.AIDL 的全称是什么?如何工作?能处理哪些类型的数据?

68.什么是 AIDL?如何使用?

69. Android中页面的横屏与竖屏操作

70.横竖屏切换的Activity 生命周期变化?

71. 获取手机中屏幕的宽和高的方法

72.内存泄漏的相关原因

74. Android内存泄漏及管理

76. Android平台的虚拟机Dalvik

78. Android中的Binder机制

79. Android中的缓存机制

80. Android 中图片的三级缓存策略

81. Glide三级缓存

84.HybridApp WebView和JS交互

87.RecyclerView和ListView的区别

88.简述一下RecyclerView缓存机制?

89.recyclerView嵌套卡顿解决如何解决

90.Universal-ImageLoader,Picasso,Fresco,Glide对比

91.Xutils, OKhttp, Volley, Retrofit对比

92.请解释下 Android 程序运行时权限与文件系统权限的区别?

93.Framework 工作方式及原理,Activity 是如何生成一个 view 的,机制是什么?

94.Android 判断SD卡是否存在

95.Android与服务器交互的方式中的对称加密和非对称加密是什么?

96. 音视频相关类

97.SurfaceView和GLSurfaceView

99.说说JobScheduler

100.说说WorkManager

101.谈一谈startService和bindService的区别,生命周期以及使用场景?

102.Service如何进行保活?

103. 进程保活(不死进程)

104.热修复的原理

105.JNI

105.0谈谈对Android NDK的理解

106.Foundation(基础组件)

107.Architecture(架构组件)(非常重要)

108.Behavior(行为组件)

109.UI(界面组件)

110. Android设计模式之MVC

111 安卓mvc/mvp/mvvm

112.设计模式的六大原则

113.Android中的性能优化相关问题

114. Bitmap的使用及内存优化

115. app优化

116.性能优化(非常重要)

117. Android对HashMap做了优化后推出的新的容器类是什么?

118.谈谈你对安卓签名的理解

119.请解释安卓为啥要加签名机制?

120.权限管理系统(底层的权限是如何进行 grant 的)?

121. Kotlin 如何在 Android 上运行?

122. 为什么要使用 Kotlin?

123. 用var和val声明变量有什么区别?

124. 用val和const声明变量有什么区别?

125. Kotlin 中如何保证 null 安全?

126.安全调用(?.)和空值检查(!!)有什么区别?

127. Kotlin 中是否有像 java 一样的三元运算符?

128. Kotlin 中的 Elvis 运算符是什么?

129. 如何将 Kotlin 源文件转换为 Java 源文件?

130.你觉得Kotlin与Java混合开发时需要注意哪些问题?

131.@JvmStatic、@JvmOverloads、@JvmFiled 在 Kotlin 中有什么用?

132. Kotlin 中的数据类是什么?

133.Kotlin中的数据类型有隐式转换吗?为什么?

134.Kotlin中可以使用int、double、float等原始类型吗?

135. Kotlin 中的字符串插值是什么?

136. Kotlin 中的解构是什么意思?

137.在Kotlin中,何为解构?该如何使用?

139. 如何检查一个lateinit变量是否已经初始化?

140. Kotlin 中的 lateinit 和 lazy 有什么区别?

141.==操作符和===操作符有什么区别?

142. Kotlin 中的 forEach 是什么?

143. Kotlin 中的伴生对象是什么?

144.kotlin中Unit的应用以及和Java中void的区别?

155. Kotlin 中的 Java 静态方法等价物是什么?

156. Kotlin 中的 FlatMap 和 Map 有什么区别?

157. Kotlin中可以使用new关键字实例化一个类对象吗?

158. Kotlin 中的可见性修饰符是什么?

159. Kotlin中的可见性修饰符有哪些?相比于 Java 有什么区别?

160. 如何在 Kotlin 中创建 Singleton 类?

161. Kotlin 中的初始化块是什么?

162. Kotlin 中的构造函数有哪些类型?

163.主构造函数和次构造函数之间有什么关系吗?

164.构造函数中使用的默认参数类型是什么?

165.谈谈kotlin中的构造方法?有哪些注意事项?

166.Kotlin 中的扩展函数是什么

167.kotlin基础: From Java To Kotlin

167.常量与变量

168.null 声明

169.空判断

170.字符串拼接

180.换行

181.三元表达式

182.操作符

183. Kotlin 中什么时候使用 lateinit 关键字?

184.Kotlin 的延迟初始化: lateinit var 和 by lazy

185 Kotlin Tips:怎么用 Kotlin 去提高生产力(kotlin优势)

186. Kotlin数组和集合

187.Kotlin中的MutableList与List有什么区别?

5.5 Kotlin集合操作符

188.Kotlin 中集合遍历有哪几种方式?

189 说一下Kotlin的伴生对象(关键字companion)

190.Kotlin 顶层函数和属性

191. Kotlin 中的协程是什么?

192. Kotlin Coroutines 中的挂起函数是什么?

193. Kotlin Coroutines 中 Launch 和 Async 有什么区别?

194. Kotlin Coroutines 中的作用域是什么?

195. Kotlin Coroutines 中的异常处理是如何完成的?

196. 在 Kotlin 中如何在 switch 和 when 之间进行选择?

197. Kotlin 中的 open 关键字是做什么用的?

198. 什么是 lambdas 表达式?

199. Kotlin 中的高阶函数是什么?

200. Kotlin 中的扩展函数是什么?

201. Kotlin 中的中缀函数是什么?

202. Kotlin 中的内联函数是什么?

203. Kotlin 中的 noinline 是什么?

204. Kotlin 中的具体化类型是什么?

205. Kotlin 中的运算符重载是什么?

206. 解释在 Kotlin 中 let、run、with 和 apply 的用例。

207.kotlin中with、run、apply、let函数的区别?一般用于什么场景?

208. Kotlin 中的 pair 和 Triple 是什么?

209. Kotlin 中的标签是什么?

210. 使用密封类而不是枚举有什么好处?

211 协程是什么

212 kotlin中关键字data的理解?相对于普通的类有哪些特点?

213.谈谈Kotlin中的Sequence,为什么它处理集合操作更加高效?

214.说说 Kotlin中 的 Any 与Java中的 Object 有何异同?


一个类中最多只能有一个companion object代码块。

伴生对象本质上就是一个静态内部类,所以它还能继承其他类。

190.Kotlin 顶层函数和属性

创建一个文件写需要的属性或方法。在其它地方直接,import 包名.函数名来导入我们将要使用的函数,然后就可以直接使用了。Kotlin中通过使用顶层函数和顶层属性帮助我们消除了Java中常见的静态工具类,使我们的代码更加整洁,值得一试。

Android Jetpack 组件是库的集合,这些库是为协同工作而构建的,不过也可以单独采用。

包含:

191. Kotlin 中的协程是什么?

一个以更高效和更简单的方式管理并发的框架,其轻量级线程编写在实际线程框架之上,通过利用函数的协作特性来充分利用它。

同步的方式做异步事件

这是一个重要的面试问题。

Kotlin协程是个什么东西? - 掘金

192. Kotlin Coroutines 中的挂起函数是什么?

挂起函数是 Kotlin 中协程的构建块。挂起函数是一个可以启动、暂停和恢复的函数。要使用挂起函数,我们需要在普通函数定义中使用挂起关键字。

193. Kotlin Coroutines 中 Launch 和 Async 有什么区别?

不同之处在于launch{}不返回任何内容,而是async{}返回 的实例Deferred<T>,该实例具有await()返回协程结果的函数,就像我们在 Java 中的 future 中future.get()获取结果一样。

换一种说法:

发射:一劳永逸

async:执行任务并返回结果

194. Kotlin Coroutines 中的作用域是什么?

详情查看链接:https://juejin.cn/post/7020343850522640414

195. Kotlin Coroutines 中的异常处理是如何完成的?

详情查看链接:https://johnnyshieh.me/posts/kotlin-coroutine-exception-handling/

196. 在 Kotlin 中如何在 switch 和 when 之间进行选择?

每当我们想要处理许多 if-else 条件时,我们通常会使用 switch-case 语句。但是 Kotlin 提供了一个更简洁的选项,即在 Kotlin 中,我们可以使用 when 来代替 switch。并且,何时可以用作:

表达

任意条件表达式

没有争论

有两个或多个选择

例如:

when(number) {

1 -> println("One")

2, 3 -> println("Two or Three")

4 -> println("Four")

else -> println("Number is not between 1 and 4")

}

197. Kotlin 中的 open 关键字是做什么用的?

默认情况下,类和函数在 Kotlin 中是最终的。因此,您不能继承类或覆盖函数。为此,您需要在类和函数之前使用 open 关键字。例如

198. 什么是 lambdas 表达式?

Lambdas 表达式是可以被视为值的匿名函数,即我们可以将 lambdas 表达式作为参数传递给函数返回它们,或者做任何其他我们可以对普通对象做的事情。例如:

val add : (Int, Int) -> Int = { a, b -> a + b }

val result = add(9, 10)

199. Kotlin 中的高阶函数是什么?

高阶函数是将函数作为参数或返回函数的函数。例如,函数可以将函数作为参数。

fun passMeFunction(abc: () -> Unit) {

// I can take function

// do something here

// execute the function

 abc()

}

例如,一个函数可以返回另一个函数。

fun add(a: Int, b: Int): Int {

return a + b

}

而且,我们有一个函数returnMeAddFunction,它接受零个参数并返回一个((Int, Int) -> Int)类型的函数。

fun returnMeAddFunction(): ((Int, Int) -> Int) {

// can do something and return function as well

// returning function

return ::add

}

而要调用上述函数,我们可以这样做:

val add = returnMeAddFunction()

val result = add(2, 2)

200. Kotlin 中的扩展函数是什么?

扩展函数就像附加到 Kotlin 中的任何类的扩展属性。通过使用扩展函数,即使不继承该类,您也可以向现有类添加一些方法或功能。例如:假设我们有视图,我们需要在其中使用视图的可见性。因此,我们可以为视图创建一个扩展函数,例如,

fun View.show() {

this.visibility = View.VISIBLE

}

fun View.hide() {

this.visibility = View.GONE

}

为了使用它,我们使用,比如,

toolbar.hide()

201. Kotlin 中的中缀函数是什么?

中缀函数用于在不使用任何括号或括号的情况下调用该函数。您需要使用中缀关键字才能使用中缀功能。

class Operations {

var x = 10;

infix fun minus(num: Int) {

this.x = this.x - num

 }

}

fun main() {

val opr = Operations()

 opr minus 8

 print(opr.x)

}

202. Kotlin 中的内联函数是什么?

内联函数指示编译器在代码中使用该函数的任何位置插入完整的函数体。要使用 Inline 函数,您只需在函数声明的开头添加一个 inline 关键字即可。

203. Kotlin 中的 noinline 是什么?

在使用内联函数并希望传递一些 lambda 函数而不是所有 lambda 函数作为内联函数时,您可以明确告诉编译器它不应该内联哪个 lambda。

inline fun doSomethingElse(abc: () -> Unit, noinline xyz: () -> Unit) {

 abc()

 xyz()

}

204. Kotlin 中的具体化类型是什么?

当您使用泛型的概念将某个类作为参数传递给某个函数并且您需要访问该类的类型时,您需要使用 Kotlin 中的 reified 关键字。

例如:

inline fun <reified T> genericsExample(value: T) {

 println(value)

 println("Type of T: ${T::class.java}")

}

fun main() {

 genericsExample<String>("Learning Generics!")

 genericsExample<Int>(100)

}

205. Kotlin 中的运算符重载是什么?

在 Kotlin 中,我们可以使用相同的运算符来执行各种任务,这称为运算符重载。为此,我们需要在函数名称之前提供具有固定名称和操作符关键字的成员函数或扩展函数,因为通常情况下,当我们使用某些操作符时,会在内部调用某些函数。例如,如果您正在编写num1+num2,那么它将转换为

num1.plus(num2).

例如:

fun main() {

val bluePen = Pen(inkColor = "Blue")

 bluePen.showInkColor()

val blackPen = Pen(inkColor = "Black")

 blackPen.showInkColor()

val blueBlackPen = bluePen + blackPen

 blueBlackPen.showInkColor()

}

operator fun Pen.plus(otherPen: Pen):Pen{

val ink = "$inkColor, ${otherPen.inkColor}"

return Pen(inkColor = ink)

 }

data class Pen(val inkColor:String){

fun showInkColor(){ println(inkColor)}

 }

206. 解释在 Kotlin 中 let、run、with 和 apply 的用例。

207.kotlin中with、run、apply、let函数的区别?一般用于什么场景?

基本介绍:

with:不是T的扩展函数,需要传入对象进去,不能判空,最后一行是返回值。

run:是T的扩展函数,内部使用this,最后一行是返回值。

apply:是T的扩展函数,内部使用this,最后一行返回的是自身。

let:是T的扩展函数,内部使用it,当然可以自定义名称(通过修改lambda表达式参数),最后一行是返回值。

also:是T的扩展函数,和let一样内部使用it,最后一行是返回自身。

使用场景:

用于初始化对象或更改对象属性,可使用apply

如果将数据指派给接收对象的属性之前验证对象,可使用also

如果将对象进行空检查并访问或修改其属性,可使用let

如果想要计算某个值,或者限制多个本地变量的范围,则使用run

208. Kotlin 中的 pair 和 Triple 是什么?

Pair 和 Triples 用于分别从函数返回两个和三个值,返回的值可以是相同的数据类型或不同的数据类型。

val pair = Pair("My Age: ", 25)

print(pair.first + pair.second)

209. Kotlin 中的标签是什么?

用 Kotlin 编写的任何表达式都称为标签。例如,如果我们在 Kotlin 代码中有一个for-loop,那么我们可以将该for-loop表达式命名为标签,并将标签名称用于for-loop。

我们可以通过使用标识符后跟@符号来创建标签。例如,name@、loop@、xyz@等。以下是标签的示例:

loop@ for (i in 1..10) {

// some code goes here

}

上述 for 循环的名称是loop.

210. 使用密封类而不是枚举有什么好处?

密封类为我们提供了拥有不同 类型的子类并包含状态的灵活性。这里需要注意的重点是扩展 Sealed 类的子类应该是 Sealed 类的嵌套类,或者应该在与 Sealed 类相同的文件中声明。

50. Kotlin 中的集合是什么?

详情查看链接:https://juejin.cn/post/6844903702403235853

为您的面试准备这 50 个问题,并通过每个问题中的给定链接来更好地了解该主题

211 协程是什么

协程(Coroutines=cooperation+routines)

Kotlin 官方文档说「本质上,协程是轻量级的线程」。

进程,线程,协程的抽象概念

进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。进程是一种抽象的概念,从来没有统一的标准定义。

线程是操作系统能够进行调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。线程是在进程下,所以同一进程下的多个线程是能共享资源的。

协程是单线程下实现多任务,它通过 yield 关键字来实现,能有效地减少多线程之间切换的开销。它是一种比线程更加轻量级的存在。正如一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程。

211.1协程基本使用

// 方法一,使用 runBlocking 顶层函数

// 通常适用于单元测试的场景,而业务开发中不会用到这种方法

runBlocking {

         getImage(imageId)

}

// 方法二,使用 GlobalScope 单例对象

//和使用 runBlocking 的区别在于不会阻塞线程。但在 Android 开发中同样不推荐这种用法,因为它的生命周期会和app一致,且不能取消

GlobalScope.launch {

         getImage(imageId)

}

// 方法三,自行通过 CoroutineContext 创建一个 CoroutineScope 对象

//推荐的使用方法,我们可以通过 context 参数去管理和控制协程的生命周期(这里的context和Android里的不是一个东西,是一个更通用的概念,会有一个Android平台的封装来配合使用)。

val coroutineScope = CoroutineScope(context)

coroutineScope.launch {

         getImage(imageId)

}

协程最常用的功能是并发,而并发的典型场景就是多线程。可以使用 Dispatchers.IO 参数把任务切到 IO 线程执行:

coroutineScope.launch(Dispatchers.IO) {

 ...

}

也可以使用 Dispatchers.Main 参数切换到主线程:

coroutineScope.launch(Dispatchers.Main) {

 ...

}

异步请求的例子完整写出来是这样的:

coroutineScope.launch(Dispatchers.Main) { // 在主线程开启协程

         val user = api.getUser() // IO 线程执行网络请求

         nameTv.text = user.name // 主线程更新 UI

}

而通常用java来写,是少不了回调方法的。

协程的「1 到 0」

多层网络请求:

coroutineScope.launch(Dispatchers.Main) { // 开始协程:主线程

         val token = api.getToken() // 网络请求:IO 线程

         val user = api.getUser(token) // 网络请求:IO 线程

         nameTv.text = user.name // 更新 UI:主线程

}

如果遇到的场景是多个网络请求需要等待所有请求结束之后再对 UI 进行更新。

比如以下两个请求:

api.getAvatar(user, callback)

api.getCompanyLogo(user, callback)

如果使用回调式的写法,本来能够并行处理的请求被强制通过串行的方式去实现,可能会导致等待时间长了一倍,也就是性能差了一倍:

api.getAvatar(user) { avatar ->

                 api.getCompanyLogo(user) { logo ->

                 show(merge(avatar, logo))

        }

}

而如果使用协程,可以直接把两个并行请求写成上下两行,最后再把结果进行合

并即可:

coroutineScope.launch(Dispatchers.Main) {

         val avatar = async { api.getAvatar(user) } // 获取用户头像

         val logo = async { api.getCompanyLogo(user) } // 获取用户所在公司的 logo

         val merged = suspendingMerge(avatar, logo) // 合并结果

         show(merged) // 更新 UI

}

可以看到,即便是比较复杂的并行网络请求,也能够通过协程写出结构清晰的代码。需要注意的是 suspendingMerge 并不是协程 API 中提供的方法,而是我们自定义的一个可「挂起」的结果合并方法。至于挂起具体是什么,可以看后面。

让复杂的并发代码,写起来变得简单且清晰,是协程的优势。

这里,两个没有相关性的后台任务,因为用了协程,被安排得明明白白,互相之间配合得很好,也就是我们之前说的「协作式任务」。本来需要回调,现在直接没有回调了,这种从 1 到 0 的设计思想真的妙哉。

211.2 suspend

suspend 是 Kotlin 协程最核心的关键字。代码执行到 suspend 函数的时候会『挂起』,并且这个『挂起』是非阻塞式的,它不会阻塞你当前的线程。

创建协程的函数:

• launch

• runBlocking

• async

runBlocking 通常适用于单元测试的场景,而业务开发中不会用到这个函数,因为它是线程阻塞的。

接下来我们主要来对比 launch 与 async 这两个函数。

• 相同点:它们都可以用来启动一个协程,返回的都是 Coroutine,我们这里不需要纠结具体是返回哪个类。

• 不同点:async 返回的 Coroutine 多实现了 Deferred 接口。

Deferred的意思就是延迟,也就是结果稍后才能拿到。

我们调用 Deferred.await() 就可以得到结果了。

看看 async 是如何使用的:

coroutineScope.launch(Dispatchers.Main) {

         val avatar: Deferred = async { api.getAvatar(user) } // 获取用户头像

         val logo: Deferred = async { api.getCompanyLogo(user) } // 获取用户所在公司的 logo

         show(avatar.await(), logo.await()) // 更新 UI

 }

可以看到 avatar 和 logo 的类型可以声明为 Deferred ,通过 await 获取结果并且更新到 UI 上显示。

await 函数签名:

public suspend fun await(): T

前面有个关键字是—— suspend

211.3 「挂起」的本质

协程中「挂起」的对象到底是什么?挂起线程,还是挂起函数?

都不对,我们挂起的对象是协程

协程可以使用 launch 或者 async 函数,协程其实就是这两个函数中闭包的代码块。launch ,async 或者其他函数创建的协程,在执行到某一个 suspend 函数的时候,这个协程会被「suspend」,也就是被挂起。

那此时又是从哪里挂起?从当前线程挂起。换句话说,就是这个协程从正在执行它的线程上脱离。

注意,不是这个协程停下来了!是脱离,当前线程不再管这个协程要去做什么了。

suspend 是有暂停的意思,但我们在协程中应该理解为:当线程执行到协程的suspend 函数的时候,暂时不继续执行协程代码了。

互相脱离的线程和协程接下来将会发生什么事情:

举例:获取一个图片,然后显示出来:

// 主线程中

GlobalScope.launch(Dispatchers.Main) {

         val image = suspendingGetImage(imageId) // 获取图片

         avatarIv.setImageBitmap(image) // 显示出来

}

suspend fun suspendingGetImage(id: String) = withContext(Dispatchers.IO) {

 ...

这段执行在主线程的协程,它实质上会往你的主线程 post 一个 Runnable,这个 Runnable 就是你的协程代码:

handler.post {

         val image = suspendingGetImage(imageId)

         avatarIv.setImageBitmap(image)

}

线程:

如果它是一个后台线程:

• 要么无事可做,被系统回收

• 要么继续执行别的后台任务

跟 Java 线程池里的线程在工作结束之后是完全一样的:回收或者再利用。

如果它是 Android 的主线程,那它接下来就会继续回去工作:也就是

一秒钟 60 次的界面刷新任务。

211.5协程

线程的代码在到达 suspend 函数的时候被掐断,接下来协程会从这个 suspend 函数开始继续往下执行,不过是在指定的线程。

谁指定的?是 suspend 函数指定的,比如我们这个例子中,函数内部的 withContext 传入的 Dispatchers.IO 所指定的 IO 线程。

Dispatchers:调度器,它可以将协程限制在一个特定的线程执行,或者将它分派到一个线程池,或者让它不受限制地运行,关于 Dispatchers 这里先不展开了。

常用的 Dispatchers ,有以下三种:

• Dispatchers.Main:Android 中的主线程

• Dispatchers.IO:针对磁盘和网络 IO 进行了优化,适合 IO 密集型的任务,比如:读写文件,操作数据库以及网络请求

• Dispatchers.Default:适合 CPU 密集型的任务,比如计算

回到我们的协程,它从 suspend 函数开始脱离启动它的线程,继续执行在 Dispatchers 所指定的 IO 线程。在 suspend 函数执行完成之后,协程为我们做的最爽的事就来了:会自动帮我们把线程再切回来。我们的协程原本是运行在主线程的,当代码遇到 suspend 函数的时候,发生线程切换,根据 Dispatchers 切换到了 IO 线程;当这个函数执行完毕后,线程又切了回来,「切回来」也就是协程会帮我再 post 一个 Runnable,让我剩下的代码继续回到主线程去执行。

结论:

协程在执行到有 suspend 标记的函数的时候,会被suspend也就是被挂起,就是切个线程;不过区别在于,挂起函数在执行完成之后,协程会重新切回它原先的线程。再简单来讲,在 Kotlin 中所谓的挂起,就是一个稍后会被自动切回来的线程调度操作。

211.6 suspend 的意义?

随便写一个自定义的 suspend 函数:

suspend fun suspendingPrint() {

         println("Thread: ${Thread.currentThread().name}")

         }

System.out: Thread: main

输出的结果还是在主线程。

为什么没切换线程?因为它不知道往哪切,需要我们告诉它。

对比之前例子中 suspendingGetImage 函数代码:

suspend fun suspendingGetImage(id: String) =

         withContext(Dispatchers.IO) {

        ...

 }

通过 withContext 源码可以知道,它本身就是一个挂起函数,它接收一个 Dispatcher 参数,依赖这个 Dispatcher 参数的指示,你的协程被挂起,然后切到别的线程。所以这个 suspend,其实并不是起到把任何把协程挂起,或者说切换线程的作用。真正挂起协程这件事,是 Kotlin 的协程框架帮我们做的。所以我们想要自己写一个挂起函数,仅仅只加上 suspend 关键字是不行的,还需要函数内部直接或间接地调用到 Kotlin 协程框架自带的 suspend 函数才行。

这个 suspend 关键字,既然它并不是真正实现挂起,那它的作用是什么?

它其实是一个提醒。

函数的创建者对函数的使用者的提醒:我是一个耗时函数,我被我的创建者用挂起的方式放在后台运行,所以请在协程里调用我。为什么 suspend 关键字并没有实际去操作挂起,但 Kotlin 却把它提供出来?因为它本来就不是用来操作挂起的。挂起的操作 —— 也就是切线程,依赖的是挂起函数里面的实际代码,而不是这个关键字。

所以suspend关键字,只是一个提醒。

你创建一个 suspend 函数但它内部不包含真正的挂起逻辑,编译器会给你一个提醒:redundant suspend modifier,告诉你这个 suspend 是多余的。所以,创建一个 suspend 函数,为了让它包含真正挂起的逻辑,要在它内部直接或间接调用 Kotlin 自带的 suspend 函数,你的这个 suspend 才是有意义的。

211.7 到底什么是「非阻塞式」挂起?协程真的更轻量级吗?

什么是「非阻塞式挂起」?

线程中的阻塞式:在单线程情况下,在单线程下执行耗时操作是会阻塞线程的,如果在多线程情况下,那么此时的线程也是非阻塞式的。

非阻塞式是相对阻塞式而言的。 Kotlin 协程在单协程的情况下也是非阻塞式 的,因为它可以利用挂起函数来切换线程。(阻塞不阻塞,都是针对单线程讲的,一旦切了线程,肯定是非阻塞的,你都跑到别的线程了,之前的线程就自由了,可以继续做别的事情了。)即:协程可以用看起来阻塞的代码写出非阻塞式的操作

阻塞的本质?

首先,所有的代码本质上都是阻塞式的,而只有比较耗时的代码才会导致人类可感知的等待,比如在主线程上做一个耗时 50 ms 的操作会导致界面卡掉几帧,这种是我们人眼能观察出来的,而这就是我们通常意义所说的「阻塞」。举个例子,当你开发的 app 在性能好的手机上很流畅,在性能差的老手机上会卡顿,就是在说同一行代码执行的时间不一样。视频中讲了一个网络 IO 的例子,IO 阻塞更多是反映在「等」这件事情上,它的性能瓶颈是和网络的数据交换,你切多少个线程都没用,该花的时间一点都少不了。而这跟协程半毛钱关系没有,切线程解决不了的事情,协程也解决不了。

总结:

• 协程就是切线程;

• 挂起就是可以自动切回来的切线程;

• 挂起的非阻塞式指的是它能用看起来阻塞的代码写出非阻塞的操作,就这么简单。

212 kotlin中关键字data的理解?相对于普通的类有哪些特点?

数据类,相当于MVVM模式下的model类,相对java自动重写了equals()/hashCode()方法、get()方法、set()方法(如果是可写入的)、toString()方法、componentN()方法、copy()方法,注意get/set方法是kotlin中的类都会为属性自动生成的方法,和数据类没关系。

equals/hashCode:equals方法重写使对象的内容一致则返回true,hashCode方法重写使对象的内容一致则hashCode值也一致。

注意:在kotlin中有 == 和 ===,==比较的对象内容,===比较的是对象的引用地址

toString:重写此方法为类和属性值的内容,如:"User(name=John, age=42)"

componentN:编译器为数据类(data class)自动声明componentN()函数,可直接用解构声明,如下:

         var girl1: Girl = Girl("嫚嫚", 29, 160, "廊坊")

         var (a,b,c,d) = girl1

         println("$a,$b,$c,$d")

在kotlin中所谓的解构就是将一个类对象中的参数拆开来,成为一个一个单独的变量,从而来使用这些单独的变量进行操作。

copy: 复制对象使用,当要复制一个对象,只改变一些属性,但其余不变,copy()就是为此而生

213.谈谈Kotlin中的Sequence,为什么它处理集合操作更加高效?

集合操作低效在哪?

处理集合时性能损耗的最大原因是循环。集合元素迭代的次数越少性能越好。

我们写个例子:

list

  .map { it ++ }

  .filter { it % 2 == 0 }

  .count { it < 3 }

反编译一下,你会发现:Kotlin编译器会创建三个while循环。

Sequences 减少了循环次数

Sequences提高性能的秘密在于这三个操作可以共享同一个迭代器(iterator),只需要一次循环即可完成。Sequences允许 map 转换一个元素后,立马将这个元素传递给 filter操作 ,而不是像集合(lists) 那样,等待所有的元素都循环完成了map操作后,用一个新的集合存储起来,然后又遍历循环从新的集合取出元素完成filter操作。

Sequences 是懒惰的

上面的代码示例,map、filter、count 都是属于中间操作,只有等待到一个终端操作,如打印、sum()、average()、first()时才会开始工作,不信?你跑下下面的代码?

val list = listOf(1, 2, 3, 4, 5, 6)

val result = list.asSequence()

        .map{ println("--map"); it * 2 }

        .filter { println("--filter");it % 3  == 0 }

println("go~")

println(result.average())

扩展:Java8 的 Stream(流) 怎么样呢?

list.asSequence()

    .filter { it < 0}

    .map { it++ }

    .average()

list.stream()

    .filter { it < 0}

    .map { it++ }

    .average()

stream的处理效率几乎和Sequences 一样高。它们也都是基于惰性求值的原理并且在最后(终端)处理集合。

214.说说 Kotlin中 的 Any 与Java中的 Object 有何异同?

同:

都是顶级父类

异:

成员方法不同

Any只声明了toString()、hashCode()和equals()作为成员方法。

我们思考下,为什么 Kotlin 设计了一个 Any ?

当我们需要和 Java 互操作的时候,Kotlin 把 Java 方法参数和返回类型中用到的 Object 类型看作 Any,这个 Any 的设计是 Kotlin 兼容 Java 时的一种权衡设计。

所有 Java 引用类型在 Kotlin 中都表现为平台类型。当在 Kotlin 中处理平台类型的值的时候,它既可以被当做可空类型来处理,也可以被当做非空类型来操作。

试想下,如果所有来自 Java 的值都被看成非空,那么就容易写出比较危险的代码。反之,如果 Java 值都强制当做可空,则会导致大量的 null 检查。综合考量,平台类型是一种折中的设计方案。

  • 0
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值