美文网首页Android开发经验谈
2020阿里巴巴,字节跳动,京东,小米,三星等大厂面试真题,牛客

2020阿里巴巴,字节跳动,京东,小米,三星等大厂面试真题,牛客

作者: 程序员面试秘籍 | 来源:发表于2020-10-11 22:27 被阅读0次

    前言

    很多人面试之前,可能没有在互联网公司工作过或者说工作过但年头较短,不知道互联网公司技术面试都会问哪些问题? 再加上可能自己准备也不充分,去面试没几个回合就被面试官几个问题打蒙了,最后以惨败收场。

    下述是我收录整理的Android面试题汇总,由于篇幅原因,在这之前都是把每一小节分开列出来,这次全部发出来,后续还会更新其余面试题内容,大家可以 关注 一下我,及时知晓我更新的知识点,同时这份面试集锦的整理也花费了我很多时间,有需要的朋友可以帮忙 转发分享 下,点个 ~

    这份集锦很长,喜欢的朋友可以 收藏 一下,慢慢品尝~

    大厂面试真题自行下载直达领取链接:【https://links.jianshu.com/go?to=https%3A%2F%2Fjq.qq.com%2F%3F_wv%3D1027%26k%3DBRZhpPkt

    1、常规知识点

    1、 Android类加载器

    在Android开发中,不管是插件化还是组件化,都是基于Android系统的类加载器ClassLoader来设计的。只不过Android平台上虚拟机运行的是Dex字节码,一种对class文件优化的产物,传统Class文件是一个Java源码文件会生成一个.class文件,而Android是把所有Class文件进行合并、优化,然后再生成一个最终的class.dex,目的是把不同class文件重复的东西只需保留一份,在早期的Android应用开发中,如果不对Android应用进行分dex处理,那么最后一个应用的apk只会有一个dex文件。

    Android中常用的类加载器有两种,DexClassLoader和PathClassLoader,它们都继承于BaseDexClassLoader。区别在于调用父类构造器时,DexClassLoader多传了一个optimizedDirectory参数,这个目录必须是内部存储路径,用来缓存系统创建的Dex文件。而PathClassLoader该参数为null,只能加载内部存储目录的Dex文件。所以我们可以用DexClassLoader去加载外部的apk文件,这也是很多插件化技术的基础。

    2、 Service

    理解Android的Service,可以从以下几个方面来理解:

    • Service是在main Thread中执行,Service中不能执行耗时操作(网络请求,拷贝数据库,大文件)。
    • 可以在xml中设置Service所在的进程,让Service在另外的进程中执行。
    • Service执行的操作最多是20s,BroadcastReceiver是10s,Activity是5s。
    • Activity通过bindService(Intent,ServiceConnection,flag)与Service绑定。
    • Activity可以通过startService和bindService启动Service。

    IntentService

    IntentService是一个抽象类,继承自Service,内部存在一个ServiceHandler(Handler)和HandlerThread(Thread)。IntentService是处理异步请求的一个类,在IntentService中有一个工作线程(HandlerThread)来处理耗时操作,启动IntentService的方式和普通的一样,不过当执行完任务之后,IntentService会自动停止。另外可以多次启动IntentService,每一个耗时操作都会以工作队列的形式在IntentService的onHandleIntent回调中执行,并且每次执行一个工作线程。IntentService的本质是:封装了一个HandlerThread和Handler的异步框架。

    2.1、生命周期示意图

    Service 作为 Android四大组件之一,应用非常广泛。和Activity一样,Service 也有一系列的生命周期回调函数,具体如下图。

    通常,启动Service有两种方式,startService和bindService方式。

    2.2、startService生命周期

    当我们通过调用了Context的startService方法后,我们便启动了Service,通过startService方法启动的Service会一直无限期地运行下去,只有在外部调用Context的stopService或Service内部调用Service的stopSelf方法时,该Service才会停止运行并销毁。

    onCreate

    onCreate: 执行startService方法时,如果Service没有运行的时候会创建该Service并执行Service的onCreate回调方法;如果Service已经处于运行中,那么执行startService方法不会执行Service的onCreate方法。也就是说如果多次执行了Context的startService方法启动Service,Service方法的onCreate方法只会在第一次创建Service的时候调用一次,以后均不会再次调用。我们可以在onCreate方法中完成一些Service初始化相关的操作。

    onStartCommand

    onStartCommand: 在执行了startService方法之后,有可能会调用Service的onCreate方法,在这之后一定会执行Service的onStartCommand回调方法。也就是说,如果多次执行了Context的startService方法,那么Service的onStartCommand方法也会相应的多次调用。onStartCommand方法很重要,我们在该方法中根据传入的Intent参数进行实际的操作,比如会在此处创建一个线程用于下载数据或播放音乐等。

    public @StartResult int onStartCommand(Intent intent, @StartArgFlags int flags, int startId) {
    }
    
    

    当Android面临内存匮乏的时候,可能会销毁掉你当前运行的Service,然后待内存充足的时候可以重新创建Service,Service被Android系统强制销毁并再次重建的行为依赖于Service中onStartCommand方法的返回值。我们常用的返回值有三种值,START_NOT_STICKYSTART_STICKYSTART_REDELIVER_INTENT,这三个值都是Service中的静态常量。

    START_NOT_STICKY

    如果返回START_NOT_STICKY,表示当Service运行的进程被Android系统强制杀掉之后,不会重新创建该Service,当然如果在其被杀掉之后一段时间又调用了startService,那么该Service又将被实例化。那什么情境下返回该值比较恰当呢?如果我们某个Service执行的工作被中断几次无关紧要或者对Android内存紧张的情况下需要被杀掉且不会立即重新创建这种行为也可接受,那么我们便可将 onStartCommand的返回值设置为START_NOT_STICKY。举个例子,某个Service需要定时从服务器获取最新数据:通过一个定时器每隔指定的N分钟让定时器启动Service去获取服务端的最新数据。当执行到Service的onStartCommand时,在该方法内再规划一个N分钟后的定时器用于再次启动该Service并开辟一个新的线程去执行网络操作。假设Service在从服务器获取最新数据的过程中被Android系统强制杀掉,Service不会再重新创建,这也没关系,因为再过N分钟定时器就会再次启动该Service并重新获取数据。

    START_STICKY

    如果返回START_STICKY,表示Service运行的进程被Android系统强制杀掉之后,Android系统会将该Service依然设置为started状态(即运行状态),但是不再保存onStartCommand方法传入的intent对象,然后Android系统会尝试再次重新创建该Service,并执行onStartCommand回调方法,但是onStartCommand回调方法的Intent参数为null,也就是onStartCommand方法虽然会执行但是获取不到intent信息。如果你的Service可以在任意时刻运行或结束都没什么问题,而且不需要intent信息,那么就可以在onStartCommand方法中返回START_STICKY,比如一个用来播放背景音乐功能的Service就适合返回该值。

    START_REDELIVER_INTENT

    如果返回START_REDELIVER_INTENT,表示Service运行的进程被Android系统强制杀掉之后,与返回START_STICKY的情况类似,Android系统会将再次重新创建该Service,并执行onStartCommand回调方法,但是不同的是,Android系统会再次将Service在被杀掉之前最后一次传入onStartCommand方法中的Intent再次保留下来并再次传入到重新创建后的Service的onStartCommand方法中,这样我们就能读取到intent参数。只要返回START_REDELIVER_INTENT,那么onStartCommand重的intent一定不是null。如果我们的Service需要依赖具体的Intent才能运行(需要从Intent中读取相关数据信息等),并且在强制销毁后有必要重新创建运行,那么这样的Service就适合返回START_REDELIVER_INTENT。

    onBind

    Service中的onBind方法是抽象方法,所以Service类本身就是抽象类,也就是onBind方法是必须重写的,即使我们用不到。在通过startService使用Service时,我们在重写onBind方法时,只需要将其返回null即可。onBind方法主要是用于给bindService方法调用Service时才会使用到。

    onDestroy

    onDestroy: 通过startService方法启动的Service会无限期运行,只有当调用了Context的stopService或在Service内部调用stopSelf方法时,Service才会停止运行并销毁,在销毁的时候会执行Service回调函数。

    2.3、bindService生命周期

    bindService方式启动Service主要有以下几个生命周期函数:

    onCreate():

    首次创建服务时,系统将调用此方法。如果服务已在运行,则不会调用此方法,该方法只调用一次。

    onStartCommand():

    当另一个组件通过调用startService()请求启动服务时,系统将调用此方法。

    onDestroy():

    当服务不再使用且将被销毁时,系统将调用此方法。

    onBind():

    当另一个组件通过调用bindService()与服务绑定时,系统将调用此方法。

    onUnbind():

    当另一个组件通过调用unbindService()与服务解绑时,系统将调用此方法。

    onRebind():

    当旧的组件与服务解绑后,另一个新的组件与服务绑定,onUnbind()返回true时,系统将调用此方法。

    3、fragemnt

    3.1、创建方式

    (1)静态创建

    首先我们需要创建一个xml文件,然后创建与之对应的java文件,通过onCreatView()的返回方法进行关联,最后我们需要在Activity中进行配置相关参数即在Activity的xml文件中放上fragment的位置。

     <fragment
            android:name="xxx.BlankFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        </fragment>
    
    
    (2)动态创建

    动态创建Fragment主要有以下几个步骤:

    1. 创建待添加的fragment实例。
    2. 获取FragmentManager,在Activity中可以直接通过调用 getSupportFragmentManager()方法得到。
    3. 开启一个事务,通过调用beginTransaction()方法开启。
    4. 向容器内添加或替换fragment,一般使用repalce()方法实现,需要传入容器的id和待添加的fragment实例。
    5. 提交事务,调用commit()方法来完成。

    3.2、Adapter对比

    FragmnetPageAdapter在每次切换页面时,只是将Fragment进行分离,适合页面较少的Fragment使用以保存一些内存,对系统内存不会多大影响。

    FragmentPageStateAdapter在每次切换页面的时候,是将Fragment进行回收,适合页面较多的Fragment使用,这样就不会消耗更多的内存

    3.3、Activity生命周期

    Activity的生命周期如下图:

    (1)动态加载:

    动态加载时,Activity的onCreate()调用完,才开始加载fragment并调用其生命周期方法,所以在第一个生命周期方法onAttach()中便能获取Activity以及Activity的布局的组件;

    (2)静态加载:

    1.静态加载时,Activity的onCreate()调用过程中,fragment也在加载,所以fragment无法获取到Activity的布局中的组件,但为什么能获取到Activity呢?

    2.原来在fragment调用onAttach()之前其实还调用了一个方法onInflate(),该方法被调用时fragment已经是和Activity相互结合了,所以可以获取到对方,但是Activity的onCreate()调用还未完成,故无法获取Activity的组件;

    3.Activity的onCreate()调用完成是,fragment会调用onActivityCreated()生命周期方法,因此在这儿开始便能获取到Activity的布局的组件;

    3.4、与Activity通信

    fragment不通过构造函数进行传值的原因是因为横屏切换的时候获取不到值。

    Activity向Fragment传值:

    Activity向Fragment传值,要传的值放到bundle对象里; 在Activity中创建该Fragment的对象fragment,通过调用setArguments()传递到fragment中; 在该Fragment中通过调用getArguments()得到bundle对象,就能得到里面的值。

    Fragment向Activity传值:
    第一种:

    在Activity中调用getFragmentManager()得到fragmentManager,,调用findFragmentByTag(tag)或者通过findFragmentById(id),例如:

    FragmentManager fragmentManager = getFragmentManager();
    
    Fragment fragment = fragmentManager.findFragmentByTag(tag);
    
    
    第二种:

    通过回调的方式,定义一个接口(可以在Fragment类中定义),接口中有一个空的方法,在fragment中需要的时候调用接口的方法,值可以作为参数放在这个方法中,然后让Activity实现这个接口,必然会重写这个方法,这样值就传到了Activity中

    Fragment与Fragment之间是如何传值的:
    第一种:

    通过findFragmentByTag得到另一个的Fragment的对象,这样就可以调用另一个的方法了。

    第二种:

    通过接口回调的方式。

    第三种:

    通过setArguments,getArguments的方式。

    3.5、api区别

    add

    一种是add方式来进行show和add,这种方式你切换fragment不会让fragment重新刷新,只会调用onHiddenChanged(boolean isHidden)。

    replace

    而用replace方式会使fragment重新刷新,因为add方式是将fragment隐藏了而不是销毁再创建,replace方式每次都是重新创建。

    commit/commitAllowingStateLoss

    两者都可以提交fragment的操作,唯一的不同是第二种方法,允许丢失一些界面的状态和信息,几乎所有的开发者都遇到过这样的错误:无法在activity调用了onSaveInstanceState之后再执行commit(),这种异常时可以理解的,界面被系统回收(界面已经不存在),为了在下次打开的时候恢复原来的样子,系统为我们保存界面的所有状态,这个时候我们再去修改界面理论上肯定是不允许的,所以为了避免这种异常,要使用第二种方法。

    3.懒加载

    我们经常在使用fragment时,常常会结合着viewpager使用,那么我们就会遇到一个问题,就是初始化fragment的时候,会连同我们写的网络请求一起执行,这样非常消耗性能,最理想的方式是,只有用户点开或滑动到当前fragment时,才进行请求网络的操作。因此,我们就产生了懒加载这样一个说法。

    Viewpager配合fragment使用,默认加载前两个fragment。很容易造成网络丢包、阻塞等问题。

    在Fragment中有一个setUserVisibleHint这个方法,而且这个方法是优于onCreate()方法的,它会通过isVisibleToUser告诉我们当前Fragment我们是否可见,我们可以在可见的时候再进行网络加载。

    从log上看setUserVisibleHint()的调用早于onCreateView,所以如果在setUserVisibleHint()要实现懒加载的话,就必须要确保View以及其他变量都已经初始化结束,避免空指针。

    使用步骤:

    申明一个变量isPrepare=false,isVisible=false,标明当前页面是否被创建了 在onViewCreated周期内设置isPrepare=true 在setUserVisibleHint(boolean isVisible)判断是否显示,设置isVisible=true 判断isPrepare和isVisible,都为true开始加载数据,然后恢复isPrepare和isVisible为false,防止重复加载。

    4、Activity

    4.1、 Activity启动流程

    用户从Launcher程序点击应用图标可启动应用的入口Activity,Activity启动时需要多个进程之间的交互,Android系统中有一个zygote进程专用于孵化Android框架层和应用层程序的进程。还有一个system_server进程,该进程里运行了很多binder service。例如ActivityManagerService,PackageManagerService,WindowManagerService,这些binder service分别运行在不同的线程中,其中ActivityManagerService负责管理Activity栈,应用进程,task。

    点击Launcher图标来启动Activity

    用户在Launcher程序里点击应用图标时,会通知ActivityManagerService启动应用的入口Activity,ActivityManagerService发现这个应用还未启动,则会通知Zygote进程孵化出应用进程,然后在这个dalvik应用进程里执行ActivityThread的main方法。应用进程接下来通知ActivityManagerService应用进程已启动,ActivityManagerService保存应用进程的一个代理对象,这样ActivityManagerService可以通过这个代理对象控制应用进程,然后ActivityManagerService通知应用进程创建入口Activity的实例,并执行它的生命周期方法。

    4.2、Activity生命周期

    (1)Activity的形态

    Active/Running:

    Activity处于活动状态,此时Activity处于栈顶,是可见状态,可与用户进行交互。

    Paused:

    当Activity失去焦点时,或被一个新的非全屏的Activity,或被一个透明的Activity放置在栈顶时,Activity就转化为Paused状态。但我们需要明白,此时Activity只是失去了与用户交互的能力,其所有的状态信息及其成员变量都还存在,只有在系统内存紧张的情况下,才有可能被系统回收掉。

    Stopped:

    当一个Activity被另一个Activity完全覆盖时,被覆盖的Activity就会进入Stopped状态,此时它不再可见,但是跟Paused状态一样保持着其所有状态信息及其成员变量。

    Killed:

    当Activity被系统回收掉时,Activity就处于Killed状态。

    Activity会在以上四种形态中相互切换,至于如何切换,这因用户的操作不同而异。了解了Activity的4种形态后,我们就来聊聊Activity的生命周期。

    Activity的生命周期

    所谓的典型的生命周期就是在有用户参与的情况下,Activity经历从创建,运行,停止,销毁等正常的生命周期过程。

    onCreate

    该方法是在Activity被创建时回调,它是生命周期第一个调用的方法,我们在创建Activity时一般都需要重写该方法,然后在该方法中做一些初始化的操作,如通过setContentView设置界面布局的资源,初始化所需要的组件信息等。

    onStart

    此方法被回调时表示Activity正在启动,此时Activity已处于可见状态,只是还没有在前台显示,因此无法与用户进行交互。可以简单理解为Activity已显示而我们无法看见摆了。

    onResume

    当此方法回调时,则说明Activity已在前台可见,可与用户交互了(处于前面所说的Active/Running形态),onResume方法与onStart的相同点是两者都表示Activity可见,只不过onStart回调时Activity还是后台无法与用户交互,而onResume则已显示在前台,可与用户交互。当然从流程图,我们也可以看出当Activity停止后(onPause方法和onStop方法被调用),重新回到前台时也会调用onResume方法,因此我们也可以在onResume方法中初始化一些资源,比如重新初始化在onPause或者onStop方法中释放的资源。

    onPause

    此方法被回调时则表示Activity正在停止(Paused形态),一般情况下onStop方法会紧接着被回调。但通过流程图我们还可以看到一种情况是onPause方法执行后直接执行了onResume方法,这属于比较极端的现象了,这可能是用户操作使当前Activity退居后台后又迅速地再回到到当前的Activity,此时onResume方法就会被回调。当然,在onPause方法中我们可以做一些数据存储或者动画停止或者资源回收的操作,但是不能太耗时,因为这可能会影响到新的Activity的显示——onPause方法执行完成后,新Activity的onResume方法才会被执行。

    onStop

    一般在onPause方法执行完成直接执行,表示Activity即将停止或者完全被覆盖(Stopped形态),此时Activity不可见,仅在后台运行。同样地,在onStop方法可以做一些资源释放的操作(不能太耗时)。

    onRestart

    表示Activity正在重新启动,当Activity由不可见变为可见状态时,该方法被回调。这种情况一般是用户打开了一个新的Activity时,当前的Activity就会被暂停(onPause和onStop被执行了),接着又回到当前Activity页面时,onRestart方法就会被回调。

    onDestroy

    此时Activity正在被销毁,也是生命周期最后一个执行的方法,一般我们可以在此方法中做一些回收工作和最终的资源释放。

    小结

    到这里我们来个小结,当Activity启动时,依次会调用onCreate(),onStart(),onResume(),而当Activity退居后台时(不可见,点击Home或者被新的Activity完全覆盖),onPause()和onStop()会依次被调用。当Activity重新回到前台(从桌面回到原Activity或者被覆盖后又回到原Activity)时,onRestart(),onStart(),onResume()会依次被调用。当Activity退出销毁时(点击back键),onPause(),onStop(),onDestroy()会依次被调用,到此Activity的整个生命周期方法回调完成。现在我们再回头看看之前的流程图,应该是相当清晰了吧。嗯,这就是Activity整个典型的生命周期过程。

    2、 View部分知识点

    Android的Activity、PhoneWindow和DecorView的关系可以用下面的图表示:

    2.1、DecorView浅析

    例如,有下面一个视图,DecorView为整个Window界面的最顶层View,它只有一个子元素LinearLayout。代表整个Window界面,包含通知栏、标题栏、内容显示栏三块区域。其中LinearLayout中有两个FrameLayout子元素。

    [图片上传失败...(image-4effee-1597744639992)]

    DecorView的作用

    DecorView是顶级View,本质是一个FrameLayout它包含两部分,标题栏和内容栏,都是FrameLayout。内容栏id是content,也就是activity中设置setContentView的部分,最终将布局添加到id为content的FrameLayout中。 获取content:ViewGroup content=findViewById(android.id.content) 获取设置的View:getChildAt(0).

    使用总结

    每个Activity都包含一个Window对象,Window对象通常是由PhoneWindow实现的。 PhoneWindow:将DecorView设置为整个应用窗口的根View,是Window的实现类。它是Android中的最基本的窗口系统,每个Activity均会创建一个PhoneWindow对象,是Activity和整个View系统交互的接口。 DecorView:是顶层视图,将要显示的具体内容呈现在PhoneWindow上,DecorView是当前Activity所有View的祖先,它并不会向用户呈现任何东西。

    2.2、View的事件分发

    View的事件分发机制可以使用下图表示:

    如上图,图分为3层,从上往下依次是Activity、ViewGroup、View。

    1. 事件从左上角那个白色箭头开始,由Activity的dispatchTouchEvent做分发
    2. 箭头的上面字代表方法返回值,(return true、return false、return super.xxxxx(),super 的意思是调用父类实现。
    3. dispatchTouchEvent和 onTouchEvent的框里有个【true---->消费】的字,表示的意思是如果方法返回true,那么代表事件就此消费,不会继续往别的地方传了,事件终止。
    4. 目前所有的图的事件是针对ACTION_DOWN的,对于ACTION_MOVE和ACTION_UP我们最后做分析。
    5. 之前图中的Activity 的dispatchTouchEvent 有误(图已修复),只有return super.dispatchTouchEvent(ev) 才是往下走,返回true 或者 false 事件就被消费了(终止传递)。

    ViewGroup事件分发

    当一个点击事件产生后,它的传递过程将遵循如下顺序:

    Activity -> Window -> View

    事件总是会传递给Activity,之后Activity再传递给Window,最后Window再传递给顶级的View,顶级的View在接收到事件后就会按照事件分发机制去分发事件。如果一个View的onTouchEvent返回了FALSE,那么它的父容器的onTouchEvent将会被调用,依次类推,如果所有都不处理这个事件的话,那么Activity将会处理这个事件。

    对于ViewGroup的事件分发过程,大概是这样的:如果顶级的ViewGroup拦截事件即onInterceptTouchEvent返回true的话,则事件会交给ViewGroup处理,如果ViewGroup的onTouchListener被设置的话,则onTouch将会被调用,否则的话onTouchEvent将会被调用,也就是说:两者都设置的话,onTouch将会屏蔽掉onTouchEvent,在onTouchEvent中,如果设置了onClickerListener的话,那么onClick将会被调用。如果顶级ViewGroup不拦截的话,那么事件将会被传递给它所在的点击事件的子view,这时候子view的dispatchTouchEvent将会被调用

    View的事件分发

    dispatchTouchEvent -> onTouch(setOnTouchListener) -> onTouchEvent -> onClick

    onTouch和onTouchEvent的区别 两者都是在dispatchTouchEvent中调用的,onTouch优先于onTouchEvent,如果onTouch返回true,那么onTouchEvent则不执行,及onClick也不执行。

    最后

    考虑到现在是金九银十面试季很多朋友问我该怎么刷面试题,我在这里准备了一些大厂经典高频面试题,需要的可以查看我的GitHub。

    注意:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

    需要这份按系统分类的2246页PDF的大厂面试真题可以查看我的【GitHub 】觉得还不错的,记得点个 star!

    或者在群文件夹中里,自行下载直达领取链接:【https://links.jianshu.com/go?to=https%3A%2F%2Fjq.qq.com%2F%3F_wv%3D1027%26k%3DBRZhpPkt

    相关文章

      网友评论

        本文标题:2020阿里巴巴,字节跳动,京东,小米,三星等大厂面试真题,牛客

        本文链接:https://www.haomeiwen.com/subject/mwdkjktx.html