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线程上。

  1. 服务不是单一的进程,服务没有自己的进程,应用程序可以不同,服务运行在相同的进程中
  2. 服务不是线程,可以在线程中工作
  3. 在应用中,如果是长时间的在后台运行,而且不需要交互的情况下,使用服务
  4. 同样是在后台运行,不需要交互的情况下,如果只是完成某个任务,之后就不需要运行,而且可能是多个任务,需要长时间运行的情况下使用线程
  5. 如果任务占用CPU时间多,资源大的情况下,要使用线程

Thread的运行是独立于Activity的,也就是说当一个Activity被finish之后,如果你没有主动停止Thread或者Thread里的run方法没有执行完毕的话,Thread就会一直执行。

8.View绘画机制

View的绘制主要涉及三个方法:onMeasure()、onLayout()、onDraw()

  1. onMeasure主要用于计算view的大小,onLayout主要用于确定view在ContentView中的位置,onDraw主要是绘制View。
  2. 在执行onMeasure()、onLayout()方法时都会通过相应的标志位或者对应的坐标点来判断是否需要执行对应的函数,如我们经常调用的invalidate方法就只会执行onDraw方法,因为此时的视图大小和位置均未发生变化,除非调用requestLayout方法完整强制进行view的绘制,从而执行上面三个方法。

进度条组件:

[ProgressView]
[AnnotationView]

9.事件传递机制

[android 事件处理机制总结,ScrollView ViewPager ListView GridView嵌套小结]

当手指触摸到屏幕时,系统就会调用相应View的onTouchEvent,并传入一系列的action。

dispatchTouchEvent的执行顺序为:

  1. 首先触发ACTIVITY的dispatchTouchEvent,然后触发ACTIVITY的onUserInteraction
  2. 然后触发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
@Override
public 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
@override
public 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优点:

  1. 系统性能的显著提升
  2. 应用启动更快、运行更快、体验更流畅、触感反馈更及时。
  3. 更长的电池
《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

浏览器打开:qq.cn.hn/FTe 免费领取

续航能力
4. 支持更低的硬件

ART缺点:

  1. 更大的存储空间占用,可能会增加10%-20%
  2. 更长的应用安装时间

Scroller执行流程里面的三个核心方法

  1. mScroller.startScroll()
  2. mScroller.computeScrollOffset()
  3. view.computeScroll()

1、在mScroller.startScroll()中为滑动做了一些初始化准备,比如:起始坐标,滑动的距离和方向以及持续时间(有默认值),动画开始时间等。

2、mScroller.computeScrollOffset()方法主要是根据当前已经消逝的时间来计算当前的坐标点。因为在mScroller.startScroll()中设置了动画时间,那么在computeScrollOffset()方法中依据已经消逝的时间就很容易得到当前时刻应该所处的位置并将其保存在变量mCurrX和mCurrY中。除此之外该方法还可判断动画是否已经结束。

12.Activity Manager Service, ActivityThread

13.Android几种进程

  1. 前台进程: 即与用户正在交互的Activity或者Activity用到的Service等,如果系统内存不足时前台进程是最后被杀死的
  2. 可见线程:可以是处于暂停状态(onPause)的Activity或者绑定在其上的Service,即被用户可见,但由于失去了焦点而不能与用户交互
  3. 服务进程:其中运行着使用startService方法启动的Service,虽然不被用户可见,但是却是用户关系的,例如用户正在非音乐界面听的音乐或者正在非下载页面自己下载的文件等,当系统要用空间运行前两者进程时才会被终止
  4. 后台进程:其中运行着执行onStop方法而停止的程序,但是却不是用户当前关心的,例如后台挂着的QQ,这样的进程系统一旦没有内存就首先被杀死。
  5. 空进程:不包含任何应用程序的程序组件的进程,这样的进程系统是一般不会让他存在的。

如何避免后台进程被杀死?

  1. 调用startForegound,让你的Service所在的进程成为前台进程
  2. Service的onStartCommand返回START_STICKY或START_REDELIVER_INTENT
  3. 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优化

  1. 首先,虽然大家都知道,还是提一下,利用好convertView来重用View,切忌每次getView都新建。ListView的核心原理就是重用View。ListView有一个回收器,Item滑出界面的时候view就会回收这里,需要显示新的Item的时候,就尽量重用回收器里面的View。
  2. 利用好ViewType,例如你的ListView中有几个类型的Item,需要给每个类型创建不同的View,这样有利于ListView的回收,当然类型不能太多
  3. 尽量让ItemView的Layout层次结构简单,这时所有Layout都必须遵守的
  4. 善用自定义View,自定义View可以有效的减小Layout的层级,而且对绘制过程可以很好的控制
  5. 尽量保证Adapter的hasStableIds()返回true,这样在notifyDataSetChanged()的时候,如果id不变,listView将不会重新绘制这个View,达到优化的目的。
  6. 每个item不能太高,特别是不要超出屏幕的高度,可以参考Facebook的优化方法,把特别复杂的Item分解为若干个小的Item.
  7. 为了保证ListView滑动的流畅性,getView()中要做尽量少的事情,不要有耗时的操作。特别是滑动的时候不要加载图片,停下来再加载。
  8. 使用RecyclerView代替。ListView每次更新数据都要notifyDataSetChanged(),有些太暴力了。RecyclerView在性能和可定制性上都有很大的改善,推荐使用。
  9. 有时候,需要从根本上考虑,是否真的要使用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支持JavaScript
webSettings.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排错一般有三种类型

  1. KeyDispatchTimeout(5 seconds) –主要类型按键或触摸事件在特定时间内无响应
  2. BroadcastTimeout(10 secends) –BroadcastReceiver在特定时间内无法处理完成
  3. ServiceTimeout(20 secends) –小概率事件 Service在特定的时间内无法处理完成

2、如何避免

  1. UI线程尽量只做跟UI相关的工作
  2. 耗时的操作(比如数据库操作,I/O,连接网络或者别的有可能阻塞UI线程的操作)把它放在单独的线程处理
  3. 尽量用Handler来处理UIthread和别的thread之间的交互

3、如何排查

  1. 首先分析log
  2. 从trace.txt文件查看调用stack,adb pull data/anr/traces.txt ./mytraces.txt
  3. 看代码
  4. 仔细查看ANR的成因(iowait?block?memoryleak?)

4、监测ANR的Watchdog

21.fragment生命周期

[图片上传中…(image-4517e1-1597835878604-0)]

常见面试问题=========

1、横竖屏切换时候Activity的生命周期

  1. 不设置Activityd饿android:configChanges时,切屏会重新掉哟过各个生命周期,切横屏时会执行一次,切竖屏时会执行两次
  2. 从trace.txt文件查看调用stack,adb pull data/anr/traces.txt ./mytraces.txt
  3. 看代码
  4. 仔细查看ANR的成因(iowait?block?memoryleak?)

4、监测ANR的Watchdog

21.fragment生命周期

[图片上传中…(image-4517e1-1597835878604-0)]

常见面试问题=========

1、横竖屏切换时候Activity的生命周期

  1. 不设置Activityd饿android:configChanges时,切屏会重新掉哟过各个生命周期,切横屏时会执行一次,切竖屏时会执行两次
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值