缘起
有很多Android 开发人员准备面试,却不知道如何准备?于是纷纷上网发帖求助。
甚至很多人网上随便找找面试题什么的,其结果就是[字节惨挂在三面],挥泪整理面筋。等诸多文章火了,究其原因就是这些文章戳中了大部分面试者的痛点,面试被刷了。
想到还有很多android程序员没有找到Android面试大纲。不知道如何准备Android面试。
于是,我就想干脆我自己做这个事吧,就算没人看,也当我自己的年终总结了。
如何准备android面试
纯技术方面的准备,如果是的话,我就提供一些拙见,大部分算是一些开发知识死角或者tips吧,权当抛砖引玉了:)
今天就总结下2020年搜集整理的面试题,难度不大,大佬可以直接路过,当然发发善心点个赞也是可以的❤️。
Activity面试题
1、Activity是什么
Activity是四大组件之一,它提供一个界面让用户点击和各种滑动操作,这就是Activity
2、Activity四种状态
- runing
- paused
- stopped
- killed
3、Activity生命周期
- onCreate()
- onStart()
- onResume()
- onPause()
- onStop()
- onDestroy()
-
onRestart()
4、Activity切换横屏时生命周期
- onSaveInstanceState()
- onPause()
- onStop()
- onDestroy()
- onCreate()
- onStart()
- onRestoreInstanceState()
- onResume()
5、进程的优先级
oom_adj
是linux内核分配给每个系统进程的一个值,代表进程的优先级,进程回收机制就是根据这个优先级来决定是否进行回收。进程的oom_adj
越大,表示此进程优先级越低,越容易被杀回收;越小,表示进程优先级越高,越不容易被杀回收。普通app进程的oom_adj
>=0,系统进程的oom_adj
才可能小于0。进程优先级从小到大如下
- 空进程
- 后台进程
- 服务进程
- 可见进程
- 前台进程
6、Activity任务栈
- 先进后出
7、Activity启动模式
- standard
- singletop
- singletask
- singleinstance
8、scheme跳转协议
android中的scheme是一种页面内跳转协议,通过定义自己的scheme协议,可以跳转到app中的各个页面
- 服务器可以定制化告诉app跳转哪个页面
- App可以通过跳转到另一个App页面
- 可以通过H5页面跳转页面
9、Context、Activity、Application之间有什么区别
Activity和Application都是Context的子类。Context从字面上理解就是上下文的意思,在实际应用中它也确实是起到了管理上下文环境中各个参数和变量的总用,方便我们可以简单的访问到各种资源。虽然Activity和Application都是Context的子类,但是他们维护的生命周期不一样。前者维护一个Acitivity的生命周期,所以其对应的Context也只能访问该activity内的各种资源。后者则是维护一个Application的生命周期
10、Activity启动过程
- 在安装应用的时候,系统会启动PackaManagerService管理服务,这个管理服务会对AndroidManifest进行解析,从而得到应用程序中的相关信息,比如service,activity,Broadcast等等,然后获得相关组件的信息
- 当用户点击应用图标时,就会调用
startActivitySately()
,而这个方法内部则是调用startActivty()
,startActivity()
最终还是会调用startActivityForResult()
。由于startActivityForResult()
是有返回结果的,系统直接返回-1,表示不需要返回结果 -
startActivityForResult()
通过Instrumentation类中的execStartActivity()
来启动activity,Instrumentation这个类主要作用是监控程序和系统之间的交互。在这个execStartActivity()
中会获取ActivityManagerService的代理对象,通过这个代理对象进行启动activity - 在ActivityManagerService的代理对象中,通过Binder通信,调用到
ApplicationThread.scheduleLaunchActivity()
进行启动activity,在这个方法中创建一个ActivityClientRecord对象,用来记录启动Activity组件的信息,然后通过handler将ActivityClientRecord发送出去 - 在handler收到消息后,调用
ActivityThread.handleLaunchActivity()
启动Activity
11、简述Activity,View,Window三者关系
- Activity本质是上下文,View本质是视图,Window本质是窗口
- Activity构造的时候会初始化一个Window,其具体实现是PhoneWindow
- PhoneWindow中有一个ViewRoot(View或ViewGroup),是最初始的根视图
- ViewRoot通过addView()将View添加到根视图上,实际上是将View交给了PhoneWindow处理
- View的事件监听是由WindowManagerService来接受消息,并且回调Activity函数
Fragment面试题
1、Fragment为什么被称为第五大组件
Fragment比Activity更节省内存,其切换模式也更加舒适,使用频率不低于四大组件,且有自己的生命周期,而且必须依附于Activity
2、Activity创建Fragment的方式
- 静态创建
- 动态创建
3、FragmentPageAdapter和FragmentPageStateAdapter的区别
- FragmentPageAdapter在每次切换页面的的时候,是将Fragment进行分离,适合页面较少的Fragment使用以保存一些内存,对系统内存不会多大影响
- FragmentPageStateAdapter在每次切换页面的时候,是将Fragment进行回收,适合页面较多的Fragment使用,这样就不会消耗更多的内存
4、Fragment生命周期
- onAttach()
- onCreate()
- onCreateView()
- onActivityCreated()
- onStart()
- onResume()
- onPause()
- onStop()
- onDestroyView()
- onDestroy()
-
onDetach()
5、Fragment的通信
- Fragment调用Activity中的方法:getActivity
- Activity调用Fragment中的方法:接口回调
- Fragment调用Fragment中的方法:FragmentManager.findFragmentById
6、Fragment的replace、add、remove方法
- replace:替代Fragment的栈顶页面
- add:添加Fragment到栈顶页面
- remove:移除Fragment栈顶页面
Service面试题
1、Service是什么
Service是四大组件之一,它可以在后台执行长时间运行操作而没有用户界面的应用组件
2、Service和Thread的区别
- Service是安卓中系统的组件,它运行在独立进程的主线程中,不可以执行耗时操作。Thread是程序执行的最小单元,分配CPU的基本单位,可以开启子线程执行耗时操作
- Service在不同Activity中可以获取自身实例,可以方便的对Service进行操作。Thread在不同的Activity中难以获取自身实例,如果Activity被销毁,Thread实例就很难再获取得到
3、Service启动方式
- startService
- bindService
4、Service生命周期
- startService
- onCreate()
- onStartCommand()
- onDestroy()
- bindService
- onCreate()
- onBind()
- onUnbind()
- onDestroy()
Broadcast Receiver面试题
1、Broadcast Receiver是什么
Broadcast是四大组件之一,是一种广泛运用在应用程序之间传输信息的机制,通过发送Intent来传送我们的数据
2、Broadcast Receiver的使用场景
- 同一App具有多个进程的不同组件之间的消息通信
- 不同App之间的组件之间的消息通信
3、Broadcast Receiver的种类
- 普通广播
- 有序广播
- 本地广播
- Sticky广播
4、Broadcast Receiver的实现
- 静态注册:注册后一直运行,尽管Activity、进程、App被杀死还是可以接收到广播
- 动态注册:跟随Activity的生命周期
5、Broadcast Receiver实现机制
- 自定义广播类继承BroadcastReceiver,复写onReceiver()
- 通过Binder机制向AMS进行注册广播
- 广播发送者通过Binder机制向AMS发送广播
- AMS查找符合相应条件的广播发送到BroadcastReceiver相应的循环队列中
- 消息队列执行拿到广播,回调BroadcastReceiver的onReceiver()
6、LocalBroadcastManager特点
- 本地广播只能在自身App内传播,不必担心泄漏隐私数据
- 本地广播不允许其他App对你的App发送该广播,不必担心安全漏洞被利用
- 本地广播比全局广播更高效
- 以上三点都是源于其内部是用Handler实现的
WebView面试题
1、WebView安全漏洞
- API16之前存在远程代码执行安全漏洞,该漏洞源于程序没有正确限制使用WebView.addJavascriptInterface方法,远程攻击者可通过使用Java反射机制利用该漏洞执行任意Java对象的方法
2、WebView销毁步骤
- WebView在其他容器上时(如:LinearLayout),当销毁Activity时,需要在onDestroy()中先移除容器上的WebView,然后再将WebView.destroy(),这样就不会导致内存泄漏
3、WebView的jsbridge
- 客户端和服务端之间可以通过Javascript来互相调用各自的方法
4、WebViewClient的onPageFinished
- WebViewClient的onPageFinished在每次完成页面的时候调用,但是遇到未加载完成的页面跳转其他页面时,就会一直调用,使用WebChromeClient.onProgressChanged可以替代
5、WebView后台耗电
- 在WebView加载页面的时候,会自动开启线程去加载,如果不很好的关闭这些线程,就会导致电量消耗加大,可以采用暴力的方法,直接在onDestroy方法中System.exit(0)结束当前正在运行中的java虚拟机
6、WebView硬件加速
Android3.0引入硬件加速,默认会开启,WebView在硬件加速的情况下滑动更加平滑,性能更加好,但是会出现白块或者页面闪烁的副作用,建议WebView暂时关闭硬件加速
7、WebView内存泄漏
由于WebView是依附于Activity的,Activity的生命周期和WebView启动的线程的生命周期是不一致的,这会导致WebView一直持有对这个Activity的引用而无法释放,解决方案如下
- 独立进程,简单暴力,不过可能涉及到进程间通信(推荐)
- 动态添加WebView,对传入WebView中使用的Context使用弱引用
Binder面试题
1、Linux内核的基本知识
- 进程隔离/虚拟地址空间:进程间是不可以共享数据的,相当于被隔离,每个进程被分配到不同的虚拟地址中
- 系统调用:Linux内核对应用有访问权限,用户只能在应用层通过系统调用,调用内核的某些程序
- binder驱动:它负责各个用户的进程,通过binder通信内核来进行交互的模块
2、为什么使用Binder
- 性能上,相比传统的Socket更加高效
- 安全性高,支持协议双方互相校验
3、Binder通信原理
- Service端通过Binder驱动在ServiceManager的查找表中注册Object对象的add方法
- Client端通过Binder驱动在ServiceManager的查找表中找到Object对象的add方法,并返回proxy对象的add方法,add方法是个空实现,proxy对象也不是真正的Object对象,是通过Binder驱动封装好的代理类的add方法
- 当Client端调用add方法时,Client端会调用proxy对象的add方法,通过Binder驱动去请求ServiceManager来找到Service端真正对象,然后调用Service端的add方法
4、AIDL
- 客户端通过aidl文件的Stub.asInterface()方法,拿到Proxy代理类
- 通过调用Proxy代理类的方法,将参数进行封包后,调用底层的transact()方法
- transact()方法会回调onTransact()方法,进行参数的解封
- 在onTransact()方法中调用服务端对应的方法,并将结果返回
5、BpBinder和BBinder
BpBinder(客户端)对象和BBinder(服务端)对象,它们都从IBinder类中派生而来,BpBinder(客户端)对象是BBinder(服务端)对象的代理对象,关系图如下
- client端:BpBinder.transact()来发送事务请求
- server端:BBinder.onTransact()会接收到相应事务
Handler面试题
1、Handler是什么
Handler通过发送和处理Message和Runnable对象来关联相对应线程的MessageQueue
2、Handler使用方法
- post(runnable)
- sendMessage(message)
3、Handler工作原理
4、Handler引起的内存泄漏
-
原因:非静态内部类持有外部类的匿名引用,导致Activity无法释放
-
解决:
- Handler内部持有外部Activity的弱引用
- Handler改为静态内部类
- Handler.removeCallback()
AsyncTask面试题
1、AsyncTask是什么
它本质上就是一个封装了线程池和Handler的异步框架
2、AsyncTask使用方法
-
三个参数
- Params:表示后台任务执行时的参数类型,该参数会传给AysncTask的doInBackground()方法
- Progress:表示后台任务的执行进度的参数类型,该参数会作为onProgressUpdate()方法的参数
- Result:表示后台任务的返回结果的参数类型,该参数会作为onPostExecute()方法的参数
-
五个方法
- onPreExecute():异步任务开启之前回调,在主线程中执行
- doInBackground():执行异步任务,在线程池中执行
- onProgressUpdate():当doInBackground中调用publishProgress时回调,在主线程中执行
- onPostExecute():在异步任务执行之后回调,在主线程中执行
- onCancelled():在异步任务被取消时回调
3、AsyncTask工作原理
4、AsyncTask引起的内存泄漏
-
原因:非静态内部类持有外部类的匿名引用,导致Activity无法释放
-
解决:
- AsyncTask内部持有外部Activity的弱引用
- AsyncTask改为静态内部类
- AsyncTask.cancel()
5、AsyncTask生命周期
在Activity销毁之前,取消AsyncTask的运行,以此来保证程序的稳定
6、AsyncTask结果丢失
由于屏幕旋转、Activity在内存紧张时被回收等情况下,Activity会被重新创建,此时,旧的AsyncTask持有旧的Activity引用,这个时候会导致AsyncTask的onPostExecute()对UI更新无效
7、AsyncTask并行or串行
- AsyncTask在Android 2.3之前默认采用并行执行任务,AsyncTask在Android 2.3之后默认采用串行执行任务
- 如果需要在Android 2.3之后采用并行执行任务,可以调用AsyncTask的executeOnExecutor()
HandlerThread面试题
1、HandlerThread产生背景
当系统有多个耗时任务需要执行时,每个任务都会开启一个新线程去执行耗时任务,这样会导致系统多次创建和销毁线程,从而影响性能。为了解决这一问题,Google提供了HandlerThread,HandlerThread是在线程中创建一个Looper循环器,让Looper轮询消息队列,当有耗时任务进入队列时,则不需要开启新线程,在原有的线程中执行耗时任务即可,否则线程阻塞
2、HanlderThread的特点、
- HandlerThread本质上是一个线程,继承自Thread
- HandlerThread有自己的Looper对象,可以进行Looper循环,可以创建Handler
- HandlerThread可以在Handler的handlerMessage中执行异步方法
- HandlerThread优点是异步不会堵塞,减少对性能的消耗
- HandlerThread缺点是不能同时继续进行多任务处理,需要等待进行处理,处理效率较低
- HandlerThread与线程池不同,HandlerThread是一个串行队列,背后只有一个线程
IntentService面试题
1、IntentService是什么
IntentService是继承自Service并处理异步请求的一个类,其内部采用HandlerThread和Handler实现的,在IntentService内有一个工作线程来处理耗时操作,其优先级比普通Service高。当任务完成后,IntentService会自动停止,而不需要手动调用stopSelf()。另外,可以多次启动IntentService,每个耗时操作都会以工作队列的方式在IntentService中onHandlerIntent()回调方法中执行,并且每次只会执行一个工作线程
2、IntentService使用方法
- 创建Service继承自IntentService
- 覆写构造方法和onHandlerIntent()方法
- 在onHandlerIntent()中执行耗时操作
视图工作机制面试题
事件分发机制面试题
Android项目构建面试题
1、Android构建流程
[图片上传失败…(image-ccd96b-1606878198554)]
2、jenkins持续集成构建
- 这里可参考蒲公英文档
3、git常用命令
- git init:仓库的初始化
- git status:查看当前仓库的状态
- git diff:查看仓库与上次修改的内容
- git add:将文件放进暂存区
- git commit:提交代码
- git clone:克隆代码
- git bransh:查看当前分支
- git checkout:切换当前分支
4、git工作流
-
fork/clone(主流)
- fork:将别人的仓库代码fork到自己的仓库上
- clone:克隆下自己仓库的代码
- update、commit:修改代码并提交到自己的仓库
- push:提交到自己的仓库
- pull request:请求添加到别人的仓库
-
clone
5、proguard是什么
ProGuard工具是用于压缩、优化和混淆我们的代码,其主作用是移除或混淆代码中无用类、字段、方法和属性
6、proguard技术功能
- 压缩
- 优化
- 混淆
- 预检测
7、proguard工作原理
将无用的字段或方法存入到EntryPoint中,将非EntryPoint的字段和方法进行替换
8、为什么要混淆
由于Java是一门跨平台的解释性语言,其源代码被编译成class字节码来适应其他平台,而class文件包含了Java源代码信息,很容易被反编译
9、annotationProcessor与compileOnly的区别
annotationProcessor与compileOnly都是只编译并不打入apk中
- annotationProcessor:编译时生成代码,编译完就不需要了
- compileOnly:有重复的库时,可以剃除重复库,只保留一个库
ANR面试题
1、什么是ANR
Application Not Responding,页面无响应的对话框
2、发生ANR的条件
应用程序的响应性是由ActivityManager和WindowManager系统服务监视的,当ANR发生条件满足时,就会弹出ANR的对话框
- Activity超过5秒无响应
- BroadcastReceiver超过10秒无响应
- Service超过20秒无响应
3、造成ANR的主要原因
主线程被IO操作阻塞
- Activity的所有生命周期回调都是执行在主线程的
- Service默认执行在主线程中
- BoardcastReceiver的回调onReceive()执行在主线程中
- AsyncTask的回调除了doInBackground,其他都是在主线程中
- 没有使用子线程Looper的Handler的handlerMessage,post(Runnable)都是执行在主线程中
4、如何解决ANR
- 使用AsyncTask处理耗时IO操作
- 使用Thread或HandlerThread提高优先级
- 使用Handler处理工作线程的耗时操作
- Activity的onCreate和onResume回调尽量避免耗时操作
OOM面试题
1、什么是OOM
OOM指Out of memory(内存溢出),当前占用内存加上我们申请的内存资源超过了Dalvik虚拟机的最大内存限制就会抛出Out of memory异常
2、OOM相关概念
- 内存溢出:指程序在申请内存时,没有足够的空间供其使用
- 内存泄漏:指程序分配出去的内存不再使用,无法进行回收
- 内存抖动:指程序短时间内大量创建对象,然后回收的现象
3、解决OOM
Bitmap相关
- 图片压缩
- 加载缩略图
- 在滚动时不加载图片
- 回收Bitmap
- 使用inBitmap属性
- 捕获异常
其他相关
- listview重用convertView、使用lru
- 避免onDraw方法执行对象的创建
- 谨慎使用多进程
Bitmap面试题
1、recycle
- 在安卓3.0以前Bitmap是存放在堆中的,我们只要回收堆内存即可
- 在安卓3.0以后Bitmap是存放在内存中的,我们需要回收native层和Java层的内存
- 官方建议我们3.0以后使用recycle方法进行回收,该方法也可以不主动调用,因为垃圾回收器会自动收集不可用的Bitmap对象进行回收
- recycle方法会判断Bitmap在不可用的情况下,将发送指令到垃圾回收器,让其回收native层和Java层的内存,则Bitmap进入dead状态
- recycle方法是不可逆的,如果再次调用getPixels()等方法,则获取不到想要的结果
2、LruCache原理
LruCache是个泛型类,内部采用LinkedHashMap来实现缓存机制,它提供get方法和put方法来获取缓存和添加缓存,其最重要的方法trimToSize是用来移除最少使用的缓存和使用最久的缓存,并添加最新的缓存到队列中
3、计算采样率
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
if (width > height) {
inSampleSize = Math.round((float)height / (float)reqHeight);
} else {
inSampleSize = Math.round((float)width / (float)reqWidth);
}
}
return inSampleSize;
}
4、采样率压缩(缩略图)
public static Bitmap thumbnail(String path,int maxWidth, int maxHeight) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
Bitmap bitmap = BitmapFactory.decodeFile(path, options);
options.inJustDecodeBounds = false;
int sampleSize = calculateInSampleSize(options, maxWidth, maxHeight);
options.inSampleSize = sampleSize;
options.inPreferredConfig = Bitmap.Config.RGB_565;
options.inPurgeable = true;
options.inInputShareable = true;
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
}
bitmap = BitmapFactory.decodeFile(path, options);
return bitmap;
}
5、质量压缩
public static String save(Bitmap bitmap,Bitmap.CompressFormat format, int quality, File destFile) {
try {
FileOutputStream out = new FileOutputStream(destFile);
if (bitmap.compress(format, quality, out)) {
out.flush();
out.close();
}
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
}
return destFile.getAbsolutePath();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
6、尺寸压缩
public static void reSize(Bitmap bmp,File file,int ratio){
Bitmap result = Bitmap.createBitmap(bmp.getWidth()/ratio, bmp.getHeight()/ratio,Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(result);
RectF rect = new RectF(0, 0, bmp.getWidth()/ratio, bmp.getHeight()/ratio);
canvas.drawBitmap(bmp, null, rect , null);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
result.compress(Bitmap.CompressFormat.JPEG, 100, baos);
try {
FileOutputStream fos = new FileOutputStream(file);
fos.write(baos.toByteArray());
fos.flush();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
7、保存到SD卡
public static String save(Bitmap bitmap,Bitmap.CompressFormat format, int quality, Context context) {
if (!Environment.getExternalStorageState()
.equals(Environment.MEDIA_MOUNTED)) {
return null;
}
File dir = new File(Environment.getExternalStorageDirectory()
+ "/" + context.getPackageName() + "/save/");
if (!dir.exists()) {
dir.mkdirs();
}
File destFile = new File(dir, UUID.randomUUID().toString());
return save(bitmap, format, quality, destFile);
}
8、三级缓存
- 网络缓存
- 本地缓存
- 内存缓存
9、NDK压缩
Android进阶——图片优化之质量压缩、尺寸压缩、采样率压缩、LibJpeg压缩
10、webp压缩
Android Webp 完全解析 快来缩小apk的大小吧-鸿洋的博客
UI卡顿面试题
1、UI卡顿原理
View的绘制帧数保持60fps是最佳,这要求每帧的绘制时间不超过16ms(1000/60),如果安卓不能在16ms内完成界面的渲染,那么就会出现卡顿现象
2、UI卡顿的原因分析
- 在UI线程中做轻微的耗时操作,导致UI线程卡顿
- 布局Layout过于复杂,无法在16ms内完成渲染
- 同一时间动画执行的次数过多,导致CPU和GPU负载过重
- overDraw,导致像素在同一帧的时间内被绘制多次,使CPU和GPU负载过重
- View频繁的触发measure、layout,导致measure、layout累计耗时过多和整个View频繁的重新渲染
- 频繁的触发GC操作导致线程暂停,会使得安卓系统在16ms内无法完成绘制
- 冗余资源及逻辑等导致加载和执行缓慢
- ANR
3、UI卡顿的优化
-
布局优化
- 使用include、ViewStub、merge
- 不要出现过于嵌套和冗余的布局
- 使用自定义View取代复杂的View
-
ListView优化
- 复用convertView
- 滑动不加载
-
背景和图片优化
- 缩略图
- 图片压缩
-
避免ANR
- 不要在UI线程中做耗时操作
内存泄漏面试题
1、Java内存泄漏引起的主要原因
长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏
2、Java内存分配策略
- 静态存储区:又称方法区,主要存储全局变量和静态变量,在整个程序运行期间都存在
- 栈区:方法体的局部变量会在栈区创建空间,并在方法执行结束后会自动释放变量的空间和内存
- 堆区:保存动态产生的数据,如:new出来的对象和数组,在不使用的时候由Java回收器自动回收
3、Android解决内存泄漏的例子
- 单例造成的内存泄漏:在单例中,使用context.getApplicationContext()作为单例的context
- 匿名内部类造成的内存泄漏:由于非静态内部类持有匿名外部类的引用,必须将内部类设置为static
- Handler造成的内存泄漏:使用static的Handler内部类,同时在实现内部类中持有Context的弱引用
- 避免使用static变量:由于static变量会跟Activity生命周期一致,当Activity退出后台被后台回收时,static变量是不安全,所以也要管理好static变量的生命周期
- 资源未关闭造成的内存泄漏:比如Socket、Broadcast、Cursor、Bitmap、ListView等,使用完后要关闭
- AsyncTask造成的内存泄漏:由于非静态内部类持有匿名内部类的引用而造成内存泄漏,可以通过AsyncTask内部持有外部Activity的弱引用同时改为静态内部类或在onDestroy()中执行AsyncTask.cancel()进行修复
内存管理面试题
1、Android内存管理机制
- 分配机制
- 管理机制
2、内存管理机制的特点
- 更少的占用内存
- 在合适的时候,合理的释放系统资源
- 在系统内存紧张的时候,能释放掉大部分不重要的资源
- 能合理的在特殊生命周期中,保存或还原重要数据
3、内存优化方法
- Service完成任务后应停止它,或用IntentService(因为可以自动停止服务)代替Service
- 在UI不可见的时候,释放其UI资源
- 在系统内存紧张的时候,尽可能多的释放非重要资源
- 避免滥用Bitmap导致内存浪费
- 避免使用依赖注入框架
- 使用针对内存优化过的数据容器
- 使用ZIP对齐的APK
- 使用多进程
冷启动和热启动面试题
1、什么是冷启动和热启动
- 冷启动:在启动应用前,系统中没有该应用的任何进程信息
- 热启动:在启动应用时,在已有的进程上启动应用(用户使用返回键退出应用,然后马上又重新启动应用)
2、冷启动和热启动的区别
- 冷启动:创建Application后再创建和初始化MainActivity
- 热启动:创建和初始化MainActivity即可
3、冷启动时间的计算
这个时间值从应用启动(创建进程)开始计算,到完成视图的第一次绘制为止
4、冷启动流程
- Zygote进程中fork创建出一个新的进程
- 创建和初始化Application类、创建MainActivity
- inflate布局、当onCreate/onStart/onResume方法都走完
- contentView的measure/layout/draw显示在界面上
总结:点击App->IPC->Process.start->ActivityThread->Application生命周期->Activity生命周期->ViewRootImpl测量布局绘制显示在界面上
5、冷启动优化
- 减少第一个界面onCreate()方法的工作量
- 不要让Application参与业务的操作
- 不要在Application进行耗时操作
- 不要以静态变量的方式在Application中保存数据
- 减少布局的复杂性和深度
- 不要在mainThread中加载资源
- 通过懒加载方式初始化第三方SDK
6、adb命令获取启动时间
adb shell am start -W packagename/MainActivity
- ThisTime:最后一个Activity启动耗时
- TotalTime:所有Activity启动耗时
- WaitTime:AMS启动Activity的总耗时
其他优化面试题
1、Android不用静态变量存储数据
- 静态变量等数据由于进程已经被杀死而被初始化
- 使用其他数据传输方式:文件/sp/contentProvider
2、SharePreference安全问题
- 不能跨进程同步
- 文件不宜过大
3、内存对象序列化
- Serializeble:是java的序列化方式,Serializeble在序列化的时候会产生大量的临时对象,从而引起频繁的GC
- Parcelable:是Android的序列化方式,且性能比Serializeble高,Parcelable不能使用在要将数据存储在硬盘上的情况
4、避免在UI线程中做繁重的操作
架构模式面试题
插件化面试题
1、插件化解决的问题
- 动态加载APK(反射、类加载器)
- 资源加载(反射、AssetManager、独立资源、分段资源)
- 代码加载(反射获取生命周期)
2、类加载器(Java中字节码添加到虚拟机中)
- DexClassLoader:能够加载未安装的jar/apk/dex,主要用于动态加载和代码热更新
- PathClassLoader:加载/data/app目录下的apk文件,只要用来加载系统中已经安装过的apk
热更新面试题
1、热更新主要流程
- 线上检查到Crash
- 拉出Bugfix分支修复Crash问题
- jenkins构建和补丁生成
- app通过推送或主动拉取补丁文件
- 将Bugfix代码合到master上
2、热更新主流框架
- Dexposed
- AndFix
- Nuwa
3、热更新的原理
- 在ClassLoader创建一个dexElements数组
- 将修复好的dex文件存放在dexElements数组的最前面
- ClassLoader会遍历dexElements数组,找到最前面的dex文件优先加载
进程保活面试题
1、进程的优先级
- 空进程
- 后台进程
- 服务进程
- 可见进程
- 前台进程
2、Android进程回收策略
- Low memory Killer(定时执行):通过一些比较复杂的评分机制,对进程进行打分,然后将分数高的进程判定为bad进程,杀死并释放内存
- OOM_ODJ:判别进程的优先级
3、Android保活方案
- 利用系统广播拉活
- 利用系统Service机制拉活
- 利用Native进程拉活
- 利用JobScheduler机制拉活
- 利用账号同步机制拉活
Lint面试题
1、什么是Android Lint
Android Lint是一个静态代码分析工具,它能够对你的Android项目中潜在的Bug、可优化的代码、安全性、性能、可用性、可访问性、国际化等进行检查
2、Lint工作流程
[图片上传失败…(image-d5f5ab-1606878198551)]
3、配置Lint
- 创建Lint.xml到根目录下,自定义Lint安全等级等
- 在Java文件中可以使用@suppressLint(“NewApi”)来忽视Lint的报错
- 在xml文件中可以使用tool:ignore(“UnusedResources”)来忽视Lint的报错
- 自定义Lint检查,可以创建类,继承Detector和实现JavaPsiScanner
Kotlin面试题
1、什么是Kotlin
- Kotlin是一种基于JVM的编程语言
- 对Java的一种拓展,比Java更简洁
- Kotlin支持函数式编程
- Kotlin类和Java类可以相互调用
2、Kotlin环境搭建
- 直接在Plugin中下载Kotlin插件即可
- 系统会自动配置到Kotlin环境
虚拟机面试题
1、Dalvik与JIT
Dalvik是Android虚拟机,JIT则是Dalvik采用的技术策略
在编译打包APK文件时,会经过以下流程Java->Class->Dalvik字节码(dex)->每次执行代码都要编译成机器码->交给底层处理
,这样处理起来效率低下,通过引入JIT(即时编译技术),当App运行时,每当遇到一个新类,JIT编译器就会对这个类进行即时编译,经过编译后的代码,会被优化成相当精简的原生型指令码,这样在下次执行到相同逻辑的时候,速度就会更快。但由于每次启动App都需要即时编译,导致运行时耗电量大
2、ART与AOT
ART是Android虚拟机,AOT则是ART采用的技术策略
在ART环境中,应用在第一次安装的时候,字节码就会预先编译成机器码,使其成为真正的本地应用,这一技术称为AOT。之后打开App的时候,不需要额外的翻译工作,直接使用本地机器码运行,因此运行速度提高。但由于会预先编译,安装时间难免会延长,而且会消耗更多的存储空间,但消耗掉空间的增幅通常不会超过应用代码包大小的20%
3、发展史
- 2.2 ~ 4.4:Dalvik虚拟机使用JIT技术
- 4.4 ~ 5.0:Dalvik虚拟机和ART虚拟机共存
- 5.0 ~ x.0:ART虚拟机使用AOT技术替代Dalvik虚拟机
4、JVM与Dalvik不同
- 类加载系统区别比较大
- JVM是基于栈,Dalvik是基于寄存器
- JVM执行的是java字节码文件,Dalvik执行的是dex字节码文件
- Dalvik可以同时存在多个,即使一个退出了也不会影响其他程序
5、Dalvik与ART不同
- Dalvik使用JIT(Just In Time 运行时编译)来将字节码转换成机器码,效率低
- ART采用AOT(Ahead Of Time 运行前编译或安装时编译)预编译技术,应用运行速度更快
- ART会占用更多的应用安装时间和存储空间
注解面试题
1、什么是Annotation
Java提供的一种元程序中的元素关联任何信息和任何元数据(metadata)的途径和方法
2、什么是metadata
- 元数据以标签的形式存在于Java代码中
- 元数据描述的信息是类型安全的
- 元数据需要编译器之外的工具额外的处理用来生成其他的程序部件
- 元数据可以只存在于Java源代码级别,也可以存在于编译之后的Class文件内部
3、注解分类
-
系统内置标准注解
- @Override:表示重写
- @Deprecated:表示已过时
- @SuppressWarnnings:表示抑制警告
-
元注解
- @Target:表示注解的修饰范围
- @Retention:表示注解的代码生存期
- @Documented:表示注解为程序员的API
- @Inherited:表示注解可以继承
-
@Target
- ElementType.CONSTRUCTOR:构造器声明
- ElementType.FIELD:成员变量、对象、属性(包括enum实例)
- ElementType.LOCAL_VARIABLE:局部变量声明
- ElementType.METHOD:方法声明
- ElementType.PACKAGE:包声明
- ElementType.PARAMETER:参数声明
- ElementType.TYPE:类、接口(包括注解类型)或enum声明
-
@Retention
- RetentionPolicy.SOURCE:在源文件中有效,当Java文件编译成class文件的时候,注解被遗弃
- RetentionPolicy.CLASS:在class文件中有效,当jvm加载class文件时候被遗弃
- RetentionPolicy.RUNTIME:在运行时有效,当jvm加载class文件之后,仍然存在
-
生命周期
- RUNTIME > CLASS > SOURCE
- .java文件 --> .class文件 --> 内存中的字节码
4、注解声明
注解支持填写数组,同时支持多种范围
@Target({ElementType.TYPE,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ModuleWrapper {}
5、Android Support Annotation
- Nullness注解(@NonNull、@Nullable):表示指定的空类型或非空类型
- ResourceType注解(@StringRes、@IntegerRes、@ColorRes等):表示指定的变量类型
- Threading注解(@WorkerThread、@UIThread、@MainThread等):表示指定的线程类型
- Range注解(@IntRange、@FloatRange等):表示允许传值的范围
- OverridingMethods注解(@CallSuper等):表示调用父类的方法
6、注解的用途
- 约束类
- 约束变量
- 约束入参
- 约束返回值
网络面试题
1.网页中输入url,到渲染整个界面的整个过程,以及中间用了什么协议?
1)过程分析:主要分为三步
-
DNS解析
。用户输入url后,需要通过DNS解析找到域名对应的ip地址,有了ip地址才能找到服务器端。首先会查找浏览器缓存,是否有对应的dns记录。再继续按照操作系统缓存—路由缓存—isp的dns服务器—根服务器的顺序进行DNS解析,直到找到对应的ip地址。 -
客户端(浏览器)和服务器交互
。浏览器根据解析到的ip地址和端口号发起HTTP请求,请求到达传输层,这里也就是TCP层,开始三次握手建立连接。服务器收到请求后,发送相应报文给客户端(浏览器),客户端收到相应报文并进行解析,得到html页面数据,包括html,js,css等。 -
客户端(浏览器)解析html数据
,构建DOM树,再构造呈现树(render树),最终绘制到浏览器页面上。
2)其中涉及到TCP/IP协议簇,包括DNS,TCP,IP,HTTP协议等等。
2.具体介绍下TCP/IP
TCP/IP一般指的是TCP/IP协议簇,主要包括了多个不同网络间实现信息传输涉及到的各种协议
主要包括以下几层:
-
应用层
:主要提供数据和服务。比如HTTP,FTP,DNS等 -
传输层
:负责数据的组装,分块。比如TCP,UDP等 -
网络层
:负责告诉通信的目的地,比如IP等 -
数据链路层
:负责连接网络的硬件部分,比如以太网,WIFI等
3.TCP的三次握手和四次挥手,为什么不是两次握手?为什么挥手多一次呢?
客户端简称A,服务器端简称B
1)TCP建立连接需要三次握手
- A向B表示想跟B进行连接(A发送
syn
包,A进入SYN_SENT
状态) - B收到消息,表示我也准备好和你连接了(B收到
syn
包,需要确认syn
包,并且自己也发送一个syn
包,即发送了syn+ack
包,B进入SYN_RECV
状态) - A收到消息,并告诉B表示我收到你也准备连接的信号了(A收到
syn+ack
包,向服务器发送确认包ack
,AB进入established
状态)开始连接。
2)TCP断开连接需要四次挥手
- A向B表示想跟B断开连接(A发送
fin
,进入FIN_WAIT_1
状态) - B收到消息,但是B消息没发送完,只能告诉A我收到你的断开连接消息(B收到fin,发送ack,进入
CLOSE_WAIT
状态) - 过一会,B数据发送完毕,告诉A,我可以跟你断开了(B发送fin,进入
LAST_ACK
状态) - A收到消息,告诉B,可以他断开(A收到fin,发送ack,B进入
close
d状态)
3)为什么挥手多一次
其实正常的断开和连接都是需要四次
:
- A发消息给B
- B反馈给A表示正确收到消息
- B发送消息给A
- A反馈给B表示正确收到消息。
但是连接中,第二步和第三步是可以合并
的,因为连接之前A和B是无联系的,所以没有其他情况需要处理。而断开的话,因为之前两端是正常连接状态,所以第二步的时候不能保证B之前的消息已经发送完毕,所以不能马上告诉A要断开的消息。这就是连接为什么可以少一步的原因。
4)为什么连接需要三次,而不是两次。
正常来说,我给你发消息,你告诉我能收到,不就代表我们之前通信是正常的吗?
- 简单回答就是,
TCP是双向通信协议
,如果两次握手,不能保证B发给A的消息正确到达。
TCP 协议为了实现可靠传输, 通信双方需要判断自己已经发送的数据包是否都被接收方收到, 如果没收到, 就需要重发。
TCP是怎么保证可靠传输的?
-
序列号和确认号
。比如连接的一方发送一段80byte数据,会带上一个序列号,比如101。接收方收到数据,回复确认号181(180+1),这样下一次发送消息就会从181开始发送了。
所以握手过程中,比如A发送syn信号给B,初始序列号为120,那么B收到消息,回复ack
消息,序列号为120+1。同时B发送syn
信号给A,初始序列号为256,如果收不到A的回复消息,就会重发,否则丢失这个序列号,就无法正常完成后面的通信了。
这就是三次握手的原因。
4.TCP和UDP的区别?
TCP
提供的是面向连接,可靠的字节流服务。即客户和服务器交换数据前,必须现在双方之间建立一个TCP连接(三次握手),之后才能传输数据。并且提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。
UDP
是一个简单的面向数据报的运输层协议。它不提供可靠性,只是把应用程序传给IP层的数据报发送出去,但是不能保证它们能到达目的地。由于UDP
在传输数据报前不用再客户和服务器之间建立一个连接,且没有超时重发等机制,所以传输速度很快。
所以总结下来就是:
- TCP 是面向连接的,UDP 是面向无连接的
- TCP数据报头包括序列号,确认号,等等。相比之下UDP程序结构较简单。
- TCP 是面向字节流的,UDP 是基于数据报的
- TCP 保证数据正确性,UDP 可能丢包
- TCP 保证数据顺序,UDP 不保证
可以看到TCP
适用于稳定的应用场景,他会保证数据的正确性和顺序,所以一般的浏览网页,接口访问都使用的是TCP
传输,所以才会有三次握手
保证连接的稳定性。
而UDP是一种结构简单的协议,不会考虑丢包啊,建立连接等。优点在于数据传输很快,所以适用于直播,游戏等场景。
5.HTTP的几种请求方法具体介绍
常见的有四种:
-
GET
获取资源,没有body,幂等性 -
POST
增加或者修改资源,有body -
PUT
修改资源,有body,幂等性 -
DELETE
删除资源,幂等性
6.HTTP请求和响应报文的格式,以及常用状态码
1)请求报文:
//请求行(包括method、path、HTTP版本)
GET /s HTTP/1.1
//Headers
Host: www.baidu.com
Content-Type: text/plain
//Body
搜索****
2)响应报文
//状态行 (包括HTTP版本、状态码,状态信息)
HTTP/1.1 200 OK
//Headers
Content-Type: application/json; charset=utf-8
//Body
[{"info":"xixi"}]
3)常用状态码
主要分为五种类型:
-
1开头
, 代表临时性消息,比如100(继续发送) -
2开头
, 代表请求成功,比如200(OK) -
3开头
, 代表重定向,比如304(内容无改变) -
4开头
, 代表客户端的一些错误,比如403(禁止访问) -
5开头
, 代表服务器的一些错误,比如500
出于篇幅的考量,本文不可能对Android面试大纲所有面试模块作出说明。即便如此,沿着本文提供的思路,参考相关章节内容,分析相关知识点和源码,你会很快、很容易地拿到一线互联网企业的offer。
文档领取方式:点此快速入手通道免费获取。
网友评论