Android面试题
Android面试题包括Android基础,还有一些源码级别的、原理这些等。所以想去大公司面试,一定要多看看源码和实现方式,常用框架可以试试自己能不能手写实现一下,锻炼一下自己。
(一)Android基础知识点
1、四大组件是什么
1)Activity:用户可操作的可视化界面,为用户提供一个完成操作指令的窗口。一个Activity通常是一个单独的屏幕,Activity通过Intent来进行通信。Android中会维持一个Activity Stack,当一个新Activity创建时,它就会放到栈顶,这个Activity就处于运行状态。
2)Service:服务,运行在手机后台,适合执行不需和用户交互且还需长期运行的任务。
3)ContentProvider:内容提供者,使一个应用程序的指定数据集提供给其他应用程序,其他应用可通过ContentResolver类从该内容提供者中获取或存入数据。它提供了一种跨进程数据共享的方式,当数据被修改后,ContentResolver接口的notifyChange函数通知那些注册监控特定URI的ContentObserver对象。
如果ContentProvider和调用者在同一进程中,ContentProvider的方法(query/insert/update/delete等)和调用者在同一线程中;如果ContentProvider和调用者不在同一进程,ContentProvider方法会运行在它自身进程的一个Binder线程中。
4)Broadcast Receiver: 广播接收者,运用在应用程序间传输信息,可以使用广播接收器来让应用对一个外部事件做出响应。
2、四大组件的生命周期和简单用法
1)Activity:onCreate()->onStart()->onResume()->onPause()->onStop()->onDestory()
onCreate():为Activity设置布局,此时界面还不可见;
onStart(): Activity可见但还不能与用户交互,不能获得焦点
onRestart(): 重新启动Activity时被回调
onResume(): Activity可见且可与用户进行交互
onPause(): 当前Activity暂停,不可与用户交互,但还可见。在新Activity启动前被系统调用保存现有的Activity中的持久数据、停止动画等。
onStop(): 当Activity被新的Activity覆盖不可见时被系统调用
onDestory(): 当Activity被系统销毁杀掉或是由于内存不足时调用
2)Service
a) onBind方式绑定的:onCreate->onBind->onUnBind->onDestory(不管调用bindService几次,onCreate只会调用一次,onStart不会被调用,建立连接后,service会一直运行,直到调用unBindService或是之前调用的bindService的Context不存在了,系统会自动停止Service,对应的onDestory会被调用)
b) startService启动的:onCreate->onStartCommand->onDestory(start多次,onCreate只会被调用一次,onStart会调用多次,该service会在后台运行,直至被调用stopService或是stopSelf)
c) 又被启动又被绑定的服务,不管如何调用onCreate()只被调用一次,startService调用多少次,onStart就会被调用多少次,而unbindService不会停止服务,必须调用stopService或是stopSelf来停止服务。必须unbindService和stopService(stopSelf)同时都调用了才会停止服务。
3)BroadcastReceiver
a) 动态注册:存活周期是在Context.registerReceiver和Context.unregisterReceiver之间,BroadcastReceiver每次收到广播都是使用注册传入的对象处理的。
b) 静态注册:进程在的情况下,receiver会正常收到广播,调用onReceive方法;生命周期只存活在onReceive函数中,此方法结束,BroadcastReceiver就销毁了。onReceive()只有十几秒存活时间,在onReceive()内操作超过10S,就会报ANR。
进程不存在的情况,广播相应的进程会被拉活,Application.onCreate会被调用,再调用onReceive。
4)ContentProvider:应该和应用的生命周期一样,它属于系统应用,应用启动时,它会跟着初始化,应用关闭或被杀,它会跟着结束。
3、Activity之间的通信方式
1)通过Intent方式传递参数跳转
2)通过广播方式
3)通过接口回调方式
4)借助类的静态变量或全局变量
5)借助SharedPreference或是外部存储,如数据库或本地文件
4、Activity各种情况下的生命周期
1) 两个Activity(A->B)切换(B正常的Activity)的生命周期:onPause(A)->onCreate(B)->onStart(B)->onResume(B)->oStop(A)
这时如果按回退键回退到A onPause(B)->onRestart(A)->onStart(A)->onResume(A)->oStop(B)
如果在切换到B后调用了A.finish(),则会走到onDestory(A),这时点回退键会退出应用
2) 两个Activity(A->B)切换(B透明主题的Activity或是Dialog风格的Acivity)的生命周期:onPause(A)->onCreate(B)->onStart(B)->onResume(B)
这时如果回退到A onPause(B)->onResume(A)->oStop(B)->onDestory(B)
3) Activity(A)启动后点击Home键再回到应用的生命周期:onPause(A)->oStop(A)->onRestart(A)->onStart(A)->onResume(A)
5、横竖屏切换的时候,Activity 各种情况下的生命周期
1)切换横屏时:onSaveInstanceState->onPause->onStop->onDestory->onCreate->onStart->onRestoreInstanceState->onResume
2) 切换竖屏时:会打印两次相同的log
onSaveInstanceState->onPause->onStop->onDestory->onCreate->onStart->onRestoreInstanceState->onResume->onSaveInstanceState->onPause->onStop->onDestory->onCreate->onStart->onRestoreInstanceState->onResume
3) 如果在AndroidMainfest.xml中修改该Activity的属性,添加android:configChanges="orientation"
横竖屏切换,打印的log一样,同1)
4) 如果AndroidMainfest.xml中该Activity中的android:configChanges="orientation|keyboardHidden",则只会打印
onConfigurationChanged->
6、Activity与Fragment之间生命周期比较
Fragment生命周期:onAttach->onCreate->onCreateView->onActivityCreated->onStart->onResume->onPause->onStop->onDestoryView->onDestory->onDetach
切换到该Fragment:onAttach->onCreate->onCreateView->onActivityCreated->onStart->onResume
按下Power键:onPause->onSaveInstanceState->onStop
点亮屏幕解锁:onStart->onRestoreInstanceState->onResume
切换到其他Fragment: onPause->onStop->onDestoryView
切回到该Fragment: onCreateView->onActivityCreated->onStart->onResume
退出应用:onPause->onStop->onDestoryView->onDestory->onDetach
7、Activity上有Dialog的时候按Home键时的生命周期
AlertDialog并不会影响Activity的生命周期,按Home键后才会使Activity走onPause->onStop,AlertDialog只是一个组件,并不会使Activity进入后台。
8、两个Activity 之间跳转时必然会执行的是哪几个方法?
前一个Activity的onPause,后一个Activity的onResume
9、前台切换到后台,然后再回到前台,Activity生命周期回调方法。弹出Dialog,生命值周期回调方法。
1)前台切换到后台,会执行onPause->onStop,再回到前台,会执行onRestart->onStart->onResume
2) 弹出Dialog,并不会影响Activity生命周期
10、Activity的四种启动模式对比
1)standard:标准启动模式(默认),每启动一次Activity,都会创建一个实例,即使从ActivityA startActivity ActivityA,也会再次创建A的实例放于栈顶,当回退时,回到上一个ActivityA的实例。
2) singleTop:栈顶复用模式,每次启动Activity,如果待启动的Activity位于栈顶,则不会重新创建Activity的实例,即不会走onCreate->onStart,会直接进入Activity的onPause->onNewIntent->onResume方法
3) singleInstance: 单一实例模式,整个手机操作系统里只有一个该Activity实例存在,没有其他Actvity,后续请求均不会创建新的Activity。若task中存在实例,执行实例的onNewIntent()。应用场景:闹钟、浏览器、电话
4) singleTask:栈内复用,启动的Activity如果在指定的taskAffinity的task栈中存在相应的实例,则会把它上面的Activity都出栈,直到当前Activity实例位于栈顶,执行相应的onNewIntent()方法。如果指定的task不存在,创建指定的taskAffinity的task,taskAffinity的作用,进入指写taskAffinity的task,如果指定的task存在,将task移到前台,如果指定的task不存在,创建指定的taskAffinity的task. 应用场景:应用的主页面
11、Activity状态保存于恢复
Activity被主动回收时,如按下Back键,系统不会保存它的状态,只有被动回收时,虽然这个Activity实例已被销毁,但系统在新建一个Activity实例时,会带上先前被回收Activity的信息。在当前Activity被销毁前调用onSaveInstanceState(onPause和onStop之间保存),重新创建Activity后会在onCreate后调用onRestoreInstanceState(onStart和onResume之间被调用),它们的参数Bundle用来数据保存和读取的。
保存View状态有两个前提:View的子类必须实现了onSaveInstanceState; 必须要设定Id,这个ID作为Bundle的Key
12、fragment各种情况下的生命周期
正常情况下的生命周期:onAttach->onCreate->onCreateView->onActivityCreated->onStart->onResume->onPause->onStop->onDestoryView->onDestory->onDetach
1)Fragment在Activity中replace onPause(旧)->onAttach->onCreate->onCreateView->onActivityCreated->onStart->onResume->onStop(旧)->onDestoryView(旧)
如果添加到backStack中,调用remove()方法fragment的方法会走到onDestoryView,但不会执行onDetach(),即fragment本身的实例是存在的,成员变量也存在,但是view被销毁了。如果新替换的Fragment已在BackStack中,则不会执行onAttach->onCreate
13、Fragment状态保存onSaveInstanceState是哪个类的方法,在什么情况下使用?
在对应的FragmentActivity.onSaveInstanceState方法会调用FragmentController.saveAllState,其中会对mActive中各个Fragment的实例状态和View状态分别进行保存.当Activity在做状态保存和恢复的时候, 在它其中的fragment自然也需要做状态保存和恢复.
参考文章:Android Fragment使用(三) Activity, Fragment, WebView的状态保存和恢复
14、Fragment.startActivityForResult是和FragmentActivity的startActivityForResult?
如果希望在Fragment的onActivityResult接收数据,就要调用Fragment.startActivityForResult, 而不是Fragment.getActivity().startActivityForResult。Fragment.startActivityForResult->FragmentActivitymHost.HostCallbacks.onStartActivityFromFragment->FragmentActivity.startActivityFromFragment。如果request=-1则直接调用FragmentActivity.startActivityForResult,它会重新计算requestCode,使其大于0xfffff。
参考文章:彻底搞懂startActivityForResult在FragmentActivity和Fragment中的异同
15、如何实现Fragment的滑动?
ViewPager+FragmentPagerAdapter+List
16、fragment之间传递数据的方式?
1)在相应的fragment中编写方法,在需要回调的fragment里获取对应的Fragment实例,调用相应的方法;
2)采用接口回调的方式进行数据传递;
a) 在Fragment1中创建一个接口及接口对应的set方法; b) 在Fragment1中调用接口的方法;c)在Fragment2中实现该接口;
3)利用第三方开源框架EventBus
17、service和activity怎么进行数据交互?
1)通过bindService启动服务,可以在ServiceConnection的onServiceConnected中获取到Service的实例,这样就可以调用service的方法,如果service想调用activity的方法,可以在service中定义接口类及相应的set方法,在activity中实现相应的接口,这样service就可以回调接口言法;
2)通过广播方式
18、说说ContentProvider、ContentResolver、ContentObserver 之间的关系
ContentProvider实现各个应用程序间数据共享,用来提供内容给别的应用操作。如联系人应用中就使用了ContentProvider,可以在自己应用中读取和修改联系人信息,不过需要获取相应的权限。它也只是一个中间件,真正的数据源是文件或SQLite等。
ContentResolver内容解析者,用于获取内容提供者提供的数据,通过ContentResolver.notifyChange(uri)发出消息
ContentObserver内容监听者,可以监听数据的改变状态,观察特定Uri引起的数据库变化,继而做一些相应的处理,类似于数据库中的触发器,当ContentObserver所观察的Uri发生变化时,便会触发它。
19、请描述一下广播BroadcastReceiver的理解
BroadcastReceiver是一种全局监听器,用来实现系统中不同组件之间的通信。有时候也会用来作为传输少量而且发送频率低的数据,但是如果数据的发送频率比较高或者数量比较大就不建议用广播接收者来接收了,因为这样的效率很不好,因为BroadcastReceiver接收数据的开销还是比较大的。
20、广播的分类
1)普通广播:完全异步的,可以在同一时刻(逻辑上)被所有接收者接收到,消息传递的效率比较高,并且无法中断广播的传播。
2)有序广播:发送有序广播后,广播接收者将按预先声明的优先级依次接收Broadcast。优先级高的优先接收到广播,而在其onReceiver()执行过程中,广播不会传播到下一个接收者,此时当前的广播接收者可以abortBroadcast()来终止广播继续向下传播,也可以将intent中的数据进行修改设置,然后将其传播到下一个广播接收者。 sendOrderedBroadcast(intent, null);//发送有序广播
3)粘性广播:sendStickyBroadcast()来发送该类型的广播信息,这种的广播的最大特点是,当粘性广播发送后,最后的一个粘性广播会滞留在操作系统中。如果在粘性广播发送后的一段时间里,如果有新的符合广播的动态注册的广播接收者注册,将会收到这个广播消息,虽然这个广播是在广播接收者注册之前发送的,另外一点,对于静态注册的广播接收者来说,这个等同于普通广播。
21、广播使用的方式和场景
1)App全局监听:在AndroidManifest中静态注册的广播接收器,一般我们在收到该消息后,需要做一些相应的动作,而这些动作与当前App的组件,比如Activity或者Service的是否运行无关,比如我们在集成第三方Push SDK时,一般都会添加一个静态注册的BroadcastReceiver来监听Push消息,当有Push消息过来时,会在后台做一些网络请求或者发送通知等等。
2)组件局部监听:这种主要是在Activity或者Service中使用registerReceiver()动态注册的广播接收器,因为当我们收到一些特定的消息,比如网络连接发生变化时,我们可能需要在当前Activity页面给用户一些UI上的提示,或者将Service中的网络请求任务暂停。所以这种动态注册的广播接收器适合特定组件的特定消息处理。
22、在manifest 和代码中如何注册和使用BroadcastReceiver?
1)mainfest中注册:静态注册的广播接收者就是一个常驻在系统中的全局监听器,也就是说如果你应用中配置了一个静态的BroadcastReceiver,而且你安装了应用而无论应用是否处于运行状态,广播接收者都是已经常驻在系统中了。
2) 动态注册:动态注册的广播接收者只有执行了registerReceiver(receiver, filter)才会开始监听广播消息,并对广播消息作为相应的处理。
IntentFilter fiter = new IntentFilter("com.smilexie.test.intent.mybroadcastreceiver");
MyBroadcastReceiver receiver = new MyBroadcastReceiver();
registerReceiver(receiver, filter);
//撤销广播接受者的动态注册
unregisterReceiver(receiver);
23、本地广播和全局广播有什么差别?
1)LocalBroadcastReceiver仅在自己的应用内发送接收广播,也就是只有自己的应用能收到,数据更加安全。广播只在这个程序里,而且效率更高。只能动态注册,在发送和注册的时候采用LocalBroadcastManager的sendBroadcast方法和registerReceiver方法。
2)全局广播:发送的广播事件可被其他应用程序获取,也能响应其他应用程序发送的广播事件(可以通过 exported–是否监听其他应用程序发送的广播 在清单文件中控制) 全局广播既可以动态注册,也可以静态注册。
24、AlertDialog,popupWindow,Activity区别
(1)Popupwindow在显示之前一定要设置宽高,Dialog无此限制。
(2)Popupwindow默认不会响应物理键盘的back,除非显示设置了popup.setFocusable(true);而在点击back的时候,Dialog会消失。
(3)Popupwindow不会给页面其他的部分添加蒙层,而Dialog会。
(4)Popupwindow没有标题,Dialog默认有标题,可以通过dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);取消标题
(5)二者显示的时候都要设置Gravity。如果不设置,Dialog默认是Gravity.CENTER。
(6)二者都有默认的背景,都可以通过setBackgroundDrawable(new ColorDrawable(android.R.color.transparent));去掉。
(7)Popupwindow弹出后,取得了用户操作的响应处理权限,使得其他UI控件不被触发。而AlertDialog弹出后,点击背景,AlertDialog会消失。
25、Application 和 Activity 的 Context 对象的区别
1)Application Context是伴随应用生命周期;不可以showDialog, startActivity, LayoutInflation
可以startService\BindService\sendBroadcast\registerBroadcast\load Resource values
2)Activity Context指生命周期只与当前Activity有关,而Activity Context这些操作都可以,即凡是跟UI相关的,都得用Activity做为Context来处理。
一个应用Context的数量=Activity数量+Service数量+1(Application数量)
26、Android属性动画特性
1)可以对非View的对象进行动画操作;
2)补间动画只能实现移动、缩放、旋转、淡入淡出,而无法扩展,如对背景颜色进行改变等,但是属性动画就可以;
3)属性动画真正改变了对象的属性
27、如何导入外部数据库?
android系统下数据库应该存放在/data/data/com..(package name)/目录下,通过FileInputStream读取原数据库,再用FileOutputStream把读取到的东西写入到那个目录下。在原数据库放在项目源码的/res/raw/目录下,建立一个DBManager类,
28、谈谈对接口与回调的理解
原理:先创建一个对象,再创建一个控制器对象,将回调对象需要被调用的方法告诉控制器对象,控制器对象负责检查某个场景是否出现或某个条件是否满足,当满足时,自动调用回调对象方法。
29、介绍下SurfView
SurfaceView拥有独立的绘图表面Surface,它不与其宿主窗口共享一个Surface,由于拥有独立的Surface,所以SurfaceView的UI就可以在一个单独的线程中进行绘制。绘图流程:在绘图表面的基础上创建一块画布,即获得一个Canvas对象;利用Canvas类提供的绘图接口在前面获得的画布上绘制任意的UI;将已填充好了的UI数据的画布缓冲区提交给SurfaceFlinger服务,以便SurfaceFlinger服务可以将它合成到屏幕上。
优点:1)可以在非UI线程中绘制;2)SurfaceView的频率可以操作60FPS; 3) 在要求实时性比较高的游戏开发中,view的onDraw一般满足不了要求,这时只能用SurfaceView
使用步骤:
1)继承自SurfaceView; 2)初始化时,拿到SurfaceHolder,给SurfaceHolder设置Callback; 3) 在Callback中写几个回调方法surfaceCreated\surfaceChanged\surfaceDestoryed;4)在surfaceCreated中起一个线程,在线程中拿到SurfaceHolder去锁定Canvas进行绘图。
RecycleView的使用
序列化的作用,以及Android两种序列化的区别
差值器
实现Interpolator接口,根据时间来计算当前属性需要改变的百分比值。设置值的变化趋势,SDK中包含了匀速插值器LinearInterpolator、加速插值器、减速插值器DecelerateInterpolator、先加速再减速AccelerateDecelerateInterpolator、弹
估值器
根据属性变化的百分比值来计算改变后的属性值。需要实现TypeEvaluatior接口
Android中数据存储方式
1)SharedPreferences:用来存储一些简单的配置信息,如登录的用户名、密码,采用map数据结构存储。写入的时候先调用edit()使其处于编辑态,再修改数据,最后用commit()来提交修改的数据。采用XML格式将数据存储到设备上/data/data/
2) 数据库SQLite
3)SD卡,本地存储:openFileInput()和openFileOutput()
4)使用ContentProvider存储数据:可向其他应用共享数据,
(二)Android源码相关分析
1、Android属性动画实现原理
工作原理:在一定时间间隔内,通过不断对值进行改变,并不断将该值赋给对象的属性,从而实现该对象在该属性上的动画效果。
1)ValueAnimator:通过不断控制值的变化(初始值->结束值),将值手动赋值给对象的属性,再不断调用View的invalidate()方法,去不断onDraw重绘view,达到动画的效果。
主要的三种方法:
a) ValueAnimator.ofInt(int values):估值器是整型估值器IntEaluator
b) ValueAnimator.ofFloat(float values):估值器是浮点型估值器FloatEaluator
c) ValueAnimator.ofObject(ObjectEvaluator, start, end):将初始值以对象的形式过渡到结束值,通过操作对象实现动画效果,需要实现Interpolator接口,自定义估值器
估值器TypeEvalutor,设置动画如何从初始值过渡到结束值的逻辑。插值器(Interpolator)决定值的变化模式(匀速、加速等);估值器(TypeEvalutor)决定值的具体变化数值。
// 自定义估值器,需要实现TypeEvaluator接口
public class ObjectEvaluator implements TypeEvaluator{
// 复写evaluate(),在evaluate()里写入对象动画过渡的逻辑
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
// 参数说明
// fraction:表示动画完成度(根据它来计算当前动画的值)
// startValue、endValue:动画的初始值和结束值
... // 写入对象动画过渡的逻辑
return value;
// 返回对象动画过渡的逻辑计算后的值
}
2) ObjectAnimator:直接对对象的属性值进行改变操作,从而实现动画效果
ObjectAnimator继承自ValueAnimator类,底层的动画实现机制还是基本值的改变。它是不断控制值的变化,再不断自动赋给对象的属性,从而实现动画效果。这里的自动赋值,是通过调用对象属性的set/get方法进行自动赋值,属性动画初始值如果有就直接取,没有则调用属性的get()方法获取,当值更新变化时,通过属性的set()方法进行赋值。每次赋值都是调用view的postInvalidate()/invalidate()方法不断刷新视图(实际调用了onDraw()方法进行了重绘视图)。
//Object 需要操作的对象; propertyName 需要操作的对象的属性; values动画初始值&结束值,
//如果是两个值,则从a->b值过渡,如果是三值,则从a->b->c
ObjectAnimator animator = ObjectAnimator.ofFloat(Object object, String propertyName, float ...values);
如果采用ObjectAnimator类实现动画,操作的对象的属性必须有get()和set()方法。
其他用法:
1)AnimatorSet组合动画
AnimatorSet.play(Animator anim) :播放当前动画
AnimatorSet.after(long delay) :将现有动画延迟x毫秒后执行
AnimatorSet.with(Animator anim) :将现有动画和传入的动画同时执行
AnimatorSet.after(Animator anim) :将现有动画插入到传入的动画之后执行
AnimatorSet.before(Animator anim) : 将现有动画插入到传入的动画之前执行
2) ViewPropertyAnimator直接对属性操作,View.animate()返回的是一个ViewPropertyAnimator对象,之后的调用方法都是基于该对象的操作,调用每个方法返回值都是它自身的实例
View.animate().alpha(0f).x(500).y(500).setDuration(500).setInterpolator()
3)设置动画监听
Animation.addListener(new AnimatorListener() {
@Override
public void onAnimationStart(Animation animation) {
//动画开始时执行
}
@Override
public void onAnimationRepeat(Animation animation) {
//动画重复时执行
}
@Override
public void onAnimationCancel()(Animation animation) {
//动画取消时执行
}
@Override
public void onAnimationEnd(Animation animation) {
//动画结束时执行
}
});
2、补间动画实现原理
主要有四种AlpahAnimation\ ScaleAnimation\ RotateAnimation\ TranslateAnimation四种,对透明度、缩放、旋转、位移四种动画。在调用View.startAnimation时,先调用View.setAnimation(Animation)方法给自己设置一个Animation对象,再调用invalidate来重绘自己。在View.draw(Canvas, ViewGroup, long)方法中进行了getAnimation(), 并调用了drawAnimation(ViewGroup, long, Animation, boolean)方法,此方法调用Animation.getTranformation()方法,再调用applyTranformation()方法,该方法中主要是对Transformation.getMatrix().setTranslate/setRotate/setAlpha/setScale来设置相应的值,这个方法系统会以60FPS的频率进行调用。具体是在调Animation.start()方法中会调用animationHandler.start()方法,从而调用了scheduleAnimation()方法,这里会调用mChoreographer.postCallback(Choregrapher.CALLBACK_ANIMATION, this, null)放入事件队列中,等待doFrame()来消耗事件。
当一个 ChildView 要重画时,它会调用其成员函数 invalidate() 函数将通知其 ParentView 这个 ChildView 要重画,这个过程一直向上遍历到 ViewRoot,当 ViewRoot 收到这个通知后就会调用ViewRoot 中的 draw 函数从而完成绘制。View::onDraw() 有一个画布参数 Canvas, 画布顾名思义就是画东西的地方,Android 会为每一个 View 设置好画布,View 就可以调用 Canvas 的方法,比如:drawText, drawBitmap, drawPath 等等去画内容。每一个 ChildView 的画布是由其 ParentView 设置的,ParentView 根据 ChildView 在其内部的布局来调整 Canvas,其中画布的属性之一就是定义和 ChildView 相关的坐标系,默认是横轴为 X 轴,从左至右,值逐渐增大,竖轴为 Y 轴,从上至下,值逐渐增大。
çªå£åæ ç³»
Android 补间动画就是通过 ParentView 来不断调整 ChildView 的画布坐标系来实现的,在ParentView的dispatchDraw方法会被调用。
dispatchDraw()
{
....
Animation a = ChildView.getAnimation()
Transformation tm = a.getTransformation();
Use tm to set ChildView's Canvas;
Invalidate();
....
}
这里有两个类:Animation 和 Transformation,这两个类是实现动画的主要的类,Animation 中主要定义了动画的一些属性比如开始时间、持续时间、是否重复播放等,这个类主要有两个重要的函数:getTransformation 和 applyTransformation,在 getTransformation 中 Animation 会根据动画的属性来产生一系列的差值点,然后将这些差值点传给 applyTransformation,这个函数将根据这些点来生成不同的 Transformation,Transformation 中包含一个矩阵和 alpha 值,矩阵是用来做平移、旋转和缩放动画的,而 alpha 值是用来做 alpha 动画的(简单理解的话,alpha 动画相当于不断变换透明度或颜色来实现动画),调用 dispatchDraw 时会调用 getTransformation 来得到当前的 Transformation。某一个 View 的动画的绘制并不是由他自己完成的而是由它的父 view 完成。
1)补间动画TranslateAnimation,View位置移动了,可是点击区域还在原来的位置,为什么?
View在做动画是,根据动画时间的插值,计算出一个Matrix,不停的invalidate,在onDraw中的Canvas上使用这个计算出来的Matrix去draw view的内容。某个view的动画绘制并不是由它自己完成,而是由它的父view完成,使它的父view画布进行了移动,而点击时还是点击原来的画布。使得它看起来变化了。
参考文章:Android 动画框架详解,第 1 部分
3、Android各个版本API的区别
Android API版本对照表及各个版本特性简单描述
主要记住一些大版本变化:
android3.0 代号Honeycomb, 引入Fragments, ActionBar,属性动画,硬件加速
android4.0 代号Ice Cream,API14:截图功能,人脸识别,虚拟按键,3D优化驱动
android5.0 代号Lollipop API21:调整桌面图标及部件透明度等
android6.0 代号M Marshmallow API23,软件权限管理,安卓支付,指纹支持,App关联,
android7.0 代号N Preview API24,多窗口支持(不影响Activity生命周期),增加了JIT编译器,引入了新的应用签名方案APK Signature Scheme v2(缩短应用安装时间和更多未授权APK文件更改保护),严格了权限访问
android8.0 代号O API26,取消静态广播注册,限制后台进程调用手机资源,桌面图标自适应
android9.0 代号P API27,加强电池管理,系统界面添加了Home虚拟键,提供人工智能API,支持免打扰模式
4、Requestlayout,onlayout,onDraw,DrawChild区别与联系
requestLayout()方法 :会导致调用measure()过程 和 layout()过程 。 说明:只是对View树重新布局layout过程包括measure()和layout()过程,如果view的l,t,r,b没有必变,那就不会触发onDraw;但是如果这次刷新是在动画里,mDirty非空,就会导致onDraw。
onLayout()方法(如果该View是ViewGroup对象,需要实现该方法,对每个子视图进行布局)
onDraw()方法绘制视图本身 (每个View都需要重载该方法,ViewGroup不需要实现该方法)
drawChild()去重新回调每个子视图的draw()方法
5、invalidate和postInvalidate的区别及使用
View.invalidate(): 层层上传到父级,直到传递到ViewRootImpl后触发了scheduleTraversals(),然后整个View树开始重新按照View绘制流程进行重绘任务。
invalidate:在ui线程刷新view
postInvalidate:在工作线程刷新view(底层还是handler)其实它的原理就是invalidate+handler
View.postInvalidate最终会调用ViewRootImpl.dispatchInvalidateDelayed()方法
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
这里的mHandler是ViewRootHandler实例,在该Handler的handleMessage方法中调用了view.invalidate()方法。
case MSG_INVALIDATE:
((View) msg.obj).invalidate();
break;
6、Activity-Window-View三者的差别
Activity:是安卓四大组件之一,负责界面展示、用户交互与业务逻辑处理;
Window:就是负责界面展示以及交互的职能部门,就相当于Activity的下属,Activity的生命周期方法负责业务的处理;
View:就是放在Window容器的元素,Window是View的载体,View是Window的具体展示。
三者的关系: Activity通过Window来实现视图元素的展示,window可以理解为一个容器,盛放着一个个的view,用来执行具体的展示工作。
7、谈谈对Volley的理解
8、如何优化自定义View
1)在要在onDraw或是onLayout()中去创建对象,因为onDraw()方法可能会被频繁调用,可以在view的构造函数中进行创建对象;
2)降低view的刷新频率,尽可能减少不必要的调用invalidate()方法。或是调用带四种参数不同类型的invalidate(),而不是调用无参的方法。无参变量需要刷新整个view,而带参数的方法只需刷新指定部分的view。在onDraw()方法中减少冗余代码。
3)使用硬件加速,GPU硬件加速可以带来性能增加。
4)状态保存与恢复,如果因内存不足,Activity置于后台被杀重启时,View应尽可能保存自己属性,可以重写onSaveInstanceState和onRestoreInstanceState方法,状态保存。
9、低版本SDK如何实现高版本api?
使用@TargetApi注解·
当代码中有比AndroidManifest中设置的android:minSdkVersion版本更高的方法,此时编译器会提示警告,解决方法是在方法上加上@SuppressLint("NewApi")或者@TargetApi()。但它们仅是屏蔽了android lint错误,在方法中还要判断版本做不同的操作。
@SuppressLint("NewApi")屏蔽一切新api中才能使用的方法报的android lint错误
@TargetApi() 只屏蔽某一新api中才能使用的方法报的android lint错误,如@TargetApi(11)如果在方法中用了只有API14才开始有的方法,还是会报错。
10、描述一次网络请求的流程
1)域名解析
浏览器会先搜索自身DNS缓存且对应的IP地址没有过期;若未找到则搜索操作系统自身的DNS缓存;若还未找到则读本地的hotsts文件;还未找到会在TCP/IP设置的本地DNS服务器上找,如果要查询的域名在本地配置的区域资源中,则完成解析;否则根据本地DNS服务器会请求根DNS服务器;根DNS服务器是13台根DNS,会一级一级往下找。
2)TCP三次握手
客户端先发送SYN=1,ACK=0,序列号seq=x报文;(SYN在连接建立时用来同步序号,SYN=1,ACK=0代表这是一个连接请求报文,对方若同意建立连接,则应在响应报文中使SYN=1,ACK=1)
服务器返回SYN=1,ACK=1,seq=y, ack=x+1;
客户端再一次确认,但不用SYN了,回复服务端, ACK=1, seq=x+1, ack=y+1
3)建立TCP连接后发起HTTP请求
客户端按照指定的格式开始向服务端发送HTTP请求,HTTP请求格式由四部分组成,分别是请求行、请求头、空行、消息体,服务端接收到请求后,解析HTTP请求,处理完成逻辑,最后返回一个具有标准格式的HTTP响应给客户端。
4)服务器响应HTTP请求
服务器接收处理完请求后返回一个HTTP响应消息给客户端,HTTP响应信息格式包括:状态行、响应头、空行、消息体
5)浏览器解析HTML代码,请求HTML代码中的资源
浏览器拿到html文件后,就开始解析其中的html代码,遇到js/css/image等静态资源时,向服务器发起一个http请求,如果返回304状态码,浏览器会直接读取本地的缓存文件。否则开启线程向服务器请求下载。
6)浏览器对页面进行渲染并呈现给用户
7)TCP的四次挥手
当客户端没有东西要发送时就要释放连接(提出中断连接可以是Client也可以是Server),客户端会发送一个FIN=1的没有数据的报文,进入FIN_WAIT状态,服务端收到后会给客户端一个确认,此时客户端不能发送数据,但可接收信息。
11、HttpUrlConnection 和 okhttp关系
两者都可以用来实现网络请求,android4.4之后的HttpUrlConnection的实现是基于okhttp
Bitmap对象的理解
looper架构
ActivityThread,AMS,WMS的工作原理
自定义View如何考虑机型适配
在onMeasure()的getDefaultSize()的默认实现中,当view的测量模式是AT_MOST或EXACTLY时,View的大小都会被设置成子View MeasureSpec的specSize.子view的MeasureSpec值是根据子View的布局参数和父容器的MeasureSpec值计算得来。当子view的布局参数是wrap_content时,对应的测量模式是AT_MOST,大小是parentSize,
自定义View的事件
AstncTask+HttpClient 与 AsyncHttpClient有什么区别?
LaunchMode应用场景
AsyncTask 如何使用?
SpareArray原理
请介绍下ContentProvider 是如何实现数据共享的?
AndroidService与Activity之间通信的几种方式
IntentService原理及作用是什么?
原理:IntentService是继承Service的一个抽象类,它在onCreate()方法中创建了一个HandlerThread,并启动该线程。HandlerThread是带有自己消息队列和Looper的线程,根据HandlerThread的looper创建一个Handler,这样IntentService的ServiceHandler的handleMessage()方法就运行在子线程中。handleMessage中调用了onHandleIntent()方法,它是一个抽象方法,继承IntentService类需要实现该方法,把耗时操作放在onHandleIntent()方法中,等耗时操作运行完成后,会调用stopSelf()方法,服务会调用onDestory()方法消毁自己。如果onHandleIntent()中的耗时操作未运行完前就调用了stopSelf()方法,服务调用onDestory()方法,但耗时操作会继续运行,直至运行完毕。如果同时多次启动IntentService,任务会放在一个队列中,onCreate()和onDestory()方法都只会运行一次。
作用:用来处理后台耗时操作,如读取数据库或是本地文件等。
说说Activity、Intent、Service 是什么关系
ApplicationContext和ActivityContext的区别
SP是进程同步的吗?有什么方法做到同步?
谈谈多线程在Android中的使用
进程和 Application 的生命周期
封装View的时候怎么知道view的大小
RecycleView原理
AndroidManifest的作用与理解
(三)常见的一些原理性问题
Handler机制和底层实现
Handler、Thread和HandlerThread的差别
1)Handler线程的消息通讯的桥梁,主要用来发送消息及处理消息。
2)Thread普通线程,如果需要有自己的消息队列,需要调用Looper.prepare()创建Looper实例,调用loop()去循环消息。
3)HandlerThread是一个带有Looper的线程,在HandleThread的run()方法中调用了Looper.prepare()创建了Looper实例,并调用Looper.loop()开启了Loop循环,循环从消息队列中获取消息并交由Handler处理。利用该线程的Looper创建Handler实例,此Handler的handleMessage()方法是运行在子线程中的。即Handler利用哪个线程的Looper创建的实例,它就和相应的线程绑定到一起,处理该线程上的消息,它的handleMessage()方法就是在那个线程中运行的,无参构造默认是主线程。HandlerThread提供了quit()/quitSafely()方法退出HandlerThread的消息循环,它们分别调用Looper的quit和quitSafely方法,quit会将消息队列中的所有消息移除,而quitSafely会将消息队列所有延迟消息移除,非延迟消息派发出去让Handler去处理。
HandlerThread适合处理本地IO读写操作(读写数据库或文件),因为本地IO操作耗时不长,对于单线程+异步队列不会产生较大阻塞,而网络操作相对比较耗时,容易阻塞后面的请求,因此HandlerThread不适合加入网络操作。
handler发消息给子线程,looper怎么启动?
关于Handler,在任何地方new Handler 都是什么线程下?
ThreadLocal原理,实现及如何保证Local属性?
请解释下在单线程模型中Message、Handler、Message Queue、Looper之间的关系
请描述一下View事件传递分发机制
Touch事件传递流程
事件分发中的onTouch 和onTouchEvent 有什么区别,又该如何使用?
View和ViewGroup分别有哪些事件分发相关的回调方法
View刷新机制
View绘制流程
自定义控件原理
自定义View如何提供获取View属性的接口?
Android代码中实现WAP方式联网
AsyncTask机制
AsyncTask原理及不足
如何取消AsyncTask?
为什么不能在子线程更新UI?
ANR产生的原因是什么?
ANR定位和修正
oom是什么?
oom(Out Of Memory)内存溢出,
什么情况导致oom?
有什么解决方法可以避免OOM?
Oom 是否可以try catch?为什么?
可以,当
内存泄漏是什么?
内存泄露就是指该被GC垃圾回收的,但被一个生命周期比它长的对象仍然在引用它,导致无法回收,造成内存泄露,过多的内存泄露会导致OOM。
什么情况导致内存泄漏?
1)非静态内部类、匿名内部类:非静态内部类、匿名内部类 都会持有外部类的一个引用,如果有一个静态变量引用了非静态内部类或者匿名内部类,导致非静态内部类或者匿名内部类的生命周期比外部类(Activity)长,就会导致外部类在该被回收的时候,无法被回收掉,引起内存泄露, 除非外部类被卸载。
解决办法:将非静态内部类、匿名内部类 改成静态内部类,或者直接抽离成一个外部类。 如果在静态内部类中,需要引用外部类对象,那么可以将这个引用封装在一个WeakReference中。
2)静态的View:当一个Activity经常启动,但是对应的View读取非常耗时,我们可以通过静态View变量来保持对该Activity的rootView引用。这样就可以不用每次启动Activity都去读取并渲染View了。但View attach到我们的Window上,就会持有一个Context(即Activity)的引用。而我们的View有事一个静态变量,所以导致Activity不被回收。
解决办法: 在使用静态View时,需要确保在资源回收时,将静态View detach掉。
3)Handler:在Activity中定义Handler对象,那么Handler持有Activty的引用。而每个Message对象是持有Handler的引用的(Message对象的target属性持有Handler引用),从而导致Message间接引用到了Activity。如果在Activty destroy之后,消息队列中还有Message对象,Activty是不会被回收的。
解决办法: 将Handler放入单独的类或者将Handler放入到静态内部类中(静态内部类不会持有外部类的引用)。如果想要在handler内部去调用所在的外部类Activity,可以在handler内部使用弱引用的方式指向所在Activity,在onDestory时,调用相应的方法移除回调和删除消息。
4)监听器(各种需要注册的Listener,Watcher等):当我们需要使用系统服务时,比如执行某些后台任务、为硬件访问提供接口等等系统服务。我们需要把自己注册到服务的监听器中。然而,这会让服务持有 activity 的引用,如果程序员忘记在 activity 销毁时取消注册,那就会导致 activity 泄漏了。
解决办法:在onDestory中移除注册
5. 资源对象没关闭造成内存泄漏:当我们打开资源时,一般都会使用缓存。比如读写文件资源、打开数据库资源、使用Bitmap资源等等。当我们不再使用时,应该关闭它们,使得缓存内存区域及时回收。
解决办法:使用try finally结合,在try块中打开资源,在finally中关闭资源
6. 属性动画:在使用ValueAnimator或者ObjectAnimator时,如果没有及时做cancel取消动画,就可能造成内存泄露。因为在cancel方法里,最后调用了endAnimation(); ,在endAnimation里,有个AnimationHandler的单例,会持有属性动画对象的引用。
解决办法:在onDestory中调用动画的cancel方法
7. RxJava:在使用RxJava时,如果在发布了一个订阅后,由于没有及时取消,导致Activity/Fragment无法销毁,导致的内存泄露。
解决办法:使用RxLifeCycle
内存泄漏和内存溢出区别?
(1)内存泄漏
1)内存泄漏:指程序中已动态分配的堆内存由于某种原因未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统奔溃等严重后果。
2)一次内存泄漏似乎不会有大的影响,但内存泄漏后堆积的结果就是内存溢出。
3)内存泄漏具有隐蔽性,积累性的特征,比其他内存非法访问错误更难检测。这是因为内存泄漏产生的原因是内存块未被释放,属于遗漏型缺陷而不是过错型缺陷。此外,内存泄漏不会直接产生可观察的错误,而是逐渐积累,降低系统的整体性性能。
4)如何有效的进行内存分配和释放,防止内存泄漏,是软件开发人员的关键问题,比如一个服务器应用软件要长时间服务多个客户端,若存在内存泄漏,则会逐渐堆积,导致一系列严重后果。
(2)内存溢出
指程序在申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,就会导致内存不够用,报错OOM,即出现内存溢出的错误。
LruCache默认缓存大小
4MB
ContentProvider的权限管理(解答:读写分离,权限控制-精确到表级,URL控制)
如何通过广播拦截和abort一条短信?
广播是否可以请求网络?
广播引起anr的时间限制是多少?
计算一个view的嵌套层级
Activity栈
Android线程有没有上限?
理论上是没有上限的,但按一般写法一般是线程最多开到2*CPU个数+1
线程池有没有上限?
要根据用户调用不同的线程池构造函数。
ListView重用的是什么?
Android为什么引入Parcelable?
有没有尝试简化Parcelable的使用?
(四)开发中常见的一些问题
ListView 中图片错位的问题是如何产生的?
混合开发有了解吗?
知道哪些混合开发的方式?说出它们的优缺点和各自使用场景?(解答:比如:RN,weex,H5,小程序,WPA等。做Android的了解一些前端js等还是很有好处的);
屏幕适配的处理技巧都有哪些?
服务器只提供数据接收接口,在多线程或多进程条件下,如何保证数据的有序到达?
动态布局的理解
怎么去除重复代码?
画出 Android 的大体架构图
Recycleview和ListView的区别
ListView图片加载错乱的原理和解决方案
ListView item缓存机制:为了使得性能更优,ListView会缓存行item(某行对应的View)。ListView通过adapter的getView函数获得每行的item。滑动过程中,1)如果某行item已经滑出屏幕,若该item不在缓存内,则put进缓存,否则更新缓存;
2)获取滑入屏幕的行item之前会先判断缓存中是否有可用的item,如果有,做为convertView参数传递给adapter的getView。
出现的问题:
1)行item图片显示重复,当前行item显示了之前某行item的图片。
比如ListView滑动到第2行会异步加载某个图片,但是加载很慢,加载过程中listView已经滑动到了第14行,且滑动过程中该图片加载结束,第2行已不在屏幕内,根据上面介绍的缓存原理,第2行的view可能被第14行复用,这样我们看到的就是第14行显示了本该属于第2行的图片,造成显示重复。
2)行item图片显示闪烁
如果第14行图片又很快加载结束,所以我们看到第14行先显示了第2行的图片,立马又显示了自己的图片进行覆盖造成闪烁错乱。
解决方法
通过上面的分析我们知道了出现错乱的原因是异步加载及对象被复用造成的,如果每次getView能给对象一个标识,在异步加载完成时比较标识与当前行item的标识是否一致,一致则显示,否则不做处理即可。
动态权限适配方案,权限组的概念
Android系统为什么会设计ContentProvider?
下拉状态栏是不是影响activity的生命周期
如果在onStop的时候做了网络请求,onResume的时候怎么恢复?
Bitmap 使用时候注意什么?
Bitmap的recycler()
Android中开启摄像头的主要步骤
ViewPager使用细节,如何设置成每次只初始化当前的Fragment,其他的不初始化?
点击事件被拦截,但是想传到下面的View,如何操作?
微信主页面的实现方式
微信上消息小红点的原理
CAS介绍
三、混合开发面试题
大厂除了技术深度之外,还要求你具备一些广度的知识,比如你要会前端知识,会混合开发,至少会一种脚本语言,C c++更不用说了,也是必会的。
Hybrid做过吗?
Hybrid通信原理是什么,有做研究吗?
react native有多少了解?讲一下原理。
weex了解吗?如何自己实现类似技术?
flutter了解吗?内部是如何实现跨平台的?
Dart语言有研究贵吗?
快应用了解吗?跟其她方式相比有什么优缺点?
说说你用过的混合开发技术有哪些?各有什么优缺点?
Python会吗?
会不会PHP?
Gradle了解多少?groovy语法会吗?
四、高端技术面试题
这里讲的是大公司需要用到的一些高端Android技术,这里专门整理了一个文档,希望大家都可以看看。这些题目有点技术含量,需要好点时间去研究一下的。
(一)图片
1、图片库对比
2、LRUCache原理
LruCache是个泛型类,主要原理是:把最近使用的对象用强引用存储在LinkedHashMap中,当缓存满时,把最近最少使用的对象从内存中移除,并提供get/put方法完成缓存的获取和添加。LruCache是线程安全的,因为使用了synchronized关键字。
当调用put()方法,将元素加到链表头,如果链表中没有该元素,大小不变,如果没有,需调用trimToSize方法判断是否超过最大缓存量,trimToSize()方法中有一个while(true)死循环,如果缓存大小大于最大的缓存值,会不断删除LinkedHashMap中队尾的元素,即最少访问的,直到缓存大小小于最大缓存值。当调用LruCache的get方法时,LinkedHashMap会调用recordAccess方法将此元素加到链表头部。
3、图片加载原理
4、自己去实现图片库,怎么做?
5、Glide源码解析
1)Glide.with(context)创建了一个RequestManager,同时实现加载图片与组件生命周期绑定:在Activity上创建一个透明的ReuqestManagerFragment加入到FragmentManager中,通过添加的Fragment感知Activty\Fragment的生命周期。因为添加到Activity中的Fragment会跟随Activity的生命周期。在RequestManagerFragment中的相应生命周期方法中通过liftcycle传递给在lifecycle中注册的LifecycleListener
2)RequestManager.load(url) 创建了一个RequestBuilder
3) RequestBuilder.into(view)
-->into(glideContext.buildImageViewTarget(view, transcodeClass))返回的是一个DrawableImageViewTarget, Target用来最终展示图片的,buildImageViewTarget-->ImageViewTargetFactory.buildTarget()根据传入class参数不同构建不同的Target对象,这个Class是根据构建Glide时是否调用了asBitmap()方法,如果调用了会构建出BitmapImageViewTarget,否则构建的是GlideDrawableImageViewTarget对象。
-->GenericRequestBuilder.into(Target),该方法进行了构建Request,并用RequestTracker.runRequest()
Request request = buildRequest(target);//构建Request对象,Request是用来发出加载图片的,它调用了buildRequestRecursive()方法以,内部调用了GenericRequest.obtain()方法
target.setRequest(request);
lifecycle.addListener(target);
requestTracker.runRequest(request);//判断Glide当前是不是处于暂停状态,若不是则调用Request.begin()方法来执行Request,否则将Request添加到待执行队列里,等暂停态解除了后再执行
-->GenericRequest.begin()
1)onSizeReady()--> Engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,
priority, isMemoryCacheable, diskCacheStrategy, this) --> a)先构建EngineKey; b) loadFromCache从缓存中获取EngineResource,如果缓存中获取到cache就调用cb.onResourceReady(cached); c)如果缓存中不存在调用loadFromActiveResources从active中获取,如果获取到就调用cb.onResourceReady(cached);d)如果active中也不存在,调用EngineJob.start(EngineRunnable), 从而调用decodeFromSource()/decodeFromCache()-->如果是调用decodeFromSource()-->ImageVideoFetcher.loadData()-->HttpUrlFetcher()调用HttpUrlConnection进行网络请求资源-->得于InputStream()后,调用decodeFromSourceData()-->loadProvider.getSourceDecoder().decode()方法解码-->GifBitmapWrapperResourceDecoder.decode()-->decodeStream()先从流中读取2个字节判断是GIF还是普通图,若是GIF调用decodeGifWrapper()来解码,若是普通静图则调用decodeBitmapWrapper()来解码-->bitmapDecoder.decode()
6、Glide使用什么缓存?
1) 内存缓存:LruResourceCache(memory)+弱引用activeResources
Map
2)磁盘缓存:DiskLruCache,这里分为Source(原始图片)和Result(转换后的图片)
第一次获取图片,肯定网络取,然后存active\disk中,再把图片显示出来,第二次读取相同的图片,并加载到相同大小的imageview中,会先从memory中取,没有再去active中获取。如果activity执行到onStop时,图片被回收,active中的资源会被保存到memory中,active中的资源被回收。当再次加载图片时,会从memory中取,再放入active中,并将memory中对应的资源回收。
之所以需要activeResources,它是一个随时可能被回收的资源,memory的强引用频繁读写可能造成内存激增频繁GC,而造成内存抖动。资源在使用过程中保存在activeResources中,而activeResources是弱引用,随时被系统回收,不会造成内存过多使用和泄漏。
7、Glide内存缓存如何控制大小?
Glide内存缓存最大空间(maxSize)=每个进程可用最大内存*0.4(低配手机是 每个进程可用最大内存*0.33)
磁盘缓存大小是250MB int DEFAULT_DISK_CACHE_SIZE = 250 * 1024 * 1024;
(二)网络和安全机制
网络框架对比和源码分析
自己去设计网络请求框架,怎么做?
okhttp源码
网络请求缓存处理,okhttp如何处理网络缓存的
(1)网络缓存优先考虑强制缓存,再考虑对比缓存
首先判断强制缓存中的数据的是否在有效期内。如果在有效期,则直接使用缓存。如果过了有效期,则进入对比缓存。
在对比缓存过程中,判断ETag是否有变动,如果服务端返回没有变动,说明资源未改变,使用缓存。如果有变动,判断Last-Modified。
判断Last-Modified,如果服务端对比资源的上次修改时间没有变化,则使用缓存,否则重新请求服务端的数据,并作缓存工作。
(2)okhttp缓存
开启使用Okhttp的缓存其实很简单,只需要给OkHttpClient对象设置一个Cache对象即可,创建一个Cache时指定缓存保存的目录和缓存最大的大小即可。
//新建一个cache,指定目录为外部目录下的okhttp_cache目录,大小为100M
Cache cache = new Cache(new File(Environment.getExternalStorageDirectory() + "/okhttp_cache/"), 100 * 1024 * 1024);
//将cache设置到OkHttpClient中,这样缓存就开始生效了。
OkHttpClient client = new OkHttpClient.Builder().cache(cache).build();
相关的类有:
1)CacheControl( HTTP中的Cache-Control和Pragma缓存控制):指定缓存规则
2)Cache(缓存类)
3)DiskLruCache(文件化的LRU缓存类)
(1)读取缓存:先获限OkHttpClient的Cache缓存对象,就是上面创建OkHttpClient设置的Cahce; 传Request请求到Cache的get方法查找缓存响应数据Response;构造一个缓存策略,再调用它的get去决策使用网络请求还是缓存响应。若使用缓存,它的cacheResponse不为空,networkRequest为空,用缓存构造响应直接返回。若使用请求,则cacheResponse为空,networkRequest不为空,开始网络请求流程。
Cache的get获取缓存方法,计算request的key值(请求url进行md5加密),根据key值去DisLruCache查找是否存在缓存内容,存则则创建绘存Entry实体。ENTRY_METADATA代表响应头信息,ENTRY_BODY代表响应体信息。如果缓存存在,在指定目录下会有两个文件****.0 *****.1分别存储某个请求缓存响应头和响应体信息。
CacheStrategy的get方法:1)若缓存响应为空或 2)请求是https但缓存响应没有握手信息;3)请求和缓存响应都是不可缓存的;4)请求是onCache,并且又包含if-Modified-Since或If-None-Match则不使用缓存; 再计算请求有效时间是否符合响应的过期时间,若响应在有效范围内,则缓存策略使用缓存,否则创建一个新的有条件的请求,返回有条件的缓存策略。
(2)存储缓存流程:从HttpEngine的readResponse()发送请求开始,判断hasBody(userResponse),如果缓存的话,maybeCache()缓存响应头信息,unzip(cacheWritingResponse(storeRequest, userResponse))缓存响应体。
从网络加载一个10M的图片,说下注意事项
TCP的3次握手和四次挥手
TCP与UDP的区别
TCP与UDP的应用
HTTP协议
HTTP1.0与2.0的区别
HTTP报文结构
HTTP与HTTPS的区别以及如何实现安全性
如何验证证书的合法性?
https中哪里用了对称加密,哪里用了非对称加密,对加密算法(如RSA)等是否有了解?
client如何确定自己发送的消息被server收到?
谈谈你对WebSocket的理解
WebSocket与socket的区别
谈谈你对安卓签名的理解。
请解释安卓为啥要加签名机制?
视频加密传输
App 是如何沙箱化,为什么要这么做?
权限管理系统(底层的权限是如何进行 grant 的)?
(三)数据库
sqlite升级,增加字段的语句
数据库框架对比和源码分析
数据库的优化
数据库数据迁移问题
(四)算法
排序算法有哪些?
最快的排序算法是哪个?
手写一个冒泡排序
手写快速排序代码
快速排序的过程、时间复杂度、空间复杂度
手写堆排序
堆排序过程、时间复杂度及空间复杂度
写出你所知道的排序算法及时空复杂度,稳定性
二叉树给出根节点和目标节点,找出从根节点到目标节点的路径
给阿里2万多名员工按年龄排序应该选择哪个算法?
GC算法(各种算法的优缺点以及应用场景)
蚁群算法与蒙特卡洛算法
子串包含问题(KMP 算法)写代码实现
一个无序,不重复数组,输出N个元素,使得N个元素的和相加为M,给出时间复杂度、空间复杂度。手写算法
万亿级别的两个URL文件A和B,如何求出A和B的差集C(提示:Bit映射->hash分组->多文件读写效率->磁盘寻址以及应用层面对寻址的优化)
百度POI中如何试下查找最近的商家功能(提示:坐标镜像+R树)。
两个不重复的数组集合中,求共同的元素。
两个不重复的数组集合中,这两个集合都是海量数据,内存中放不下,怎么求共同的元素?
一个文件中有100万个整数,由空格分开,在程序中判断用户输入的整数是否在此文件中。说出最优的方法
一张Bitmap所占内存以及内存占用的计算
一张图片(bitmap)占用的内存影响因素:图片原始长、宽,手机屏幕密度,图片存放路径下的密度,单位像素占用字节数
bitmapSize=图片长度*(inTargetDensity手机的density / inDensity图片存放目录的density)*宽度*(手机的inTargetDensity / inDensity目标存放目录的density)*单位像素占用的字节数(图片长宽单位是像素)
1)图片长宽单位是像素:单位像素字节数由其参数BitmapFactory.Options.inPreferredConfig变量决定,它是Bitmap.Config类型,包括以下几种值:ALPHA_8图片只有alpha值,占用一个字节;ARGB_4444 一个像素占用2个字节,A\R\G\B各占4bits;ARGB_8888一个像素占用4个字节,A\R\G\B各占8bits(高质量图片格式,bitmap默认格式);ARGB_565一个像素占用2字节,不支持透明和半透明,R占5bit, Green占6bit, Blue占用5bit. 从Android4.0开始该项无效。
2) inTargetDensity 手机的屏幕密度(跟手机分辨率有关系)
inDensity原始资源密度(mdpi:160; hdpi:240; xhdpi:320; xxhdpi:480; xxxhdpi:640)
当Bitmap对象在不使用时,应该先调用recycle(),再将它设置为null,虽然Bitmap在被回收时可通过BitmapFinalizer来回收内存。但只有系统垃圾回收时才会回收。Android4.0之前,Bitmap内存分配在Native堆中,Android4.0开始,Bitmap的内存分配在dalvik堆中,即Java堆中,调用recycle()并不能立即释放Native内存。
2000万个整数,找出第五十大的数字?
烧一根不均匀的绳,从头烧到尾总共需要1个小时。现在有若干条材质相同的绳子,问如何用烧绳的方法来计时一个小时十五分钟呢?
求1000以内的水仙花数以及40亿以内的水仙花数
5枚硬币,2正3反如何划分为两堆然后通过翻转让两堆中正面向上的硬8币和反面向上的硬币个数相同
时针走一圈,时针分针重合几次
N*N的方格纸,里面有多少个正方形
x个苹果,一天只能吃一个、两个、或者三个,问多少天可以吃完?
(五)插件化、模块化、组件化、热修复、增量更新、Gradle
对热修复和插件化的理解
插件化原理分析
模块化实现(好处,原因)
热修复,插件化
项目组件化的理解
描述清点击 Android Studio 的 build 按钮后发生了什么
(六)架构设计和设计模式
谈谈你对Android设计模式的理解
MVC MVP MVVM原理和区别
你所知道的设计模式有哪些?
项目中常用的设计模式
手写生产者/消费者模式
写出观察者模式的代码
适配器模式,装饰者模式,外观模式的异同?
用到的一些开源框架,介绍一个看过源码的,内部实现过程。
谈谈对RxJava的理解
RxJava是基于响应式编程,基于事件流、实现异步操(类似于Android中的AsyncTask、Handler作用)作的库,基于事件流的链式调用,使得RxJava逻辑简洁、使用简单。RxJava原理是基于一种扩展的观察者模式,有四种角色:被观察者Observable 观察者Observer 订阅subscribe 事件Event。RxJava原理可总结为:被观察者Observable通过订阅(subscribe)按顺序发送事件(Emitter)给观察者(Observer), 观察者按顺序接收事件&作出相应的响应动作。
RxJava中的操作符:
1)defer():直到有观察者(Observer)订阅时,才会动态创建被观察者对象(Observer)&发送事件,通过Observer工厂方法创建被观察者对象,每次订阅后,都会得到一个刚创建的最新的Observer对象,可以确保Observer对象里的数据是最新的。defer()方法只会定义Observable对象,只有订阅操作才会创建对象。
Observable
@Override
public ObservableSource extends T> call() throws Exception {
return Observable.just();
}
}
2)timer() 快速创建一个被观察者(Observable),延迟指定时间后,再发送事件
Observable.timer(2, TimeUnit.SECONDS)//也可以自定义线程timer(long, TimeUnit, Scheduler)
.subscribe(new Observer
@Override
public void onSubscribe(Disposable d) {
}
...
});
3) interval() intervalRange() 快速创建一个被观察者对象(Observable),每隔指定时间就发送事件
//interval三个参数,参数1:第一次延迟时间 参数2:间隔时间数字 参数3:时间单位
Observable.interval(3, 1, TimeUnit.SECONDS).subscribe();
//intervalRange五个参数,参数1:事件序列起始点 参数2:事件数量 参数3:第一次延迟时间 参数4:间隔时间数字 参数5:时间单位
Observable.intervalRange(3, 10, 2, 1, TimeUnit.SECONDS).subscribe();
RxJava的功能与原理实现
Rxjava发送事件步骤:
1)创建被观察者对象Observable&定义需要发送的事件
Observable.create(new ObservableOnSubscribe
@Override
public void subscribe(ObservableEmitter
//定义发送事件的行为
}
});
Observable.create()方法实际创建了一个ObservableCreate对象,它是Observable的子类,传入一个ObservableOnSubscribe对象,复写了发送事件行为的subscribe()方法。
2)创建观察者对象Observer&定义响应事件的行为
Observer observer = new Observer
@Override
public void onSubscribe(Disposable d){//Disposable对象可用于结束事件
//默认最先调用
}
@Override
public void onNext(T t){
}
@Override
public void onError(Throwable d){
}
@Override
public void onComplete(){
}
}
3)通过subscribe()方法使观察者订阅被观察者
Observable.subscribe(Observer observer);//实际调用的是ObservableCreate.subscribeActual()方法,具体实现如下
protected void subscribeActual(Observer super T> observer) {
// 1. 创建1个CreateEmitter对象用于发射事件(封装成1个Disposable对象)
CreateEmitter
// 2. 调用观察者(Observer)的onSubscribe()
observer.onSubscribe(parent);
try {
// 3. 调用source对象的(ObservableOnSubscribe对象)subscribe()
source.subscribe(parent);
} catch (Throwable ex) {
Exceptions.throwIfFatal(ex);
parent.onError(ex);
}
}
RxJava的作用,与平时使用的异步操作来比的优缺点
说说EventBus作用,实现方式,代替EventBus的方式
从0设计一款App整体架构,如何去做?
说一款你认为当前比较火的应用并设计(比如:直播APP,P2P金融,小视频等)
谈谈对java状态机理解
Fragment如果在Adapter中使用应该如何解耦?
Binder机制及底层实现
对于应用更新这块是如何做的?(解答:灰度,强制更新,分区域更新)?
实现一个Json解析器(可以通过正则提高速度)
统计启动时长,标准
(七)性能优化
如何对Android 应用进行性能分析以及优化?
ddms 和 traceView
性能优化如何分析systrace?
用IDE如何分析内存泄漏?
Java多线程引发的性能问题,怎么解决?
启动页白屏及黑屏解决?
启动太慢怎么解决?
在冷启动系统要执行三个任务:加载和启动应用程序;在app启动后立即显示一个空白窗体;创建APP进程。
应用启动后会执行:创建应用程序对象;启动主线程;创建Main Activity;初始化构造View;在屏幕上布局;执行初始化绘制操作;
应用启动,空白窗口会一直存在直到系统完成了应用的首次绘制操作,此时,系统会替换启动窗口,让用户能和APP进行交互。
对于热启动,如果应用的Activity驻留在内存中,应用就可避免重复进行对象初始化。如果系统执行了内存回收并触发GC,如onTrimMemory(),热启动时对象仍需重建,这样系统进程也会一直显示白屏直到应用完成Activity的渲染。
测量应用启动时间:1) 可通过logcat中查看Displayed中显示启动类耗时; 2) 通过adb shell am start -S -W 包名/启动类全限定名,-S表示重启当前应用,命令进行检测启动app的耗时。3) 使用reportFullyDrawn()方法来测量应用启动到所有资源和视图层次结构完整显示之间的所经过的时间。
优化方法:1)减少view的层级,减少复用和布局嵌套使布局扁平化,不要加载对用户不可见的布局,如使用ViewStub;
2)将需要在主线程中初始化但可不立即完成的延迟加载,部分组件放到子线程中初始化。
3)减少Application.onCreate和启动页和第一个界面onCreate中方法的耗时操作。
4)设置闪屏页,将闪屏页设置为启动页的activity窗口背景windowBackground属性,为启动屏幕提供一个背景,
怎么保证应用启动不卡顿?
应用卡顿的主要原因有:速度曲线不够流畅,掉帧、触摸响应速度
Android显示机制:app->SurfaceFlinger->Display
开发者选项->GPU呈现模式分析->在屏幕上显示为条形图,可以在屏幕上看到显示每帧绘制花费的时间,有条基准线是16ms,超过这条基线很可能出现掉帧。如果蓝线很长,则说明软件draw太费时,可通过traceview来继续分析draw的java代码。如果中间红色部分很长则说明是openGL ES绘制太费时,用gltrace来分析OpernGL ES调用过程。用systrace分析,用traceview来看代码,
App启动崩溃异常捕捉
自定义View注意事项
现在下载速度很慢,试从网络协议的角度分析原因,并优化(提示:网络的5层都可以涉及)。
Https请求慢的解决办法(提示:DNS,携带数据,直接访问IP)
如何保持应用的稳定性
1)需求明确清楚,编码时明确知道要实现的功能和实现方法,技术选型等,对一些库进行封装再使用。防止代码冗余、避免多线程导致的问题;
2)异常崩溃处理捕获,在使用对象前做判空处理等
3)提高编码质量,用lint\findbugs进行代码静态分析;
4)OOM和内存泄漏检测
5)框架测试,兼容性测试、单元测试、monkey测试
6)发布维度,灰度,选择部分渠道进行发布,收集问题;
7)热更新。
RecyclerView和ListView的性能对比
ListView的优化
RecycleView优化
View渲染
Bitmap如何处理大图,如一张30M的大图,如何预防OOM
java中的四种引用的区别以及使用场景
强引用置为null,会不会被回收?
如果对象没有被GC Roots引用到,会被GC回收到,但什么时候回收需根据JVM的特性什么时候触发GC操作。即使调用了System.gc() JVM也不一定会触发GC。
(八)NDK、jni、Binder、AIDL、进程通信有关
请介绍一下NDK
什么是NDK库?
jni用过吗?
如何在jni中注册native函数,有几种注册方式?
Java如何调用c、c++语言?
jni如何调用java层代码?
1)Java类中要调用jni方法,需要在java类中声明本地方法,public native void methodName();//本地方法。还要在类的静态代码块中导入so库 static { System.loadLibrary("MyJni");}
2)在C/C++获取类的对象的方法有两种:
a)通过c/c++创建java对象,通过对象获取类,通过类获取类的构造方法的ID,基于方法ID和类,创建新对象。
JNIEXPORT void JNICALL JAVA_nativeMethod(JNIEnv *env, jobject this, jint i) {
jclass clazz = (*env).GetObjectClass(thiz);
jmethodID mid = (*env).GetMethodID(clazz, "
jobject obj = (*env).NewObject(clazz, mid);
}
b) 通过c/c++创建不同类对象,通过FindClass方法获取需要的类;通过类获取类的构造方法的ID,基于方法ID和类,创建对象
JNIEXPORT void JNICALL JAVA_nativeMethod(JNIEnv *env, jobject this, jint i) {
jclass clazz = (*env).FindClass("com/packagepath/className");
jmethodID mid = (*env).GetMethodID(clazz, "
jobject obj = (*env).NewObject(clazz, mid);
}
调用java方法跟上面调用构造函数类似,获取类的方法ID,基于对象的方法id调用Java方法
JNIEXPORT void JNICALL JAVA_nativeMethod(JNIEnv *env, jobject thiz, jint i) {
jclass clazz = (*env).GetObjectClass(thiz);
m_Object = (*env).NewGlobalRef(thiz);
m_mid = (*env).GetMethodID(clazz, "methodName", "()V");//获取Java方法的ID
m_fid = (*env).GetFieldID(clazz, "a","I");//获取Java变量的ID
(*env).SetIntField(m_Object, m_fid, i);
(*env).CallVoidMethod(m_Object, m_mid);
}
进程间通信的方式?
Binder机制
简述IPC?
什么是AIDL?
AIDL解决了什么问题?
AIDL如何使用?
Android 上的 Inter-Process-Communication 跨进程通信时如何工作的?
多进程场景遇见过么?
Android进程分类?
前台进行(当前正在前台运行的进程,说明用户当前正在与该进程交互), 满足以下至少一个条件的叫做 foreground progcess:
a.有一个Activity在前台获得焦点可与用户互动
b.有一个 BroadcastReceiver组件正在运行onReceive()方法
c.有一个Sevice组件正在运行onCreate()/onStart()/onDestory()方法
可见进程(可见,但用户不能直接与之交互)满足以下条件之一称为可见进程:a.有一个Activity能被用户看见但是失去焦点(处于onPause()状态) b.有一个 Service调用了startForeground()方法 c.绑定了一个Service,系统将该Service作为一个特殊的用户知道的功能使用如自动更换壁纸,输入法服务等。
服务进程(拥有service的进程,一般在后台为用户服务的),通过startService()方法开启的没有绑定在activity上的Service的进程,Service长时间运行 (超过30分钟以上)会被降级到cached process
后台进程(对用户作用不大,缺少该进程一般不会影响用户对系统的体验)
空进程(一般作为缓存机制服务的)
进程和 Application 的生命周期?
进程调度
谈谈对进程共享和线程安全的认识
Android进程共享可通过共享用户ID来实现,
对于SharedPreferences想实现多进程共享需要设置MODE_MULTI_PROCESS,设置了这个Flag后,每次调用Context.getSharedPreferences时系统会重新从SP
SharedPreferences myPrefs = context.getSharedPreferences(MY_FILE_NAME, Context.MODE_MULTI_PROCESS | Context.MODE_PRIVATE);
谈谈对多进程开发的理解以及多进程应用场景
什么是协程?
(九)framework层、ROM定制、Ubuntu、Linux之类的问题
java虚拟机的特性
谈谈对jvm的理解
JVM内存区域,开线程影响哪块内存
对Dalvik、ART虚拟机有什么了解?
Art和Dalvik对比
虚拟机原理,如何自己设计一个虚拟机(内存管理,类加载,双亲委派)
谈谈你对双亲委派模型理解
JVM内存模型,内存区域
类加载机制
谈谈对ClassLoader(类加载器)的理解
谈谈对动态加载(OSGI)的理解
内存对象的循环引用及避免
内存回收机制、GC回收策略、GC原理时机以及GC对象
垃圾回收机制与调用System.gc()区别
System.gc()只是通知垃圾回收器要进行垃圾回收操作,但并没有立即执行垃圾回收。它只是建议JVM安排GC运行,还有可能被拒绝。
Ubuntu编译安卓系统
系统启动流程是什么?(提示:Zygote进程 –> SystemServer进程 –> 各种系统服务 –> 应用进程)
大体说清一个应用程序安装到手机上时发生了什么
简述Activity启动全部过程
1)Activity.startActivity-->startActivityForResult()
2)-->Instrumentation.execStartActivity()-->execStartActivity()
3)ActivityManager.getService().startActivity()通过Binder到ActivityManagerService.startActivity()
4)-->ActivityStarter.startActivityMayWait()-->startActivityLocked()-->startActivityUnchecked()
5)--ActivityStackSupervisor.resumeFocusedStackTopActivityLocked()-->ApplicationThread$scheduleLaunchActivity
6)ApplicationThread.schedulelaunchActivity()通过ActivityThread.sendMessage,再处理消息,进入handleLaunchActivity
-->Instrumentation$newActivity创建Activity的实例,使用类加载器创建Activity对象。
-->makeApplication创建Application对象,调用它的Application.onCreate()方法
Instrumentation这个类就是完成对Application和Activity初始化和生命周期的工具类。
App启动流程,从点击桌面开始
1)点击桌面App图标,Launcher进程采用Binder IPC向system_server进程发送startActivity()
2)system_server进程向Zygote发送创建进程的请求(AMS中通过startActivity()方法,调用startProcessLocked()函数),Zygote通过socket通信的方式让Zygote进程 fork一个新进程出来作为App进程;
3)App进程通过Binder IPC向system_server进程发起attachApplication请求(ActivityThread的main()函数里会创建Application,还调用ActivityStackSupervisor.attachApplicationLocked);
4)system_server进程收到请求后,进行一系列准备工作,再通过binder IPC向APP进程发送scheduleLaunchActivity请求;
5)App进程的binder线程(ApplicationThread)收到请求后,通过handler向主线程发送LAUNCHER_ACTIVITY消息;
6)主线程收到Message消息后,通过反射机制创建出Activity并回调Activity.onCreate()等方法;
7)App正式启动,开始进入Activity的生命周期。
逻辑地址与物理地址,为什么使用逻辑地址?
Android为每个应用程序分配的内存大小是多少?
根据应用实际使用情况分配,初始给进程分配为8M,应用最大可分配的可能是64M\128M\256M等
Android中进程内存的分配,能不能自己分配定额内存?
进程内存分配跟手机配置有关,不同手机可能不一样,有64M\128M\256M等,heapgrowthlimit是一个普通应用的内存限制,可通过ActivityManager.getLargeMemoryClass()获得,在mainfest中设置了largeHeap=true后,可以使应用可用内存增大到原来两倍。并不能自己定额分配内存,android系统是根据应用所需要内存大小,先分配初始大小heapstartsize,当应用申请更多内存,系统会再进行分配,但不得超过最大内存,超过了会报OOM。
进程保活的方式
1)模拟前台进程,startForeground()将当前进程伪装成前台进程,将进程优先级提高到最高,现在前台进程会显示在通知栏中,取消不掉。
2)JobScheduler机制唤醒,系统会根据自己实现定时去调用改接口传递进程去实现一些操作,且这个接口被强制停止后仍能正常启动。在调用JobSchedule.schedule()来启动任务。
3)实现Service类时,将onStartCommand()返回值设置为START_STICKY,利用系统机制在service挂掉后自动拉活,但这种方式只适合原生系统,像小米、华为等定制化比较高的第三方厂商,这些都限制了。
4)一像素的Activity,
5)应用之间相互唤醒。
如何保证一个后台服务不被杀死?(相同问题:如何保证service在后台不被kill?)比较省电的方式是什么?
App中唤醒其他进程的实现方式
1)启动其他进程的Activity\Service或是发送一条广播给相应的应用(该应用得静态注册此广播)
OOM定位与分析,如何定位哪块原因导致应用最终发生OOM?
OOM发生后,可以用Android Studio自带的Android Monitor dump出HPROF文件,再用SDK中hprof-conv工具转换为标准的Java堆转储文件格式,再用MAT继续分析。切换到histogram视图,按shadow heap降序排序,对实例对象占用内存大小排序,再查看实例到GC ROOT的路径。
一般可能导致的如图片:直接加载超大尺寸图片(对图片尺寸缩放预处理后再加载)、图片加载后未及时释放(利用LRU机制保证图片个数和占用内存);在页面中,加载非常多的图片(避免同时加载大量图片)
JNI层的crash如何捕获?
参考:JNI定位c++错误
通过ndk安装包中的addr2line objdump ndk-stack工具进行分析crash,ndk针对不同的CPU架构实现了多套相同的工具,在选择add2line objdump时要根据目标机器CPU架构来选择。
一般JNI发生异常,会出现一个Fatal signal信号量,大概知道是哪个函数引起的,再看下面的backtrace日志,backtrace是JNI调用堆栈信息,以“#两位数字 pc”开头,找到对应的函数,再用addr2line进行定位出错的位置。addr2line -C -f -e ./obj/armeabi/xxx.so 000eea70
使用ndk-stack协助我们获取底层崩溃的堆栈信息,adb logcat | ndk-stack -sym ./obj/armeabi/xxx.so
应用卡顿定位
1)使用UI线程的Looper打印日志
Android主线程更新UI,如果1S钟刷新少于60次,即FPS小于60,一帧加载超过16.67ms的话,用户就会产生卡顿的感觉。Android使用消息机制进行UI更新,UI线程中有个Looper,其loop方法会不断提取message,调用其绑定的Handler在UI线程执行。如果handler.dispatchMessage方法中有耗时操作,就会发生卡顿。如果有卡顿,就打印出UI线程的堆栈信息。
优点:用户使用app就可以监控卡顿情况,但因需另开子线程获取堆栈信息,会消耗系统资源。
2)使用Choreographer.FrameCallback监控卡顿
Android系统每16ms会发出SYNC信息,通知界面重绘、渲染,每一次同步的周期为16.6ms,代表一帧的刷新频率可以在两次回调时间周期来判断是否发生卡顿。(Android4.1以上才支持)。可以通过Choreographer.FrameCallback回调doFrame(long)函数,如果两次doFrame之间间隔大于16.67ms说明发生了卡顿。这种方法从app层面来监控卡顿,同时可实时计算帧率和掉帧数,实时监测APP页面的帧率数据,一旦发现帧率过低,可自动保存现场堆栈信息。
卡顿监控系统处理流程:开发修复-》用户上报(后台配置下灰度0.2%的用户量进行卡顿监控和上报,每个用户一天上报一次,上报后删除文件不影响存储空间)-》后台解析(过滤、去重、分类、反解堆栈、入库)-》平台展示-》自动提单
Http2.0有关多路复用:
多路复用原理:HTTP2流和多路复用 HTTP2.0原理详细解析
原理是二进制分帧层+流实现了多路复用,OKHttp是怎么支持的呢,那就是读帧,读流,Okhttp对http2的支持简单分析
ViewStub是怎么实现延时加载的?
ViewStub是一个不可见、大小为0的View。具体体现在ViewStub的构造函数中会进行设置setVisibility(GONE)设置控件不可见,同时会设置setWillNotDraw(true),即本View不会调用onDraw()方法绘制内容。在它的onMeasure函数中会调用setMeasureDimenssion(0, 0)即不会测量视图,直接设置一个大小为0的View.
对于ViewStub.inflate()机制是:1)调用LayoutInflate.flate(mLayoutResource, parent, false)来加载ViewStub中android:layout设置的布局view(最后一个参数false是代表attachToRoot设置成false,说明忽略android:layout中布局根节点的layoutParams参数);2)从父视图中获取当前ViewStub在父视图中位置(在加载父视图时会用一个占位符来代表ViewStub);3)将当前ViewStub从parent中移除;4)将android:layout中的布局view add到父视图中,如果StubView中设置了layoutParams属性就会用ViewStub中设置的。
应用场景:如网络加载失败页面,评论区域内的listView(当没有评论或是请求失败的时候不加载)
加载方式:findViewById(R.id.stubViewId).setVisibility(View.VISIBLE);或是((ViewStub)findViewById(R.id.StubViewId)).inflate();其实设置Visibility最终也是调用inflate来加载布局的。
如果ViewStub标签下写上width/height,在ViewStub相应的layout xml文件中也进行了宽高定义,会以谁为准?
其实上面也分析过了,会以ViewStub中设置的layoutParams为准。
ViewStub可不可以加载多次呢?
不能,上面也分析过了,ViewStub调用inflate()方法后,会把自己从父视图中移除掉,并把自身所包含的内容添加到父视图中,再重新加载,找不到在父视图了,就会抛出ViewStub must have a non-null ViewGroup viewparent。
merge标签
merge一般可以和include/ViewStub结合使用,当include/ViewStub从外部导入xml结构时,可以将被导入的xml用merge作为根节点表示,当被加载到父布局中可以将它们融合到父级结构中,而不会出现冗余的节点。因为它是直接将其中的子元素添加到merge标签的parent中,这样就保证不会引入额外的层级了。
注意:1)