2020-Android- 面试重难点(万字篇),android 屏幕适配的五种方式
5.NDK
Dalvik 虚拟机在调用一个成员函数的时候,如果发现该成员函数是一个 JNI 方法,那么就会直接跳到它的地址去执行。也就是说,JNI 方法是直接在本地操作系统执行的,而不是 Dalvik 虚拟机解释器执行。由此也可以看出,JNI 方法是 Android 应用程序与本地操作系统直接进行通信的一个手段。
JNI 原理:
[Dalvik 虚拟机 JNI 方法的注册过程分析]
例子:当 libnanosleep.so 文件被加载的时候,函数 JNI_OnLoad 就会被调用。在函数 JNI_OnLoad 中,参数 vm 描述的是当前线程中的 Dalvik 虚拟机,通过调用它的成员函数 GetEnv 就可以获得一个 JNIEnv 对象。有了这个 JNIEnv 对象之后,我们就可以调用另外一个函数 jniRegisterNativeMethods 来向当前进程的 Dalvik 虚拟机注册一个 JNI 方法。
6.Android 系统启动过程,App 启动过程
App 启动过程:
[Activity 启动过程详解]
从桌面点击到 activity 启动的过程
1、Launcher 线程捕获 onclick 的点击事件,调用 Launcher.startActivitySafely,进一步调用 Launcher.startActivity,最后调用父类 Activity 的 startActivity。
2、Activity 和 ActivityManagerService 交互,引入 Instrumentation,将启动请求交给 Instrumentation,调用 Instrumentation.execStartActivity。
3、调用 ActivityManagerService 的 startActivity 方法,这里做了进程切换(具体过程请查看源码)。
4、开启 Activity,调用 onCreate 方法
7.Activity,Fragment,Service 生命周期
常见的例子:程序正运行着来电话了,这个程序咋办呢?中止了呗,如果中止的时候新出的一个 Activity 是全屏的 onPause->onStop,恢复的时候 onStart->onResume,如果打断这个应用程序的是一个 Theme 为 Translucent 或者 Dialog 的 Activity 那么只是 onPause,恢复的时候 onResume。
onPause:恢复的时候 onResume
onCreate:在这里创建界面,做一些数据的初始化工作
onStart:到这一步变成用户可见不可交互的
onPause:到这一步是可见但不可交互的,系统会停止动画等消耗 CPU 的事情,应该在这里保存你的一些 数据,因为这个时候你的程序的优先级降低,有可能被 系统回收。在这里保存的数据,应该在 onResume 里读出来。注意:这个方法里做的事情时间要短,因为下一个 Activity 不会等到这个方法完成才启动。
onStop:变得不可见,被下一个 Activity 覆盖了(onPause 和 onStop 的区别是否可见)
onDestroy:这是 Activity 被干掉前最后一个被调用方法了,可能是外面类调用 finish 方法或者是系统为了节省空间将它暂时性的干掉,可以用 isFinishing()来判断它,如果你有一个 ProgressDialog 在线程中转动,请在 onDestroy 里把它 cancel 掉,不然等线程结束的时候,调用 Dialog 的 cancel 会抛出异常的。
onPause,onstop,onDestroy,三种状态下,Activity 都有可能被系统干掉。
启动另一个 Activity 然后 finish,先调用旧 Activity 的 onPause 方法,然后调用新的 Activity 和 onCreate->onStart->onResume 方法,然后调用旧 Activity 的 onStop->onDestroy 方法。
如果没有调用 finish 那么 onDestroy 方法不会被调用,而且在 onStop 之前还会调用 onSavedInstanceState 方法
onRestart 方法执行完了之后还会调用 onStart 方法
fragment:[SupportFragmentManager,childFragment]
service:
[Android Service 的生命周期]
[android-Service 和 Thread 的区别]
Service 和 Intent Service:没啥区别,只是 IntentService 在 onCreate 方法中开启新的 HandlerThread 去执行。
Service 运行的进程和线程:当它运行的时候如果是 LocalService,那么对应的 Service 是运行在主进程的 main 线程上的。如 onCreate,onStart 这些函数都是在系统调用的时候在主进程的 main 线程上运行的。如果是 RemoteSevice,那么对应的 Service 则是运行在独立的 main 线程上。
服务不是单一的进程,服务没有自己的进程,应用程序可以不同,服务运行在相同的进程中
服务不是线程,可以在线程中工作
在应用中,如果是长时间的在后台运行,而且不需要交互的情况下,使用服务
同样是在后台运行,不需要交互的情况下,如果只是完成某个任务,之后就不需要运行,而且可能是多个任务,需要长时间运行的情况下使用线程
如果任务占用 CPU 时间多,资源大的情况下,要使用线程
Thread 的运行是独立于 Activity 的,也就是说当一个 Activity 被 finish 之后,如果你没有主动停止 Thread 或者 Thread 里的 run 方法没有执行完毕的话,Thread 就会一直执行。
8.View 绘画机制
View 的绘制主要涉及三个方法:onMeasure()、onLayout()、onDraw()
onMeasure 主要用于计算 view 的大小,onLayout 主要用于确定 view 在 ContentView 中的位置,onDraw 主要是绘制 View。
在执行 onMeasure()、onLayout()方法时都会通过相应的标志位或者对应的坐标点来判断是否需要执行对应的函数,如我们经常调用的 invalidate 方法就只会执行 onDraw 方法,因为此时的视图大小和位置均未发生变化,除非调用 requestLayout 方法完整强制进行 view 的绘制,从而执行上面三个方法。
进度条组件:
[ProgressView][AnnotationView]
9.事件传递机制
[android 事件处理机制总结,ScrollView ViewPager ListView GridView 嵌套小结]
当手指触摸到屏幕时,系统就会调用相应 View 的 onTouchEvent,并传入一系列的 action。
dispatchTouchEvent 的执行顺序为:
首先触发 ACTIVITY 的 dispatchTouchEvent,然后触发 ACTIVITY 的 onUserInteraction
然后触发 LAYOUT 的 dispatchTouchEvent,然后触发 LAYOUT 的 onInterceptTouchEvent
这就解释了重写 ViewGroup 时必须调用 super.dispatchTouchEvent();
(1)dispatchTouchEvent:
此方法一般用于初步处理事件,因为动作是由此分发,所以通常会调用 super.dispatchTouchEvent。这样就会继续调用 onInterceptTouchEvent,再由 onInterceptTouchEvent 决定事件流向。
(2)onInterceptTouchEvent:
若返回值为 true 事件会传递到自己的 onTouchEvent();若返回值为 false 传递到下一个 View 的 dispatchTouchEvent();
(3)onTouchEvent():
若返回值为 true,事件由自己消耗,后续动作让其处理;若返回值为 false,自己不消耗事件了,向上返回让其他的父 View 的 onTouchEvent 接受处理
三大方法关系的伪代码:如果当前 View 拦截事件,就交给自己的 onTouchEvent 去处理,否则就丢给子 View 继续走相同的流程。
public boolean dispatchTouchEvent(MotionEvent ev){boolean consume = false;if(onInterceptTouchEvent(ev)){consume = onTouchEvent(ev);}else{consume = child.dispatchTouchEvent(ev);}return consume;}
onTouchEvent 的传递:
当有多个层级的 View 时,在父层级允许的情况下,这个 action 会一直传递直到遇到最深层的 View。所以 touch 事件最先调用的是最底层 View 的 onTouchEvent,如果 View 的 onTouchEvent 接收到某个 touch action 并做了相应处理,最后有两种返回方式 return true 和 return false;return true 会告诉系统当前的 View 需要处理这次的 touch 事件,以后的系统发出的 ACTION_MOVE,ACTION_UP 还是需要继续监听并接收的,并且这次的 action 已经被处理掉了,父层的 View 是不可能触发 onTouchEvent 的了。所以每一个 action 最多只能有一个 onTouchEvent 接口返回 true。如果返回 false,便会通知系统,当前 View 不关心这一次的 touch 事件,此时这个 action 会传向父级,调用父级 View 的 onTouchEvent。但是这一次的 touch 事件之后发出任何 action,该 View 都不在接受,onTouchEvent 在这一次的 touch 事件中再也不会触发,也就是说一旦 View 返回 false,那么之后的 ACTION_MOVE,ACTION_UP 等 ACTION 就不会在传入这个 View,但是下一次 touch 事件的 action 还是会传进来的。
父层的 onInterceptTouchEvent
前面说了底层的 View 能够接收到这次的事件有一个前提条件:在父层允许的情况下。假设不改变父层级的 dispatch 方法,在系统调用底层 onTouchEvent 之前会调用父 View 的 onInterceptTouchEvent 方法判断,父层 View 是否要截获本次 touch 事件之后的 action。如果 onInterceptTouchEvent 返回了 true,那么本次 touch 事件之后的所有 action 都不会向深层的 View 传递,统统都会传给父层 View 的 onTouchEvent,就是说父层已经截获了这次 touch 事件,之后的 action 也不必询问 onInterceptTouchEvent,在这次的 touch 事件之后发出的 action 时 onInterceptTouchEvent 不会再被调用,直到下一次 touch 事件的来临。如果 onInterceptTouchEvent 返回 false,那么本次 action 将发送给更深层的 View,并且之后的每一次 action 都会询问父层的 onInterceptTouchEvent 需不需要截获本次 touch 事件。只有 ViewGroup 才有 onInterceptTouchEvent 方法,因为一个普通的 View 肯定是位于最深层的 View,只有 ViewGroup 才有 onInterceptTouchEvent 方法,因为一个普通的 View 肯定是位于最深层的 View,touch 能够传到这里已经是最后一站了,肯定会调用 View 的 onTouchEvent()。
底层 View 的 getParent().requestDisallowInterceptTouchEvent(true)
对于底层的 View 来说,有一种方法可以阻止父层的 View 获取 touch 事件,就是调用 getParent().requestDisallowInterceptTouchEvent(true)方法。一旦底层 View 收到 touch 的 action 后调用这个方法那么父层 View 就不会再调用 onInterceptTouchEvent 了,也无法截获以后的 action(如果父层 ViewGroup 和最底层 View 需要截获不同焦点,或不同手势的 touch,不能使用这个写死)。
曾经开发过程中遇到的两个示例:左边是处理 ViewPager 和 ListView 的冲突,纪录水平和垂直方向的偏移量,如果水平方向的偏移更多的话就让 ViewPager 处理 pager 滑动
右边处理的 ViewPager 和 ImageBanner 的滑动冲突,同样是纪录偏移量,如果发生在 ImageBanner 上的水平偏移量大于垂直偏移量的话就让 banner 滚动
想想为什么右边是重写 dispatchTouchEvent 方法而不是 onInterceptTouchEvent 方法?
FixedViewPager@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev){switch(ev.getAction() & MotionEvent.ACTION_MASK){case MotionEvent.ACTION_DOWN:mX = ev.getX();mY = ev.getY();break;case MotionEvent.ACTION_MOVE:float x = ev.getX();float y = ev.getY();float dX = x - mX;float dY = y - mY;float tmp = Math.abs(dX) / Math.abs(dY);mX = x;mY = y;if(tmp > 1){return true;}else{return super.omInterceptTouchEvent(ev);}}}
FixedImageLoadBanner@overridepublic boolean dispatchTouchEvent(MotionEvent ev){if(mX != 0 || mY != 0){float dY = ev.getRawY() - mY;float dX = ev.getRawX() - mX;if(Math.abs(dY) > Math.abs(dX)){requestDisallowInterceptTouchEvent(false);}else{requestDisallowInterceptTouchEvent(true);}}mX = ev.getRawX();mY = ev.getRawY();return super.dispatchTouchEvent(ev);}
10.ART 和 Dalvik 区别
art 上应用启动快,运行快,但是耗费更多存储空间,安装时间长,总的来说 ART 的功效就是”空间换时间”。
ART: Ahead of Time Dalvik: Just in Time
什么是 Dalvik:Dalvik 是 Google 公司自己设计用于 Android 平台的 Java 虚拟机。Dalvik 虚拟机是 Google 等厂商合作开发的 Android 移动设备平台的核心组成部分之一,它可以支持已转换为.dex(即 Dalvik Executable)格式的 Java 应用程序的运行,.dex 格式是专为 Dalvik 应用设计的一种压缩格式,适合内存和处理器速度有限的系统。Dalvik 经过优化,允许在有限的内存中同时运行多个虚拟机的实例,并且每一个 Dalvik 应用作为独立的 Linux 进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。
什么是 ART:Android 操作系统已经成熟,Google 的 Android 团队开始将注意力转向一些底层组件,其中之一是负责应用程序运行的 Dalvik 运行时。Google 开发者已经花了两年时间开发更快执行效率更高更省电的替代 ART 运行时。ART 代表 Android Runtime,其处理应用程序执行的方式完全不同于 Dalvik,Dalvik 是依靠一个 Just-In-Time(JIT)编译器去解释字节码。开发者编译后的应用代码需要通过一个解释器在用户的设备上运行,这一机制并不高效,但让应用能更容易在不同硬件和架构上运行。ART 则完全改变了这套做法,在应用安装的时候就预编译字节码到机器语言,这一机制叫 Ahead-Of-Time(AOT)编译。在移除解释代码这一过程后,应用程序执行将更有效率,启动更快。
ART 优点:
系统性能的显著提升
应用启动更快、运行更快、体验更流畅、触感反馈更及时。
更长的电池
续航能力 4. 支持更低的硬件
ART 缺点:
更大的存储空间占用,可能会增加 10%-20%
更长的应用安装时间
Scroller 执行流程里面的三个核心方法
mScroller.startScroll()
mScroller.computeScrollOffset()
view.computeScroll()
1、在 mScroller.startScroll()中为滑动做了一些初始化准备,比如:起始坐标,滑动的距离和方向以及持续时间(有默认值),动画开始时间等。
2、mScroller.computeScrollOffset()方法主要是根据当前已经消逝的时间来计算当前的坐标点。因为在 mScroller.startScroll()中设置了动画时间,那么在 computeScrollOffset()方法中依据已经消逝的时间就很容易得到当前时刻应该所处的位置并将其保存在变量 mCurrX 和 mCurrY 中。除此之外该方法还可判断动画是否已经结束。
12.Activity Manager Service, ActivityThread
13.Android 几种进程
前台进程: 即与用户正在交互的 Activity 或者 Activity 用到的 Service 等,如果系统内存不足时前台进程是最后被杀死的
可见线程:可以是处于暂停状态(onPause)的 Activity 或者绑定在其上的 Service,即被用户可见,但由于失去了焦点而不能与用户交互
服务进程:其中运行着使用 startService 方法启动的 Service,虽然不被用户可见,但是却是用户关系的,例如用户正在非音乐界面听的音乐或者正在非下载页面自己下载的文件等,当系统要用空间运行前两者进程时才会被终止
后台进程:其中运行着执行 onStop 方法而停止的程序,但是却不是用户当前关心的,例如后台挂着的 QQ,这样的进程系统一旦没有内存就首先被杀死。
空进程:不包含任何应用程序的程序组件的进程,这样的进程系统是一般不会让他存在的。
如何避免后台进程被杀死?
调用 startForegound,让你的 Service 所在的进程成为前台进程
Service 的 onStartCommand 返回 START_STICKY 或 START_REDELIVER_INTENT
Service 的 onDestroy 里面重新启动自己
14.Activity 启动模式
standard:Activity 的默认加载方式,该方法会通过跳转到一个新的 Activity,同时将该实例压入到栈中(不管该 Activity 是否已经存在在 Task 栈中,都是采用 new 操作,生命周期从 onCreate()开始)。例如:栈中顺序是 A B C D,此时 D 通过 Intent 跳转到 A,那么栈中结构就变成 A B C D A,点击返回按钮的显示顺序是 D C B A,依次摧毁。
singleTop:singleTop 模式下,当前 Activity D 位于栈顶的时候,如果通过 Intent 跳转到它本身的 Activity(D),那么不会重新创建一个新的 D 实例(走 onNewIntent()),所以栈中的结构依次为 A B C D,如果跳转到 B,那么由于 B 不处于栈顶,所以会新建一个 B 实例并压入到栈中,结构就变成了 A B C D B。应用实例:三条推送,点进去都是一个 Activity,这肯定用 singletop
singleTask:singleTask 模式下,Task 栈中只能有一个对应的 Activity 实例。例如:Task 栈 1 中结构为:A B C D。此时 D 通过 Intent 跳转到 B(走 onNewIntent()),则栈的结构变成了:A,B。其中的 C 和 D 被栈弹出销毁了,也就是说位于 B 之上的实例都被销毁了。通常应用于首页,首页肯定在栈底部,也只能在栈底部。
singleInstance:singleInstance 模式下,会将打开的 Activity 压入一个新的任务栈中。例如:Task 栈 1 中结构为:A B C,C 通过 Intent 跳转到了 D(D 的模式为 singleInstance),那么则会新建一个 Task,栈 1 中结构依旧为 A B C,栈 2 中结构为 D。此时屏幕显示 D,之后 D 通过 Intent 跳转到 D,栈 2 不会压入新的 D,所以两个栈中的情况没发生改变。如果 D 跳转到了 C,那么就会根据 C 对应的 launchMode 在栈 1 中进行对应的操作,C 如果为 standard,那么 D 跳转到 C,栈 1 的结构为 A B C C ,此时点击返回按钮,还是在 C,栈 1 的结构变为 A B C,而不会回到 D。
launchMode 为 singleTask 的时候,通过 Intent 启动到一个 Activity,如果系统已经存在一个实例,系统就会将请求发送到这个实例上,但这个时候,系统就不会再调用通常情况下我们处理请求数据的 onCreate 方法,而不是调用 onNewIntent 方法。
onSavedInstanceState 的调用遵循一个重要原则,即当系统”未经你许可”时销毁了你的 Activity,则 onSavedInstanceState 会被系统调用,这时系统的责任,因为它必须要提供一个机会让你保存你的数据,至于 onRestoreInstanceState 方法,需要注意的是,onSavedInstanceState 方法和 onRestoreInstanceState 方法”不一定”是成对调用的。
onRestoreInstanceState 被调用的前提是,Activity A 确实被系统销毁了,而如果仅仅是停留在有这种可能性的情况下,则该方法不会被调用,例如,当正在显示 Activity A 的时候,用户按下 HOME 键回到主界面,然后用户紧接着又返回到 Activity A,这种情况下 Activity A 一般不会因为内存的原因被销毁,故 Activity 的 onRestoreInstanceState 方法不会被执行。
另外,onRestoreInstanceStated 的 bundle 参数也会传递到 onCreate 方法中,你也可以选择在 onCreate 方法中做数据还原。
onSavedInstanceState(Bundle bundle)通常和 onRestoreInstanceState(Bundle bundle)不会成对出现,onRestoreInstanceState 这玩意不太好触发,给大家提个好办法,横竖屏切换的时候 100%会触发。然后保存在 onRestoreInstanceState bundle 里面的数据,就是 onCreate 的那个参数 bundle 啦,要怎么恢复就看开发者了。
15.TMImageView 图片库的设计
Feature 机制
16.ListView 优化
首先,虽然大家都知道,还是提一下,利用好 convertView 来重用 View,切忌每次 getView 都新建。ListView 的核心原理就是重用 View。ListView 有一个回收器,Item 滑出界面的时候 view 就会回收这里,需要显示新的 Item 的时候,就尽量重用回收器里面的 View。
利用好 ViewType,例如你的 ListView 中有几个类型的 Item,需要给每个类型创建不同的 View,这样有利于 ListView 的回收,当然类型不能太多
尽量让 ItemView 的 Layout 层次结构简单,这时所有 Layout 都必须遵守的
善用自定义 View,自定义 View 可以有效的减小 Layout 的层级,而且对绘制过程可以很好的控制
尽量保证 Adapter 的 hasStableIds()返回 true,这样在 notifyDataSetChanged()的时候,如果 id 不变,listView 将不会重新绘制这个 View,达到优化的目的。
每个 item 不能太高,特别是不要超出屏幕的高度,可以参考 Facebook 的优化方法,把特别复杂的 Item 分解为若干个小的 Item.
为了保证 ListView 滑动的流畅性,getView()中要做尽量少的事情,不要有耗时的操作。特别是滑动的时候不要加载图片,停下来再加载。
使用 RecyclerView 代替。ListView 每次更新数据都要 notifyDataSetChanged(),有些太暴力了。RecyclerView 在性能和可定制性上都有很大的改善,推荐使用。
有时候,需要从根本上考虑,是否真的要使用 listView 来实现你的需求,或者是否有其他选择?
17.webView
如何使用 webview 在 js 中调用 java 方法?
webView.addJavaScriptInterface(new Object(){xxx}, "xxx");
答案:可以使用 WebView 控件执行 JavaScript 脚本,并且可以在 JavaScript 中执行 Java 代码。要想让 WebView 控件执行 JavaScript,需要调用 WebSettings.setJavaScriptEnabled 方法,代码如下:
WebView webView = (WebView)findViewById(R.id.webview)WebSettings webSettings = webView.getSettings()//设置 WebView 支持 JavaScriptwebSettings.setJavaScriptEnabled(true)webView.setWebChromeClient(new WebChromeClient())
JavaScript 调用 Java 方法需要使用 WebView.addJavascriptInterface 方法设置 JavaScript 调用的 Java 方法,代码如下:
webView.addJavascriptInterface(new Object(){
public String process(String value){
return result;}}, "demo");
可以使用下面的 JavaScript 代码调用 process 方法,代码如下:
function search(){
result.innerHTML = "" + window.demo.process('data') + "";}
18.SurfaceView 和 View 的最本质的区别
SurfaceView 是在一个新起的单独线程中可以重新绘制画面,而 view 必须在 UI 的主线程中更新画面。
在 UI 的主线程中更新画面可能会引发问题,比如你更新的时间过长,那么你的主 UI 线程就会被你正在画的函数阻塞。那么将无法响应按键、触屏等消息。当使用 SurfaceView 由于是在新的线程中更新画面所以不会阻塞你的 UI 主线程。但这也带来了另外一个问题,就是事件同步。比如你触屏了一下,你需要 SurfaceView 中 thread 处理,一般就需要有一个 event queue 的设计来保存 touchevent,这会稍稍复杂一点,因为涉及到线程安全。
19.标签
[merge 和 include]
[Android ViewStub 的基本使用]简言之,都是用来解决重复布局的问题,但是标签能够在布局重用的时候减少 UI 层级结构。viewStub 标签是用来给其他的 View 事先占据好位置,当需要的时候用 inflater()或者是 setVisible()方法显示这些 View。
20.ANR 排错
1、ANR 排错一般有三种类型
KeyDispatchTimeout(5 seconds) –主要类型按键或触摸事件在特定时间内无响应
BroadcastTimeout(10 secends) –BroadcastReceiver 在特定时间内无法处理完成
ServiceTimeout(20 secends) –小概率事件 Service 在特定的时间内无法处理完成
2、如何避免
UI 线程尽量只做跟 UI 相关的工作
耗时的操作(比如数据库操作,I/O,连接网络或者别的有可能阻塞 UI 线程的操作)把它放在单独的线程处理
尽量用 Handler 来处理 UIthread 和别的 thread 之间的交互
3、如何排查
首先分析 log
从 trace.txt 文件查看调用 stack,adb pull data/anr/traces.txt ./mytraces.txt
看代码
仔细查看 ANR 的成因(iowait?block?memoryleak?)
4、监测 ANR 的 Watchdog
21.fragment 生命周期
[图片上传中...(image-4517e1-1597835878604-0)]
======================常见面试问题===============================
1、横竖屏切换时候 Activity 的生命周期
不设置 Activityd 饿 android:configChanges 时,切屏会重新掉哟过各个生命周期,切横屏时会执行一次,切竖屏时会执行两次
评论