一、Activity生命周期
实际面试中可能会以实例形式出现,比如:启动A,再从A启动B,请描述各生命周期
二、Activity的启动模式
Activity的启动模式有4种,分别是Standard、SingleTop、SingleTask、SingleInstance
Standard模式:这种模式下,Activity可以有多个实例,每次启动Activity,无论任务栈中是否已经有这个Activity的实例,系统都会创建一个新的Activity实例
SingleTop模式:它和standard模式非常相似,主要区别就是当一个singleTop模式的Activity已经位于任务栈的栈顶,再去启动它时,不会再创建新的实例,如果不位于栈顶,就会创建新的实例
SingleTask模式:此模式下的Activity在同一个Task内只有一个实例,如果Activity已经位于栈顶,系统不会创建新的Activity实例,和singleTop模式一样。但Activity已经存在但不位于栈顶时,系统就会把该Activity移到栈顶,并把它上面的activity出栈
SingleInstance:此模式是全局单例的,启动一个singleInstanceActivity时,系统会创建一个新的任务栈,并且这个任务栈只有这一个Activity,与默认任务栈不在一个栈内,一般在系统应用中会用到
三、App及Activity的启动过程
应用的启动步骤:
1,Launcher通过Binder进程间通信机制通知ActivityManagerService,它要启动一个Activity
2,ActivityManagerService通过Binder进程间通信机制通知Launcher进入Paused状态
3,Launcher通过Binder进程间通信机制通知ActivityManagerService,它已经准备就绪进入Paused状态,于是ActivityManagerService就创建一个新的进程,用来启动一个ActivityThread实例,即将要启动的Activity就是在这个ActivityThread实例中运行
4,ActivityThread通过Binder进程间通信机制将一个ApplicationThread类型的Binder对象传递给ActivityManagerService,以便以后ActivityManagerService能够通过这个Binder对象和它进行通信
5,ActivityManagerService通过Binder进程间通信机制通知ActivityThread,现在一切准备就绪,它可以真正执行Activity的启动操作了
四、Broadcast分类,使用、区别
广播大体分为两种不同的类型:普通广播和有序广播,其它如系统广播,本地广播也可以归为这两大类中
区别:
普通广播:所有跟广播的intent匹配的广播接收者都可以收到该广播,并且是没有先后顺序(同时收到)
有序广播:系统会根据接收者声明的优先级别按顺序逐个执行接收者,前面的接收者有权终止广播(BroadcastReceiver.abortBroadcast()),如果广播被前面的接收者终止,后面的接收者就再也无法获取到广播
使用:
先声明广播:
public class IncomingSMSReceiver extendsBroadcastReceiver {
@Override
public void onReceive(Contextcontext, Intentintent) {
}
}
动态注册(记得解注册):
IntentFilter filter = newIntentFilter("android.provider.Telephony.SMS_RECEIVED");
IncomingSMSReceiver receiver = newIncomingSMSReceiver();
registerReceiver(receiver, filter);
五、对服务的理解,如何杀死一个服务。服务的生命周期(start与bind)
篇幅较长,可点击下方链接查看:
Android中对服务Service的理解,Service生命周期学习,如何启动Service及代码验证
六、View的绘制流程
View 绘制中主要流程分为measure,layout, draw 三个阶段。
OnMeasure():测量视图大小。从顶层父View到子View递归调用measure方法,measure方法又回调OnMeasure
OnLayout():确定View位置,进行页面布局。从顶层父View向子View的递归调用view.layout方法的过程,即父View根据上一步measure子View所得到的布局大小和布局参数,将子View放在合适的位置上
OnDraw():绘制视图。ViewRoot创建一个Canvas对象,然后调用OnDraw()。含六个步骤:绘制视图的背景;保存画布的图层(Layer);绘制View的内容;绘制View子视图(如果没有就不用);还原图层(Layer);绘制滚动条
七、事件分发
事件分发的顺序
页面Activity->容器ViewGroup->控件View
事件分发的核心方法:
dispatchTouchEvent:进行实践分发处理,返回结果表示该事件是否需要分发。默认返回true表示分发给下级视图,不过最终是否分发成功还得根据根据onInterceptTouchEvent方法的拦截判断结果;返回false表示不分发
onInterceptTouchEvent:进行事件拦截处理,返回结果表示当前容器是否需要拦截该事件。返回true表示予以拦截,该手势不会分发给下级视图;默认返回false表示不拦截,该手势会分发给下级视图进行后续处理
onTouchEvent:近视事件触摸处理,返回结果表示该事件是否处理完毕。返回true表示处理完毕,无须处理上级视图的onTouchEvent方法,一路返回结束流程;返回false表示该手势尚未完成,返回继续处理上级视图的onTouchEvent方法,然后根据上级onTouchEvent方法的返回值判断直接结束或由上上级处理。
上述手势方法的执行者有3个:
页面类:可操作dispatchTouchEvent和onInterceptTouchEvent两种方法
容器类:可操作dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent三种方法
控件类:可操作dispatchTouchEvent和onTouchEvent两种方法
八、Handler的原理
1,在使用handler的时候,在handler所创建的线程需要维护一个唯一的Looper对象, 每个线程对应一个Looper,每个线程的Looper通过ThreadLocal来保证,Looper对象的内部又维护有唯一的一个MessageQueue,所以一个线程可以有多个handler,
但是只能有一个Looper和一个MessageQueue
2,Message在MessageQueue不是通过一个列表来存储的,而是将传入的Message存入到了上一个Message的next中(单链表结构),在取出的时候通过顶部的Message就能按放入的顺序依次取出Message
3,Looper对象通过loop()方法开启了一个死循环,不断地从looper内的MessageQueue中取出Message,然后通过handler将消息分发传回handler所在的线程
4,Handler的构造方法,会得到当前线程中保存的Looper实例,进而与Looper实例中的MessageQueue想关联。Handler的sendMessage方法,会给msg的target赋值为handler自身,然后加入MessageQueue中
5、在构造Handler实例时,我们会重写handleMessage方法,也就是msg.target.dispatchMessage(msg)最终调用的方法
Handler引起内存泄漏问题的解决:使用静态内部类+弱引用的方式;在外部类对象被销毁时,将MessageQueue中的消息清空
九、Android动画种类、区别
Android 中的动画有帧动画,补间动画,属性动画
帧动画:主要用于播放帧帧准备图片类似GIF图片优点使用简单便、缺点需要事先准备每帧图片
补间动画:补间动画可以对View进行位置,大小,旋转,透明度四种变化,但是补间动画并没有真正改变View的属性
属性动画:弥补了补间动画不能改变属性的缺点,可以直接更改属性、几乎适用于任何对象
十、Fragment与Fragment、Activity通信的方式
1,直接在一个Fragment中调用另外一个Fragment中的方法
2,使用接口回调
3,使用广播
4,Fragment直接调用Activity中的public方法
5,Bundle传递
十一、保存Activity状态
在当前Activity被销毁之前会调用onSaveInstanceState()方法
Activity重新创建之后会调用onRestoreInstanceState()方法(如果有数据需要恢复)
所以,重写这两个方法即可
调用时机:onSaveInstanceState方法是在onPaused之后onStop之前,onRestoreInstanceState方法是在onStart之后onResume之前
十二、Activity,Window跟View之间的关系
我们看图了解一下Activity,PhoneWindow,DecorView之间的联系
Activity:一个Activity就“相当于”一个界面(通过setContentView指定具体的View)。我们可以直接在Activity里处理事件,如onKeyEvent,onTouchEvent等。 并可以通过Activity维护应用程序的生命周期
Window:表示一个窗口,一般来说,Window大小取值为屏幕大小。但是这不是绝对的,如对话框、Toast等就不是整个屏幕大小。你可以指定Window的大小。Window是一个抽象基类,是 Activity 和整个 View 系统交互的接口,只有一个子类实现类PhoneWindow
View:主要是用于绘制我们想要的结果,是一个基本的UI组件
DecorView:DecorView是一个Window的根容器,它本质上是一个FrameLayout。DecorView有唯一一个子View,它是一个垂直LinearLayout,包含两个子元素,一个是TitleView(ActionBar的容器),另一个是ContentView(窗口内容的容器)。关于ContentView,它是一个FrameLayout(android.R.id.content),我们平常用的setContentView就是设置它的子View
总体来说:一个Activity构造的时候只能初始化一个Window(PhoneWindow),Activity会调用PhoneWindow的setContentView()将layout布局添加到DecorView上,而此时的DecorView就是那个最底层的View。然后通过LayoutInflater.infalte()方法加载布局生成View对象并通过addView()方法添加到Window上(PhoneWindow有一个View容器 mContentParent,这个View容器是一个ViewGroup,是最初始的根视图,然后通过addView方法将View一个个层叠到mContentParent上,这些层叠的View最终放在Window这个载体上面)。
十三、ViewHolder有什么用
ViewHolder是一个持有者的类,它的作用就是一个临时的储存器。在列表中,每一个item 的图层都是一样的,那么每次getview 的时候就需要重复的去查找,使用ViewHolder可以把你getView方法中每次返回的View存起来,可以下次再用。这样做的好处就是不必每次都到布局文件中去拿到你的View,提高了效率。
十四、Intentservice有什么用
IntentService是Service类的子类,用来处理异步请求,是一个封装了HandlerThread和Handler的异步框架,可用于执行后台耗时的任务(Service处于主线程不能直接进行耗时操作),任务执行后会自动停止,并且可以多次启动。
如何使用,创建一个类继承自IntentService,实现onHandleIntent()方法,用于实现任务逻辑,启动时要使用startService()方式
十五、有哪几种创建线程的方式,优缺点
1,继承Thread类
2,实现Runnable接口
3,实现Callable接口
继承Thread类的优缺点:编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。缺点:线程类已经继承了Thread类,所以不能再继承其他父类
实现Runnable接口和Calllable接口的优缺点:在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。缺点:编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法
十六、Android中跨进程通讯的几种方式
1,通过Bundle:Bundle实现了Parcelable接口(一种特有的序列化方法),所以它可以很方便的在不同的进程之间进行传输。简单易用,但传输的数据类型受限
2,通过文件共享:将对象序列化之后保存到文件中,再通过反序列,将对象从文件中读取出来。此方式对文件的格式没有具体的要求,可以是文件、XML、JSON等。文件共享适合在对数据同步要求不高的进程间通信,并且要妥善处理并发读/写的问题。它不适合高并发场景,无法做到进程间的及时通信
3,使用Messenger:在Messenger中放入我们需要传递的数据,实现进程间数据传递。Messenger只能传递Message对象,Messenger是一种轻量级的IPC方案,它的底层实现是AIDL
4,使用AIDL(Android Interface Definition Language):AIDL是一种IDL语言,用于生成可以在Android设备上两个进程之间进行进程间通信(IPC)的代码。如果在一个进程中(例如Activity)要调用另一个进程中(例如Service)对象的操作,就可以使用AIDL生成可序列化的参数。AIDL是IPC的一个轻量级实现。只有当你允许来自不同的客户端访问你的服务并且需要处理多线程问题时你才必须使用AIDL,其他情况下都可以选择其他方法。AIDL功能强大,支持实时通信,主要是处理多线程、多客户端并发访问的,而Messenger是单线程处理
5,Socket:主要还是应用在网络通信中
6,广播接收者(BroadcastReceiver)
7,内容提供者(ContentProvider):ContentProvider是一种设备内部共享数据的机制,APP(包括系统应用)可以通过ContentProvider将自身应用的数据对外提供共享,使得其它应用可以对这些数据实现访问和操作
其实从大类分可以分为三类:socket,文件共享,Binder(AIDL、广播、ContentProvider、Messenger的底层实现都是Binder)
十七、进程和线程的区别,如何给四大组件指定多进程
1,进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位
2,同一个进程中可以包括多个线程,并且线程共享整个进程的资源(寄存器、堆栈、上下文),一个进行至少包括一个线程
3,线程是轻两级的进程,它的创建和销毁所需要的时间比进程小很多,所有操作系统中的执行功能都是创建线程去完成的
给四大组件指定多进程:在AndroidManifest.xml中的配置对应组件的android:process属性即可,参数即指定进程名称
十八、同步与异步
同步与异步是针对应用程序与内核的交互而言的。同步过程中进程触发IO操作并等待或者轮询的去查看IO操作是否完成。异步过程中进程触发IO操作以后,直接返回,做自己的事情,IO交给内核来处理,完成后内核通知进程IO完成
同步中各个任务按顺序执行,异步中多个任务可以同时执行
十九、Android UI适配
1,选用主流分辨率来适配
2,使用限定符
3,利用weight和match
4,使用百分比布局和constraintLayou约束布局
5,第三方适配方案
二十、bitmap的三级缓存思想与如何优化bitmap
三级缓存:内存缓存、本地缓存(磁盘缓存)、网络缓存
当 App 需要引用缓存时,首先到内存缓存中读取,读取不到再到本地缓存中读取,还获取不到就到网络异步读取,读取成功之后再保存到内存和本地缓存中
LruCache 只是内存缓存的一种比较优秀且常用的缓存算法,并不是说内存缓存就一定指的是 LruCache。很多优秀的图片加载库比如 Glide 有自己独特的内存、磁盘、网络缓存技术
bitmap优化:
1,及时回收Bitmap的内存(调用Bitmap的recycle()方法)
2, 缓存通用的Bitmap对象(多次用到同一张图片的情况)
3,压缩图片:
对图片质量进行压缩:BitmapConfig的配置、bitmap.compress()
对图片尺寸进行压缩:
使用decodeFile、decodeResource、decodeStream进行解析Bitmap时,配置inDensity和inTargetDensity
使用inJustDecodeBounds预判断Bitmap的大小及使用inSampleSize设置采样率进行压缩
bitmap.compress()
使用libjpeg.so库进行压缩:libjpeg是广泛使用的开源JPEG图像库,我们可以自己编译libjpeg进行图片的压缩
4,使用异步加载
二十一、SurfaceView 与View的区别
View通过刷新来重绘视图,Android系统通过发出VSYNC信号来进行屏幕的重绘,刷新的时间间隔一般为16ms,在一些需要频繁刷新的界面,如果刷新执行很多逻辑绘制操作,就会导致刷新使用时间超过了16ms,就会导致丢帧或者卡顿,比如你更新画面的时间过长,那么你的主UI线程会被你的绘制函数阻塞,那么将无法响应按键,触屏等消息,会造成 ANR 问题。
SurfaceView虽然继承自View,但拥有独立的surface,即它不与其宿主窗口共享同一个surface,可以单独在一个线程进行绘制,并不会占用主线程的资源,这样,绘制就会比较高效,因为SurfaceView的窗口刷新的时候不需要重绘应用程序的窗口(android普通窗口的视图绘制机制是一层一层的,任何一个子元素或者是局部的刷新都会导致整个视图结构全部重绘一次,因此效率非常低下),所以可以实现复杂而高效的UI。游戏,视频播放,直播,都可以用SurfaceView来实现,SurfaceView有两个子类GLSurfaceView和VideoView。
二十二、Bundle传递数据为什么需要序列化
序列化是一种处理对象流的机制——把内存中的Java对象转换成二进制流。 对象流化后,将对象内容保存在磁盘文件中或作为数据流进行网络传输。简单来说,序列化是将对象的状态信息转换为可以存储或传输的形式的过程。
Bundle传递数据为什么需要序列化:
1,永久性保存对象,保存对象的字节序列到本地文件中
2,对象可以在网络中传输
3,对象可以在IPC之间传递
二十三、Android OOM及ANR的产生原因及处理
Android为不同类型的进程分配了不同的内存使用上限,如果程序在运行过程中出现了内存泄漏的而造成应用进程使用的内存超过了这个上限,则会产生内存泄漏,进而会导致崩溃
OOM产生原因:
1.数据库的cursor没有关闭
2.构造adapter没有使用缓存contentview
3.调用registerReceiver()后未调用unregisterReceiver()
4.未关闭InputStream/OutputStream
5.Bitmap使用后未调用recycle()
6.Context泄漏
7.static关键字
OOM处理:
1.数据库的cursor没有关闭:及时关闭
2.构造adapter没有使用缓存contentview:使用ViewHolder,实现convertView复用
3.调用registerReceiver()后未调用unregisterReceiver():及时解注册
4.未关闭InputStream/OutputStream:及时关闭
5.Bitmap使用后未调用recycle():及时回收,加载优化,参考第二十问
6.Context泄漏:除了static泄露context的方式外,内部类持有外部对象也会造成内存泄露,常见是内部线程造成的。解决:将线程的内部类改为静态内部类;在线程内部采用弱引用保存Context引用
7.static关键字:尽量避免static成员变量引用资源耗费过多的实例,比如Context;Context尽量使用Application Context;使用WeakReference代替强引用
ANR产生原因:当前的事件没有机会得到处理;当前的事件正在处理,但没有及时完成
ANR规则:
1,按键或触摸事件在5s内无响应
2,BroadcastReceiver在10s内无法处理完成
3,Service在20s内无法处理完成
ANR解决:
1,按键或触摸事件在5s内无响应:UI线程尽量只做跟UI相关的工作;耗时的工作(比如数据库操作,I/O,连接网络或者别的有可能阻碍UI线程的操作)把它放入单独的线程处理
2,BroadcastReceiver在10s内无法处理完成:BroadCastReceiver 要进行复杂操作的的时候,可以在onReceive()方法中启动一个Service来处理(并不是直接在Service中执行耗时操作,参考下一条)
3,Service在20s内无法处理完成:Service是运行在主线程的,如果需要耗时操作,可在Service中开启线程或使用IntentService
二十四、app优化:性能优化、内存优化、启动优化、图片优化、布局优化、响应优化、电量优化、网络优化、安装包优化
Android App优化
二十五、讲解一下Context
Context是一个场景,代表与操作系统的交互的一种过程。从程序的角度上来理解:Context是个抽象类,Context一共有三种类型,分别是Application、Activity和Service。这三个类虽然分别各种承担着不同的作用,但它们都属于Context的一种,而它们具体Context的功能则是由ContextImpl类去实现的。
Context数量 = Activity数量 + Service数量 + 1Application
二十六、Android与H5的交互
Android中是通过webView来加载html页面,而在网页中,JavaScript又是一个很举足轻重的脚本,所以与H5的交互一般就是指与js的交互
实现Java和js交互:
1,WebView开启JavaScript脚本执行
2,WebView设置供JavaScript调用的交互接口
3,客户端和网页端编写调用对方的代码
Android本地通过Java调用HTML页面中的JavaScript方法:
1,若调用的js方法没有返回值,则直接可以调用mWebView.loadUrl("javascript:do()");其中do是js中的方法;
2,若有返回值时我们可以调用mWebView.evaluateJavascript()方法:
js调用Android本地Java方法:
1,在Android4.2以上可以直接使用@JavascriptInterface注解来声明
2,定义完这个方法后再调用mWebView.addJavascriptInterface()方法
3,在js中调用Java的方法
二十七、混合开发
目前App的开发主要包含三种方式:原生开发、HTML5开发和混合开发
原生开发:是在Android、IOS等移动平台上利用官方提供的开发语言、开发类库、开发工具进行App开发。原生应用在应用性能上和交互体验上应该是最好的,但是原生应用的可移植性比较差,特别是一款原生的App,Android和IOS都要各自开发,同样的逻辑、界面要写两套。
HTML5开发:,是利用Web技术进行的App开发,我们知道web技术本身需要浏览器的支持才能进行展示和用户交互。主要用到的技术是HTML5、JavaScript、CSS等。H5开发的好处是可以跨平台,编写的代码可以同时在Android、IOS、Windows上进行运行。由于Web技术本身的限制,H5移动应用不能直接访问设备硬件和离线存储,所以在体验和性能上有很大的局限性。
混合开发:结合了原生和H5开发的技术,是取长补短的一种开发模式,原生代码部分利用WebView插件或者其它的框架为H5提供了一个容器,程序主要的业务实现、界面展示是利用H5相关的Web技术进行实现的。比如现在的京东、淘宝、今日头条等都是利用的混合开发模式。
混合开发的优缺点:
优点:
1、开发效率高,节约时间,同一套代码Android和IOS基本都可用
2、更新和部署比较方便,不需要每次升级都要上传到App Store进行审核了,只需要在服务器端升级就可以
3、代码维护方便、版本更新快,降低产品成本
缺点是:
1、由于不能直接操控硬件有些方面性能不是很好
2、另外有技术比较新版本的兼容性比较差,还有就是即懂原生开发又懂H5开发的高端人才难找
混合App开发是未来的趋势,目前市面上的混合开发框架有:React Native,Weex,PhoneGap ,Ionic ,Hbuilder,appcan,ApiCloud等
二十八、RxJava
RxJava是一个在 Java VM 上使用可观测的序列来组成异步的、基于事件的程序的库(一个支持异步的链式编程库)
RxJava 的优势是简洁,但它的简洁的与众不同之处在于,随着程序逻辑变得越来越复杂,它依然能够保持简洁
RxJava 的异步实现,是通过一种扩展的观察者模式来实现的
RxJava 的基本实现:创建 Observer、创建 Observable、Subscribe (订阅)
应用场景:与Retrofit联用、Rx类库的使用(如基于RxJava的开源类库Rxpermissions、RxBinding以及RxBus)、其它用到异步的地方
二十九、MVP,MVC,MVVM
MVC:
视图层(View) :对应于xml布局文件和java代码动态view部分
控制层(Controller) :MVC中Android的控制层是由Activity来承担的,Activity本来主要是作为初始化页面,展示数据的操作,但是因为XML视图功能太弱,所以Activity既要负责视图的显示又要加入控制逻辑,承担的功能过多
模型层(Model) :针对业务模型,建立的数据结构和相关的类,它主要负责网络请求,数据库处理,I/O的操作
工作原理:当用户出发事件的时候,view层会发送指令到controller层,接着controller去通知model层更新数据,model层更新完数据以后直接显示在view层上,这就是MVC的工作原理
MVP:
MVP跟MVC很相像,唯一的差别是Model和View之间不进行通讯,都是通过Presenter完成
工作原理:view层发出的事件传递到presenter层中,presenter层去操作model层,并且将数据返回给view层
MVVM:
MVVM通过DataBinding双向绑定的机制,实现数据和UI内容,只要想改其中一方,另一方都能够及时更新的一种设计理念,这样就省去了很多在View层中写很多case的情况,只需要改变数据就行
工作原理:presenter层换成了viewmodel层,view层和viewmodel层是相互绑定的关系,当更新viewmodel层的数据的时候,view层会相应的更新ui
三十、session与cookie的区别
cookie是服务器在本地机器上存储的小段文本并随每一个请求发送至同一服务器,是在客户端保持状态的方案。
在网站中,http请求是无状态的。也就是说即使第一次和服务器连接后并且登录成功后,第二次请求服务器依然不能知道当前请求是哪个用户。cookie的出现就是为了解决这个问题。第一次登录后服务器返回一些数据(cookie)给浏览器,然后浏览器保存在本地,当该用户发送第二次请求的时候,就会自动的把上次请求存储的cookie数据自动的携带给服务器,服务器通过浏览器携带的数据就能判断当前用户是哪个了。cookie存储的数据量有限,不同的浏览器有不同的存储大小,但一般不超过4KB。因此使用cookie只能存储一些小量的数据。
session和cookie的作用有点类似,都是为了存储用户相关的信息。不同的是,cookie是存储在本地浏览器,而session存储在服务器。存储在服务器的数据会更加的安全,不容易被窃取。但存储在服务器也有一定的弊端,就是会占用服务器的资源,但现在服务器已经发展至今,一些session信息还是绰绰有余的。
区别:
1,存储数据量方面:session 能够存储任意的 java 对象,cookie 只能存储 String 类型的对象且存储量小
2,一个在客户端一个在服务端。因Cookie在客户端所以不是十分安全。
3,Session过多时会消耗服务器资源,大型网站会有专门Session服务器,Cookie存在客户端没问题
4,域的支持范围不一样,比方说a.com的Cookie在a.com下都能用,而www.a.com的Session在api.a.com下都不能用,解决这个问题的办法是JSONP或者跨域资源共享。
三十一、如何取消一个网络请求
1,使用 httpClient时调用httpClient.getConnectionManager().shutdown()
2,通过设置tag取消
3,使用Retrofit2时通过调用Call的cancel()方法
4,使用Retrofit2+Rxjava2通过调用 Disposable的dispose()方法
三十二、如何实现一个推送,极光推送原理
1,轮询法:这种方法最简单,Client每过一段时间向Server请求一次数据。优缺点很明显,优点是实现简单;缺点是间隔时间不好控制,并且消耗大(电量、流量)
2,长连接法:Client使用socket连接Server,并且保持socket连接,Server随时可以通过这个socket发送数据给Client。优点:最有效,客户端设备消耗比第一种小(设备应该从系统层对socket的长连接做优化,socket链接维护成本从客户端来讲应该是小于频繁的http请求的);缺点:服务端压力大,每一个设备都需要一个socket连接
极光推送原理:
服务端设计:针对 C10K 问题,极光采用了多消息循环、异步非阻塞的模型,在一台双核、24G内存的服务器上,实现峰值维持超过300万个长连接
客户端设计:
为了长时间保持外网IP不变,需要客户端定期发送心跳给运营商,以便刷新NAT列表
AlarmManager 是 Android 系统封装的用于管理 RTC 的模块,RTC (Real Time Clock) 是一个独立的硬件时钟,可以在 CPU 休眠时正常运行,在预设的时间到达时,通过中断唤醒 CPU。这意味着,如果我们用 AlarmManager 来定时执行任务,CPU 可以正常的休眠,只有在需要运行任务时醒来一段很短的时间。极光推送的 Android SDK 就是基于这种技术实现的。
三十三、如何进行单元测试
单元测试又称为模块测试,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。
android中的单元测试基于JUnit,可分为本地测试和instrumented测试
在android测试框架中,常用的有以下几个框架和工具类:JUnit4、AndroidJUnitRunner、Mockito、Espresso
基本步骤:添加单元测试支持(默认提供),添加单元测试类,运行单元测试,查看运行结果
三十四、进程的优先级、进程保活(不死进程)
进程优先级(从高到低):前台进程:Foreground process、可见进程:Visible process、服务进程:Service process、后台进程:Background process、空进程:Empty process
Android系统会在内存不足的时候去将进程杀死,俗称Low Memory Killer,它是基于Linux内核的 OOM Killer(Out-Of-Memory killer)机制,内存不足时,优先杀oom_adj值(不是进程优先级)高的进程
常见的保活拉起方式:
1,不同的app进程,用广播相互唤醒
2,利用系统Service机制拉活(前台服务)
3,Native进程拉起
4,双进程守护
5,JobScheduler
三十五、JNI/NDK基本使用,java如何调用C语言的方法
JNI:Java Native Interface(Java 本地编程接口),一套编程规范,它提供了若干的 API 实现了 Java 和其他语言的通信(主要是 C/C++)。Java与本地代码通过JNI可以相互调用。Java 通过 C/C++ 使用本地的代码的一个关键性原因在于 C/C++ 代码的高效性。
NDK全称是Native Development Kit,NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。
NDK集成了交叉编译器(交叉编译器需要UNIX或LINUX系统环境),并提供了相应的mk文件隔离CPU、平台、ABI等差异,开发人员只需要简单修改mk文件(指出“哪些文件需要编译”、“编译特性要求”等),就可以创建出so。
使用:添加NDK支持,按提示创建工程,根据需求使用NDK(待完善)
三十六、经典第三方框架原理
比较经典的有:OkHttp、Retrofit、Glide、ButterKnife、RxJava等,涉及的框架较多,需查找对应文章
三十七、AIDL理解
具体概念请参考第十六问
service 根据AIDL 生成相应的 java interface声明文件和实现impl文件,并且需要定义新的service将这些实现暴露给用户,即提供onbind方法。
client,根据AIDL中的声明,通过相应的Intent,bind到对应的service。获得service的stub,请求相应的功能。
service 和client 运行在不同的进程中。client端通过stub向真正的service发起结果请求。 系统通过stub隐藏了内部的真正的通信机制,只返回相应的service的执行结果。
AIDL的核心是Binder,我们通过AIDL文件来描述接口,来得到一个封装好的IBinder代理,来实现接口的远程调用。
三十八、Binder机制原理
IPC机制:
Binder架构和运行机制:
Binder框架定义了四个角色:Server,Client,ServiceManager以及Binder驱动。其中Server,Client,SMgr运行于用户空间,驱动运行于内核空间。
使用服务的具体执行过程:
1,client通过获得一个server的代理接口,对server进行调用
2,代理接口中定义的方法与server中定义的方法时一一对应的
3,client调用某个代理接口中的方法时,代理接口的方法会将client传递的参数打包成Parcel对象
4,代理接口将Parcel发送给内核中的binder driver
5,server会读取binder driver中的请求数据,如果是发送给自己的,解包Parcel对象,处理并将结果返回
6,整个的调用过程是一个同步过程,在server处理的时候,client会block住。因此client调用过程不应在主线程。
Binder数据拷贝只需要一次,而管道、消息队列、Socket都需要2次,共享内存方式一次内存拷贝都不需要,但实现方式又比较复杂。
Binder通信采用C/S架构,安全性好,简单高效。
三十九、ClassLoader
ClassLoader翻译过来就是类加载器,是用来加载 Class 的。它负责将 Class 的字节码形式转换成内存形式的 Class 对象。字节码可以来自于磁盘文件 *.class,也可以是 jar 包里的 *.class,也可以来自远程服务器提供的字节流。
JVM 启动的时候,并不会一次性加载所有的class文件,而是根据需要去动态加载。
JVM 中内置了三个重要的 ClassLoader,分别是 BootstrapClassLoader、ExtensionClassLoader 和 AppClassLoader。
BootstrapClassLoader 负责加载 JVM 运行时核心类,这些类位于 JAVA_HOME/lib/rt.jar 文件中,我们常用内置库 java.xxx.* 都在里面,比如 java.util.*、java.io.*、java.nio.*、java.lang.* 等等。这个 ClassLoader 比较特殊,它是由 C 代码实现的,我们将它称之为「根加载器」。
ExtensionClassLoader 负责加载 JVM 扩展类,比如 swing 系列、内置的 js 引擎、xml 解析器 等等,这些库名通常以 javax 开头,它们的 jar 包位于 JAVA_HOME/lib/ext/*.jar 中,有很多 jar 包。
AppClassLoader 才是直接面向我们用户的加载器,它会加载 Classpath 环境变量里定义的路径中的 jar 包和目录。我们自己编写的代码以及使用的第三方 jar 包通常都是由它来加载的。
ClassLoader采用双亲代理模型,加载一个类时,首先BootStrap进行寻找,找不到再由Extension ClassLoader寻找,最后才是App ClassLoader。
Android中的classLoader具有两个特点:
类加载共享:当一个class文件被任何一个ClassLoader加载过,就不会再被其他ClassLoader加载。
类加载隔离:不同ClassLoader加载的class文件肯定不是一个。
四十、静态代理跟动态代理
代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。
静态代理类优缺点
优点:
代理使客户端不需要知道实现类是什么,怎么做的,而客户端只需知道代理即可(解耦合)
缺点:
1,代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
2,代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。即静态代理类只能为特定的接口(Service)服务。如想要为多个接口服务则需要建立很多个代理类。
动态代理:在运行时,通过反射机制实现动态代理,并且能够代理各种类型的对象
动态代理优点:接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。而且动态代理的应用使我们的类职责更加单一,复用性更强。
四十一、AMS,WMS,PMS
AMS(ActivityManagerService)是Android中最核心的服务,主要负责系统中四大组件的启动、切换、调度及应用进程的管理和调度等工作,其职责与操作系统中的进程管理和调度模块相类似
WMS(WindowManagerService)是系统服务的一部分,由SystemService启动,发生异常时自动重启,直到系统关机时才能退出。主要有两方面的功能:
全局的窗口管理(Output):应用程序的显示请求在SurfaceFlinger和WMS的协助下有序地输出给物理屏幕或其他显示设备。
全局的事件管理派发(Input):事件源包括键盘、触摸屏、鼠标、轨迹球(TraceBoll)等。
PMS(PackageManagerService):Android Package管理服务,其主要管理Android系统中App的安装包等信息
(深入理解请查找相关资料)
四十二、组件化原理,模块化机制
组件化:将一个app的代码拆分成几份独立的组件,组件之间是低耦合的,可以独立编译打包;也可以将组件打包到一个apk中。
在开发中不断地增加新特性意味着更长的build时间和更长的增量build时间。在工程较大的项目中,build时间要占到10%~15%的工作时间。把Application分成多个modules可以解决这个问题,也就是组件化概念
组件层的模块都依赖于基础层,从而产生第三者联系,这种第三者联系最终会编译在APP Module中,那时将不会有这种隔阂,那么其中的Base Module就是跨越组件化层级的关键,也是模块间信息交流的基础。
模块化:
Android studio支持了多个module开发时,提出的模块化概念。
具体实践:把常用的功能、控件、基础类、第三方库、权限等公共部分抽离封装,把业务拆分成N个模块进行独立(module)的管理。而所有的业务组件都依赖于封装的基础库,业务组件之间不做依赖,这样的目的是为了让每个业务模块能单独运行。
模块化的特点是:模块之间解耦,可以独立管理。
四十三、热更新与插件化,它们的实现核心原理是什么,它们的区别,如何进行dex替换
热更新:无须下载新apk即可完成对app线上bug的修复
插件化:App 的部分功能模块在打包时并不以传统方式打包进 apk 文件中,而是以另一种形式封装进 apk内部,或者放在网络上适时下载,在需要的时候动态对这些功能模块进行加载,称之为插件化。
热更新原理:基于ClassLoader 的 dex 文件替换。主要为以tinker为代表的multidex类加载法和以阿里andfix为代表的底层替换法,而阿里sophix为了提高热修复的成功率同时采用了上述两种方案,并在兼容性上进行了一定的优化。
插件化原理:通过自定义 ClassLoader 来加载新的 dex 文件,从而让程序原本没有的类可以被使用,即动态加载
热更新和插件化的区别:
1,插件化的内容在原 App 中没有,而热更新是原 App 中的内容做了改动
2,插件化在代码中有固定的入口,而热更新则可能改变任意位置的代码
dex替换:以Tinker为例,Tinker 热修复的主要原理就是通过对比旧 APK 的 dex 文件与新 APK 的 dex 文件,生成补丁包,然后在 APP 中通过补丁包与旧 APK 的 dex 文件合成新的 dex 文件。流程如下图所示:
四十四、Hook以及插桩技术
Hook:翻译过来是钩子的意思,Hook技术,就是在事件传送到终点前截获并监控事件的传输,像个钩子钩上事件一样,并且能够在钩上事件时,处理一些自己特定的事件。
Hook 的选择点:静态变量和单例,因为一旦创建对象,它们不容易变化,非常容易定位。
Hook 过程:
寻找 Hook 点,原则是静态变量或者单例对象,尽量 Hook public 的对象和方法
选择合适的代理方式,如果是接口可以用动态代理
用代理对象替换原始对象
插桩技术:在保证被测程序原有逻辑完整性的基础上在程序中插入一些探针(又称为“探测仪”,本质上就是进行信息采集的代码段,可以是赋值语句或采集覆盖信息的函数调用),通过探针的执行并抛出程序运行的特征数据,通过对这些数据的分析,可以获得程序的控制流和数据流信息,进而得到逻辑覆盖等动态信息,从而实现测试目的的方法。
Hook与插桩的区别:
hook是通过代理等方式,可以随时取消这个hook
插桩是通过字节编码的方式,把修改后的方法重新编译成字节,然后比对的方式来修改源文件
四十五、逆向技术
逆向技术,狭义的讲就是apk反编译,主要用到了三个工具:dex2jar,jd-jui,APKTool,也有第三方工具,从下图可基本了解使用流程。
四十六、代码管理工具,为什么会产生代码冲突,该如何解决
常见代码管理工具:SVN是集中式管理,git是分布式管理
为什么产生代码冲突:两个用户修改了同一个文件的同一块区域,git不知道应该以哪份为准,就会报告内容冲突。
如何解决:参考文章