美文网首页Android进阶之路Android高级进阶
近两年Android BAT面试题大整理,面试前一定要准备好!(

近两年Android BAT面试题大整理,面试前一定要准备好!(

作者: 小小小小怪兽_666 | 来源:发表于2020-09-28 17:17 被阅读0次

    这是我近段时间收集的面试题,献给打算年后找工作的同学们。文中涉及的知识比较广也可能比较零散,并且一些较为基础的知识我都略去了(比如Android四大组件是什么这类问题),有些我附上了自己的理解,有些附上了详细的相关文章链接。大家挑自己感兴趣的内容查看即可,后期我也会继续不断补充。

    基础组件篇

    横竖屏切换时Activity的生命周期变化?

    1.如果自己没有配置android:ConfigChanges,这时默认让系统处理,就会重建Activity,此时Activity的生命周期会走一遍。

    onSaveInstanceState() 与onRestoreIntanceState()
    资源相关的系统配置发生改变或者资源不足:例如屏幕旋转,当前Activity会销毁,并且在onStop之前回调onSaveInstanceState保存数据,在重新创建Activity的时候在onStart之后回调onRestoreInstanceState。其中Bundle数据会传到onCreate(不一定有数据)和onRestoreInstanceState(一定有数据)。 用户或者程序员主动去销毁一个Activity的时候不会回调,其他情况都会调用,来保存界面信息。如代码中finish()或用户按下back,不会回调。

    2.如果设置android:configChanges="orientation|keyboardHidden|screenSize">,此时Activity的生命周期不会重走一遍,Activity不会重建,只会回调onConfigurationChanged方法。

    activity的startActivity和context的startActivity区别

    (1)从Activity中启动新的Activity时可以直接mContext.startActivity(intent)就好,

    (2)如果从其他Context中启动Activity则必须给intent设置Flag:

    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) ; 
    mContext.startActivity(intent);
    

    介绍下Android应用程序启动过程

    整个应用程序的启动过程要执行很多步骤,但是整体来看,主要分为以下五个阶段:

    **一. **:Launcher通过Binder进程间通信机制通知ActivityManagerService,它要启动一个Activity;

    二.:ActivityManagerService通过Binder进程间通信机制通知Launcher进入Paused状态;

    三.:Launcher通过Binder进程间通信机制通知ActivityManagerService,它已经准备就绪进入Paused状态,于是ActivityManagerService就创建一个新的进程,用来启动一个ActivityThread实例,即将要启动的Activity就是在这个ActivityThread实例中运行;

    四. :ActivityThread通过Binder进程间通信机制将一个ApplicationThread类型的Binder对象传递给ActivityManagerService,以便以后ActivityManagerService能够通过这个Binder对象和它进行通信;

    五 :ActivityManagerService通过Binder进程间通信机制通知ActivityThread,现在一切准备就绪,它可以真正执行Activity的启动操作了。

    如何保证Service不被杀死?

    • 提供进程优先级,降低进程被杀死的概率 方法一:监控手机锁屏解锁事件,在屏幕锁屏时启动1个像素的 Activity,在用户解锁时将 Activity 销毁掉。 方法二:启动前台service。 方法三:提升service优先级: 在AndroidManifest.xml文件中对于intent-filter可以通过android:priority = "1000"这个属性设置最高优先级,1000是最高值,如果数字越小则优先级越低,同时适用于广播。

    • 在进程被杀死后,进行拉活 方法一:注册高频率广播接收器,唤起进程。如网络变化,解锁屏幕,开机等 方法二:双进程相互唤起。 方法三:依靠系统唤起。 方法四:onDestroy方法里重启service:service +broadcast 方式,就是当service走ondestory的时候,发送一个自定义的广播,当收到广播的时候,重新启动service;

    • 依靠第三方 根据终端不同,在小米手机(包括 MIUI)接入小米推送、华为手机接入华为推送;其他手机可以考虑接入腾讯信鸽或极光推送与小米推送做 A/B Test。

    简述下Acitivty任务栈和使用方法

    任务栈是一种后进先出的结构。位于栈顶的Activity处于焦点状态,当按下back按钮的时候,栈内的Activity会一个一个的出栈,并且调用其onDestory()方法。如果栈内没有Activity,那么系统就会回收这个栈,每个APP默认只有一个栈,以APP的包名来命名.

    1、standard:默认模式:每次启动都会创建一个新的activity对象,放到目标任务栈中

    2、singleTop:判断当前的任务栈顶是否存在相同的activity对象,如果存在,则直接使用,如果不存在,那么创建新的activity对象放入栈中

    3、singleTask:在任务栈中会判断是否存在相同的activity,如果存在,那么会清除该activity之上的其他activity对象显示,如果不存在,则会创建一个新的activity放入栈顶

    4、singleIntance:会在一个新的任务栈中创建activity,并且该任务栈种只允许存在一个activity实例,其他调用该activity的组件会直接使用该任务栈种的activity对象

    方法一: 使用android:launchMode="standard|singleInstance|single Task|singleTop"来控制Acivity任务栈。

    方法二: Intent Flags:

    Intent intent=new Intent();
    intent.setClass(MainActivity.this, MainActivity2.class);
    intent.addFlags(Intent. FLAG_ACTIVITY_CLEAR_TOP);
    startActivity(intent);
    

    Flags有很多,比如:
    Intent.FLAG_ACTIVITY_NEW_TASK 相当于singleTask
    Intent. FLAG_ACTIVITY_CLEAR_TOP 相当于singleTop

    Context相关问题

    Activity和Service以及Application的Context是不一样的,Activity继承自ContextThemeWraper.其他的继承自ContextWrapper。

    每一个Activity和Service以及Application的Context都是一个新的ContextImpl对象 getApplication()用来获取Application实例的,但是这个方法只有在Activity和Service中才能调用的到。那么也许在绝大多数情况下我们都是在Activity或者Service中使用Application的,但是如果在一些其它的场景,比如BroadcastReceiver中也想获得Application的实例,这时就可以借助getApplicationContext()方法。

    getApplicationContext()比getApplication()方法的作用域会更广一些,任何一个Context的实例,只要调用getApplicationContext()方法都可以拿到我们的Application对象。

    Context的数量等于Activity的个数 + Service的个数 + 1,这个1为Application。

    那Broadcast Receiver,Content Provider呢?Broadcast Receiver,Content Provider并不是Context的子类,他们所持有的Context都是其他地方传过去的,所以并不计入Context总数。

    怎么在Service中创建Dialog对话框

    1.在我们取得Dialog对象后,需给它设置类型,即:
    dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT)

    2.在Manifest中加上权限:
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

    View篇

    非UI线程可以更新UI吗?

    可以

    当访问UI时,ViewRootImpl会调用checkThread方法去检查当前访问UI的线程是哪个,如果不是UI线程则会抛出异常 执行onCreate方法的那个时候ViewRootImpl还没创建,无法去检查当前线程.ViewRootImpl的创建在onResume方法回调之后.

    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }
    

    非UI线程是可以刷新UI的,前提是它要拥有自己的ViewRoot,即更新UI的线程和创建ViewRoot是同一个,或者在执行checkThread()前更新UI.

    解决ScrollView嵌套ListView和GridView冲突的方法

    重写ListView的onMeasure方法,来自定义高度:

    @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
            super.onMeasure(widthMeasureSpec, expandSpec);
        }
    

    主要考察对MeasureSpec的三种模式的理解,相关文章.

    自定义View优化策略

    为了加速你的view,对于频繁调用的方法,需要尽量减少不必要的代码。先从onDraw开始,需要特别注意不应该在这里做内存分配的事情,因为它会导致GC,从而导致卡顿。在初始化或者动画间隙期间做分配内存的动作。不要在动画正在执行的时候做内存分配的事情。

    你还需要尽可能的减少onDraw被调用的次数,大多数时候导致onDraw都是因为调用了invalidate().因此请尽量减少调用invaildate()的次数。如果可能的话,尽量调用含有4个参数的invalidate()方法而不是没有参数的invalidate()。没有参数的invalidate会强制重绘整个view。

    另外一个非常耗时的操作是请求layout。任何时候执行requestLayout(),会使得Android UI系统去遍历整个View的层级来计算出每一个view的大小。如果找到有冲突的值,它会需要重新计算好几次。另外需要尽量保持View的层级是扁平化的,这样对提高效率很有帮助。

    如果你有一个复杂的UI,你应该考虑写一个自定义的ViewGroup来执行他的layout操作。与内置的view不同,自定义的view可以使得程序仅仅测量这一部分,这避免了遍历整个view的层级结构来计算大小。这个PieChart 例子展示了如何继承ViewGroup作为自定义view的一部分。PieChart 有子views,但是它从来不测量它们。而是根据他自身的layout法则,直接设置它们的大小。

    线程篇

    Handler、Message、Looper、MessageQueue

    一、相关概念的解释

    • 主线程(UI线程)

    定义:当程序第一次启动时,Android会同时启动一条主线程(Main Thread) 作用:主线程主要负责处理与UI相关的事件

    • Message(消息)

    定义:Handler接收和处理的消息对象(Bean对象)
    作用:通信时相关信息的存放和传递

    • ThreadLocal

    定义:ThreadLocal是线程内部的存储类,通过它可以实现在每个线程中存储自己的私有数据。即数据存储以后,只能在指定的线程中获取这个存储的对象,而其它线程则不能获取到当前线程存储的这个对象。
    作用:负责存储和获取本线程的Looper

    • MessageQueue(消息队列)

    定义:采用单链表的数据结构来存储消息列表
    作用:用来存放通过Handler发过来的Message,按照先进先出执行

    • Handler(处理者)

    定义:Message的主要处理者
    作用:负责发送Message到消息队列&处理Looper分派过来的Message

    • Looper(循环器)

    定义:扮演Message Queue和Handler之间桥梁的角色
    作用: 消息循环:循环取出Message Queue的Message 消息派发:将取出的Message交付给相应的Handler

    2、自己画下图解

    3、Handler发送消息有哪几种方式?

    一、sendMessage(Message msg)
    二、post(Ruunable r)

    4、Handler处理消息有哪几种方式?

    这个直接看dispatchMessage()源码:

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            //1\. post()方法的处理方法
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //2\. sendMessage()方法的处理方法
            handleMessage(msg);
        }
    }
    //1\. post()方法的最终处理方法
    private static void handleCallback(Message message) {
        message.callback.run();
    }
    //2\. sendMessage()方法的最终处理方法
    public void handleMessage(Message msg) {
    }
    

    5.Message、Handler、MessageQueue、Looper的之间的关系?

    首先,是这个MessagQueue,MessageQueue是一个消息队列,它可以存储Handler发送过来的消息,其内部提供了进队和出队的方法来管理这个消息队列,其出队和进队的原理是采用单链表的数据结构进行插入和删除的,即enqueueMessage()方法和next()方法。这里提到的Message,其实就是一个Bean对象,里面的属性用来记录Message的各种信息。

    然后,是这个Looper,Looper是一个循环器,它可以循环的取出MessageQueue中的Message,其内部提供了Looper的初始化和循环出去Message的方法,即prepare()方法和loop()方法。在prepare()方法中,Looper会关联一个MessageQueue,而且将Looper存进一个ThreadLocal中,在loop()方法中,通过ThreadLocal取出Looper,使用MessageQueue的next()方法取出Message后,判断Message是否为空,如果是则Looper阻塞,如果不是,则通过dispatchMessage()方法分发该Message到Handler中,而Handler执行handlerMessage()方法,由于handlerMessage()方法是个空方法,这也是为什么需要在Handler中重写handlerMessage()方法的原因。这里要注意的是Looper只能在一个线程中只能存在一个。这里提到的ThreadLocal,其实就是一个对象,用来在不同线程中存放对应线程的Looper。

    最后,是这个Handler,Handler是Looper和MessageQueue的桥梁,Handler内部提供了发送Message的一系列方法,最终会通过MessageQueue的enqueueMessage()方法将Message存进MessageQueue中。我们平时可以直接在主线程中使用Handler,那是因为在应用程序启动时,在入口的main方法中已经默认为我们创建好了Looper。

    6.为什么在子线程中创建Handler会抛异常?

    Handler的工作是依赖于Looper的,而Looper(与消息队列)又是属于某一个线程(ThreadLocal是线程内部的数据存储类,通过它可以在指定线程中存储数据,其他线程则无法获取到),其他线程不能访问。因此Handler就是间接跟线程是绑定在一起了。因此要使用Handler必须要保证Handler所创建的线程中有Looper对象并且启动循环。因为子线程中默认是没有Looper的,所以会报错。 正确的使用方法是:

      private final class WorkThread extends Thread {
    
            private Handler mHandler;
    
            public Handler getHandler() {
                return mHandler;
            }
              public void quit() {
                mHandler.getLooper().quit();
            }
            @Override
            public void run() {
                super.run();
                //创建该线程对应的Looper,
                // 内部实现
                // 1。new Looper()
                // 2。将1步中的lopper 放在ThreadLocal里,ThreadLocal是保存数据的,主要应用场景是:线程间数据互不影响的情况
                // 3。在1步中的Looper的构造函数中new MessageQueue();
                //其实就是创建了该线程对用的Looper,Looper里创建MessageQueue来实现消息机制
                //对消息机制不懂得同学可以查阅资料,网上很多也讲的很不错。
                Looper.prepare();
                mHandler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                        Log.d("WorkThread", (Looper.getMainLooper() == Looper.myLooper()) + "," + msg.what);
                    }
                };
                //开启消息的死循环处理即:dispatchMessage
                Looper.loop();
                //注意这3个的顺序不能颠倒
                Log.d("WorkThread", "end");
            }
        }
    

    HandlerThread

    1、HandlerThread作用

    当系统有多个耗时任务需要执行时,每个任务都会开启一个新线程去执行耗时任务,这样会导致系统多次创建和销毁线程,从而影响性能。为了解决这一问题,Google提供了HandlerThread,HandlerThread是在线程中创建一个Looper循环器,让Looper轮询消息队列,当有耗时任务进入队列时,则不需要开启新线程,在原有的线程中执行耗时任务即可,否则线程阻塞。

    2、HanlderThread的优缺点

    • HandlerThread本质上是一个线程类,它继承了Thread;
    • HandlerThread有自己的内部Looper对象,可以进行looper循环;
    • 通过获取HandlerThread的looper对象传递给Handler对象,可以在handleMessage()方法中执行异步任务。
    • 创建HandlerThread后必须先调用HandlerThread.start()方法,Thread会先调用run方法,创建Looper对象。
    • HandlerThread优点是异步不会堵塞,减少对性能的消耗
    • HandlerThread缺点是不能同时继续进行多任务处理,需要等待进行处理,处理效率较低
    • HandlerThread与线程池不同,HandlerThread是一个串行队列,背后只有一个线程。

    IntentService

    • 它本质是一种特殊的Service,继承自Service并且本身就是一个抽象类

    • 它可以用于在后台执行耗时的异步任务,当任务完成后会自动停止

    • 它拥有较高的优先级,不易被系统杀死(继承自Service的缘故),因此比较适合执行一些高优先级的异步任务 它内部通过HandlerThread和Handler实现异步操作

    • 创建IntentService时,只需实现onHandleIntent和构造方法,onHandleIntent为异步方法,可以执行耗时操作

    • 即使我们多次启动IntentService,但IntentService的实例只有一个,这跟传统的Service是一样的,最终IntentService会去调用onHandleIntent执行异步任务。

    • 当任务完成后,IntentService会自动停止,而不需要手动调用stopSelf()。另外,可以多次启动IntentService,每个耗时操作都会以工作队列的方式在IntentServiceonHandlerIntent()回调方法中执行,并且每次只会执行一个工作线程。

    AsyncTask

    1、AsyncTask是什么

    AsyncTask是一种轻量级的异步任务类,它可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程并主线程中更新UI,通过AsyncTask可以更加方便执行后台任务以及在主线程中访问UI,但是AsyncTask并不适合进行特别耗时的后台任务,对于特别耗时的任务来说,建议使用线程池。

    2、AsyncTask使用方法

    三个参数

    • Params:表示后台任务执行时的参数类型,该参数会传给AysncTask的doInBackground()方法

    • Progress:表示后台任务的执行进度的参数类型,该参数会作为onProgressUpdate()方法的参数

    • Result:表示后台任务的返回结果的参数类型,该参数会作为onPostExecute()方法的参数

    五个方法

    • onPreExecute():异步任务开启之前回调,在主线程中执行
    • doInBackground():执行异步任务,在线程池中执行
    • onProgressUpdate():当doInBackground中调用
    • publishProgress时回调,在主线程中执行
    • onPostExecute():在异步任务执行之后回调,在主线程中执行
    • onCancelled():在异步任务被取消时回调

    3、AsyncTask引起的内存泄漏

    原因:非静态内部类持有外部类的匿名引用,导致Activity无法释放

    解决: AsyncTask内部持有外部Activity的弱引用 AsyncTask改为静态内部类 Activity的onDestory()中调用AsyncTask.cancel()

    4.结果丢失

    屏幕旋转或Activity在后台被系统杀掉等情况会导致Activity的重新创建,之前运行的AsyncTask会持有一个之前Activity的引用,这个引用已经无效,这时调用onPostExecute()再去更新界面将不再生效。

    5、AsyncTask并行or串行

    AsyncTask在Android 2.3之前默认采用并行执行任务,AsyncTask在Android 2.3之后默认采用串行执行任务 如果需要在Android 2.3之后采用并行执行任务,可以调用AsyncTask的executeOnExecutor();

    6.AsyncTask内部的线程池

    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

    sDefaultExecutorSerialExecutor的一个实例,而且它是个静态变量。也就是说,一个进程里面所有AsyncTask对象都共享同一个SerialExecutor对象。

    一些面试心经

    一般情况下第一轮都是基础面试,需要扎实的基础

    • 最常用的Android 基础知识
    • Java 基础知识
    • 了解一些 常用东西的原理,例如:handler, thread 等
    • 项目中的技术点

    第二轮的时候需要了解更深层次的东西

    • Android 事件分发机制原理
    • Android 绘图机制原理
    • WindowManager 的相关知识
    • 进程间传输方式
    • Java 内存管理机制
    • 一些常用的 list,map 原理,以及子类之间的差别

    能进入第三轮基本没什么问题,但是要注意以下问题

    该轮一般是 老大或者部门负责人,问的问题一般都看 深度与广度 当问及薪水的时候,要说一个合适的,小公司随意,大公司一定要慎重,当心里没底的时候,可以告诉对方,让对方给一个合理的薪资。一般都是在原工资基础之上增长,听猎头说一般涨幅都在15%-30%,超 NB 的可以要30%及以上,如果感觉自己还不错的,挺厉害的,建议最高20%,一般人就定在15% 左右最靠谱。公司内部一般有一套机制,根据公司情况而定。

    我们的面试原则就是拿到合理薪资,得到 offer
    个人发展情况,这个问题很难回答,如果和公司方向不符合,极有可能和公司无缘。建议多试探性的问问公司缺少什么,你能否给予公司对应的东西。当然对于有自我追求的人,那可以放心大胆的提。我的方向就是架构师,哈哈哈,挺极端的,别学我哦。我感觉选择都是双向的,因此我知道自己需要的是什么。

    你最擅长什么UI 还是其他什么?这个问题更不好回答。你要说你擅长 UI,是不是意味着你其他能力就不行?虽然我不知道面试官的用意,但是我能感觉到,这个问题不是那么好回答,我会回答说自己都行,来什么业务接什么需求。可能回答不太好,总之和公司的职位吻合就行,这样总不至于出错吧。

    简历模板推荐:

    github.com/Trinea/trin…
    github.com/geekcompany…

    网站

    lintcode(算法):答案link
    牛客

    面试前如何复习?

    其实客户端开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

    这里分享一下面试这段时间的复习路线:(以下体系的复习资料是我从各路大佬收集整理好的)

    《Android开发七大模块核心知识笔记》

    《960全网最全Android开发笔记》

    《379页Android开发面试宝典》

    历时半年,我们整理了这份市面上最全面的安卓面试题解析大全
    包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。

    如何使用它?

    1.可以通过目录索引直接翻看需要的知识点,查漏补缺。
    2.五角星数表示面试问到的频率,代表重要推荐指数

    《507页Android开发相关源码解析》

    只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

    真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

    资料太多,全部展示会影响篇幅,暂时就先列举这些部分截图,以上资源均免费分享,以上内容均放在了开源项目:github 中已收录,大家可以自行获取(或者关注主页扫描加微信获取)。

    相关文章

      网友评论

        本文标题:近两年Android BAT面试题大整理,面试前一定要准备好!(

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