前言
转眼间就九月底了,秋招旅程即将结束(虽然还有拼多多的面试但是我已经无法抑制内心想要放假的心情了哈哈哈哈哈哈哈哈哈)作为一个半路出家,专注于安卓开发的菜狗,秋招拿下了10个offer(美团,快手,网易云音乐···),虽然拿不下BAT的offer(编程题渣渣默默流泪),但也还能接受。毕竟也参加了这么多场面试,就把我在面试过程中碰到的一些安卓开发的问题做个总结吧
1.android的handler机制
Android中主线程是不能进行耗时操作的,子线程是不能进行更新UI的。所以就有了handler, 它的作用就是实现线程之间的通信。handler 整个流程中,主要有四个对象,handler,Message,MessageQueue,Looper。当应用创建的时候,就会在主线程中创建 handler 对象,我们通过要传送的消息保存到 Message 中,handler 通过调用 sendMessage 方法将 Message 发送到 MessageQueue 中,Looper 对象就会不断的调用 loop()方法不断的从 MessageQueue 中取出 Message 交给 handler 进行处理。从而实现线程之间的通信。
2.主线程的handler是怎么判断收到的消息是哪个handler传来的
handler在sendMessage的时候会构建一个Message对象,并且把自己放在Message的target里面,这样的话Looper就可以根据Message中的target来判断当前的消息是哪个handler传来的。
3.Activity四种启动模式
standard 模式
这是默认模式,每次激活 Activity 时都会创建 Activity 实例,并放入任务栈中。使用场景: 大多数 Activity。
singleTop 模式
如 果 在 任 务 的 栈 顶 正 好 存 在 该 Activity 的 实 例 , 就 重 用 该 实 例( 会 调 用 实 例 的 onNewIntent()),否则就会创建新的实例并放入栈顶,即使栈中已经存在该 Activity 的实例, 只要不在栈顶,都会创建新的实例。使用场景如新闻类或者阅读类 App 的内容页面。
singleTask 模式
如果在栈中已经有该 Activity 的实例,就重用该实例(会调用实例的 onNewIntent())。重用时, 会让该实例回到栈顶,因此在它上面的实例将会被移出栈。如果栈中不存在该实例,将会创 建新的实例放入栈中。使用场景如浏览器的主界面。不管从多少个应用启动浏览器,只会启动主界面一次,其余情况都会走 onNewIntent,并且会清空主界面上面的其他页面。
singleInstance 模式
在一个新栈中创建该 Activity 的实例,并让多个应用共享该栈中的该 Activity 实例。一旦该模式的 Activity 实例已经存在于某个栈中,任何应用再激活该 Activity 时都会重用该栈中的 实例( 会调用实例的 onNewIntent())。其效果相当于多个应用共享一个应用,不管谁激活该 Activity 都会进入同一个应用中。使用场景如闹铃提醒,将闹铃提醒与闹铃设置分离。 singleInstance 不要用于中间页面,如果用于中间页面,跳转会有问题,比如:A -> B (singleInstance)->C,完全退出后,在此启动,首先打开的是 B。
4.Service和Activity的区别
(1)从设计的角度来讲,Activity的设计与Web页面非常类似,它主要负责与用户进行交互。Service则是在后台运行,默默地为用户提供功能,进行调度和统筹,如果一棵树的地上部分是Activity的话,它庞大的根须就Service。
(2)从使用的角度来讲:Service不仅可以给Activity建立双向连接,为Activity提供数据和功能支持,也可以单向接受Intent的请求,进行数据的分析处理和功能调度。
(3)从扮演的角色来讲:Activity的功能比较单一,主要就是显示应用所具有的一些功能,帮助用户与应用进行交互,像一个人的脸。而Service可能扮演功能调度者也能扮演功能提供者
5.结束Activity的方法
(1)finish():结束当前Activity,不会立即释放内存。遵循android内存管理机制。
(2)exit():结束当前组件如Activity,并立即释放当前Activity所占资源。
(3)killProcess():结束当前组件如Activity,并立即释放当前Activity所占资源。
(4)restartPackage():结束整个App,包括service等其它Activity组件。
6.Android中apk文件的结构
(1)Manifest文件
AndroidManifest.xml是每个应用都必须定义和包含的,它描述了应用的名字、版本、权限、引用的库文件等信息
(2)META-INF目录
META-INF目录下存放的是签名信息,用来保证apk包的完整性和系统的安全。
(3)classes.dex文件
在Android系统中,dex文件是可以直接在Dalvik虚拟机中加载运行的文件。
(4)res目录
res目录存放资源文件
(5)resources.arsc
编译后的二进制资源文件
7.Android源码编译过程
8.Handler,Thread,HandlerThread区别
(1)Handler:在android中负责发送和处理消息,通过它可以实现其他支线线程与主线程之间的消息通讯。
(2)Thread:Java进程中执行运算的最小单位,亦即执行处理机调度的基本单位。某一进程中一路单独运行的程序。
(3)HandlerThread:一个继承自Thread的类,然后在内部直接实现了Looper
9.sendMessage和post(Runnable)的区别
(1)post和sendMessage本质上是没有区别的,只是实际用法中有一点差别
(2)post也没有独特的作用,post本质上还是用sendMessage实现的,post只是一中更方便的用法而已
10.SharedPreferences是否支持并且可以用作进程间通讯?
理论上是可以的。在使用SharedPreferences的时候,大家都可以看到它有几个读写的标志位,比如 PRIVATE、READABLE、WRITEABLE等。
在使用SharedPreferences进行进程数据共享时,我们发现,有些虽然过时了,但是实际上还是可以用的。只是Google不建议大家这么干了。
11.okhttp3中的设计模式
(1)Builder。如OkHttpClient、Request、Response、MultipartBody、HttpUrl
(2)Factory Method
(3)Observer。观察者有两个,一个是EventListener,另一个是WebSocketListener
(4)Singleton
(5)Strategy。如CookieJar
(6)Chain of Responsibility。okhttp中最核心的部分当属它的**HTTP请求拦截器(Interceptor)**了,这里是一个责任链模式。
12.权限类别
从Android 6.0(API23)开始,当app运行时用户授予用户的权限,而不是在安装程序的时候。
系统权限分为2种,分别为normal和dangerous.
Normal permission:对于用户隐私没有危险的,在清单文件中申请就可以直接授权。
Dangerous permission:app需要访问用户的隐私信息等,即使在清单文件注册,也需要在运行是通过用户授权。这一块的权限才需要动态获取
13.Fragment的生命周期
14.View的绘制流程
View的工作流程主要是指measure、layout、draw这三大流程,即测量、布局和绘制
measure确定View的测量宽/高
layout确定View的最终宽/高和四个顶点的位置
draw 则将View绘制到屏幕上
15.Android进程间通信方式
16.使Service变成前台Service的步骤
(1)在AndroidManifest.xml文件中进行权限声明
(2)在Service内部生成Notification对象
(3)调用startForeground()方法
17.广播动态注册和静态注册的步骤
广播动态注册:
(1)创建BroadcastReceiver实例
(2)创建Intentfilter
(3)调用registerReceiver(BroadcastReceiver,Intentfilter)方法,启动广播接收器
(4)调用unregisterReceiver()方法,结束调用
广播静态注册:
(1)创建BroadcastReceiver实例
(2)在Androidmanifest.xml中添加intent-filter
18.Activity调用Service方法
(1)调用bindService()方法
(2)绑定后调用ServiceConnection的onServiceConnected()方法,该方法里面可调用Service的方法
19.Service生命周期
20.IntentService中的onHandleIntent()方法在哪里运行?
IntentService中的onHandleIntent()方法是在子线程中运行的,可以处理耗时逻辑
21.OkHttp使用步骤
(1)创建OkHttpClient实例
(2)创建一个request对象
(3)调用OkHttpClient实例的newCall()方法创建一个Call对象,调用它的execute()方法来发送请求并获取服务器返回的数据
val response=client.newCall(request).execute()
22.HttpURLConnection使用步骤
(1)获取HttpURLConnection的实例
(2)设置HTTP请求所使用的方法。常用的方法主要有两个:GET和POST
(3)调用getInputStream()方法可以获取到服务器返回的输入流,对输入流进行读取
(4)调用disconnect()方法关闭HTTP连接
23.Retrofit使用步骤
(1)使用Retrofit.Builder()创建一个Retrofit对象,声明使用的根URL和JSON解析工具
(2)调用Retrofit对象的create()方法,传入具体Service接口,创建该接口的动态代理对象
(3)重写动态代理对象的getAppData().enqueue()方法
24.动态添加Fragment的步骤
(1)创建待添加Fragment的实例。
(2)获取FragmentManager,在Activity中可以直接调用getSupportFragmentManager()方法获取。
(3)开启一个事务,通过调用beginTransaction()方法开启。
(4)向容器内添加或替换Fragment,一般使用事务的replace()方法实现,需要传入容器的id和待添加的Fragment实例。
(5)提交事务,调用commit()方法来完成
25.JSON优缺点
比起XML,JSON的主要优势在于它的体积更小,在网络上传输的时候更省流量。但缺点在于,它的语义性比较差,看起来不如XML直观
26.Fragment与Activity交互,Fragment与Fragment交互
(1)Actvity->Fragment:调用FragmentManager的findFragmentById()方法,可以在Activity中得到相应Fragment的实例,然后调用该Fragment的方法
(2)Fragment->Activity:Fragment可以调用getActivity()方法来得到和当前Fragment相关联的Activity实例
(3)Fragment->Fragment:在一个Fragment中可以得到与它相关联的Activity,然后再通过这个Activity去获取另外一个Fragment的实例
27.Parcelable和Serializable的区别
不同于Serializable将对象进行序列化,Parcelable方式的实现原理是将一个完整的对象进行分解,而分解之后的每一部分都是Intent所支持的数据类型
28.requestLayout()调用的时机
当建立好了decorView与ViewRoot的关联后,ViewRoot类的requestLayout()方法会被调用,以完成应用程序用户界面的初次布局。实际被调用的是ViewRootImpl类的requestLayout()方法
29.事件传递到子view,但是子view不处理触摸事件的话会咋样?
触摸事件分发过程中,如果事件传递到子view,子view不处理,返还给上一层父view,父view拦截下来的话,会把事件传递给自身的onTouchEvent()。不拦截的话继续往上传,直到有一层父view拦截下来。如果上传到Activity也不处理的话,该事件将会被抛弃。
30.Android APK 编译打包流程
编译–>DEX–>打包–>签名和对齐
31.Dalvik虚拟机和ART虚拟机的区别
32.往SharedPreferences存储数据的步骤
(1)调用SharedPreferences的edit()方法获取SharedPreferences.Editor对象
(2)向SharedPreferences.Editor对象添加数据
(3)调用apply()方法将添加的数据提交,完成数据存储操作
33.kotlin中的Any和JAVA中的Object的区别与联系
区别:两者定义的方法不同
(1)Any中定义的方法有:toString()、equals()、hashCode() 3个
(2)Object类中定义的方法有:toString()、equals()、hashCode()、getClass()、clone()、finalize()、notify()、notifyAll()、wait()、wait(long)、wait(long,int) 11个
联系:Kotlin编译器将kotlin.Any和java.lang.Object类视作两个不同的类,但是在运行时它们俩就是一样的。你可以打印:println(Any().javaClass),打印结果都是class java.lang.Object
34.View滑动冲突解决办法
(1)外部拦截法
(2)内部拦截法(需要结合disallowInterceptTouchEvent()方法使用)
35.谈谈你对代码混淆的理解
(1)代码混淆(Obfuscated code)是将程序中的代码以某种规则转换为难以阅读和理解的代码的一种行为。
(2)混淆的好处就是它的目的:令 APK 难以被逆向工程,即很大程度上增加反编译的成本。此外,Android 当中的"混淆"还能够在打包时移除无用资源,显著减少 APK 体积。最后,还能以变通方式避免 Android 中常见的 64k 方法数引用的限制
(3)Java 平台为我们提供了 Proguard 混淆工具来帮助我们快速地对代码进行混淆。根据 Java 官方介绍,Proguard 对应的具体中文定义如下:
A.它是一个包含代码文件压缩、优化、混淆和校验等功能的工具
B.它能够检测并删除无用的类、变量、方法和属性
C.它能够优化字节码并删除未使用的指令
D.它能够将类、变量和方法的名字重命名为无意义的名称从而达到混淆效果
E.最后,它还会校验处理后的代码,主要针对 Java 6 及以上版本和 Java ME
36.内存中的图片是什么引用类型?
内存中的图片是软引用,在内存不足的时候会对内存中的图片进行清除
37.ANR
38.MMKV
39.SharedPreference读和写有啥特点?
SharedPreference读和写都是加锁的
40.是不是任何时候Parcelable都要优于Serializable?
Parcelable数据读写在共享内存中进行,当需要储存到设备时,因为在外界有变化的情况下,Parcelable不能很好的保证数据的持续性,所以这种情况下建议使用Serializable
41.SQLite是线程安全的吗?
SQLite数据库本身不具备线程安全性
42.ListView和RecyclerView的缓存机制
ListView有两级缓存,分别是Active View和Scrap View,缓存的对象是ItemView;而RecyclerView有四级缓存,分别是Scrap、Cache、ViewCacheExtension和RecycledViewPool,缓存的对象ViewHolder。Scrap和Cache分别是通过position去找ViewHolder可以直接复用;ViewCacheExtension自定义缓存,目前来说应用场景比较少却需慎用;RecycledViewPool通过type来获取ViewHolder,获取的ViewHolder是个全新,需要重新绑定数据
43.图片在内存中的大小如何计算?
图片在内存中的大小是根据width,height一个像素的所占用的字节数计算的
44.DecorView和ContentView两者的包含关系
DecorView包含ContentViews,DecorView是FrameLayout的子类,说白了也是继承自View
45.高阶函数
如果一个函数接收另一个函数作为参数,或者返回值的类型是另一个函数,那么该函数就称为高阶函数
46.函数式API
函数式API是接收Lambda参数的函数
47.Lambda表达式
Lambda表达式是一小段可以作为参数传递的代码
48.一次Binder通信最大传输容量
一次Binder通信最大可以传输是1MB-8KB(PS:8k是两个pagesize,一个pagesize是申请物理内存的最小单元)
49.Fragment有没有OnRestart()方法?
没有
50.客户端和服务端如何实现数据同步
增量同步、全量同步
51.Handler机制中Message的属性字段
属性字段:arg1、arg2、what、obj、replyTo等;其中arg1和arg2是用来存放整型数据的;what是用来保存消息标示的;obj是Object类型的任意对象;replyTo是消息管理器,会关联到一个handler,handler就是处理其中的消息
52.Binder跨进程通信过程
53.SharePreference内部怎么保证数据读取速度
因为SharePreference会进行预加载,提前将XML的内容通过io流的形式放在一个map集合中,之后要取数据的时候就可以直接从map集合中读取,而不用再从xml中读取,加快了读取速度
54.AssetManager的基本定义
Provides access to an application’s raw asset files; see Resources for the way most applications will want to retrieve their resource data. This class presents a lower-level API that allows you to open and read raw files that have been bundled with the application as a simple stream of bytes.
55.函数式编程
函数式编程(Functional Programming)是一种编程范式,它将计算机运算视为数学上的函数计算,并且避免使用程序状态以及易变对象。函数编程语言最重要的基础是λ演算(lambda calculus)。而且λ演算的函数可以接受函数当作输入(引数)和输出(传出值)。
比起指令式编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而不是设计一个复杂的执行过程。
56.函数式编程副作用
(1)修改一个变量
(2)修改一个对象的字段值
(3)抛出异常
(4)在控制台显示信息、从控制台接收输入
(5)在屏幕上显示(GUI)
(6)读写文件、网络、数据库
57.对handler中消息分发(dispatchMessage(msg))的理解
(1)若msg.callback属性不为空,则代表使用了post(Runnable r)发送消息,则直接回调Runnable对象里复写的run()
(2)若msg.callback属性为空,则代表使用了sendMessage(Message msg)发送消息,则回调复写的handleMessage(msg)
58.databinding的缺点
(1)扩展性不好
(2)一旦我们开始实现复杂的布局,将会使我们的Data Binding解决方案越来越复杂
(3)databinding导致无法使用单元测试
(4)databinding提供的功能比butterknife少很多
59.Android获取屏幕分辨率的方法
getDisplayMetrics()
笔试知识点
1.
2.
3.
4.
5.
6.
7.
8.