美文网首页面试移动开发
Android面试题汇总及答案

Android面试题汇总及答案

作者: flypple | 来源:发表于2019-05-24 19:20 被阅读30次

    汇总一下Android面试题,并逐渐补充答案。

    最近比较忙,没有进一步整理,不过没有答案的题目也可以作为复习引导来用,后续有时间在补充吧。

    Android:

    1. 说一下四大组件。

    四大组件指Activity、Service、BroadcastReceiver、ContentProvider,这四大组件都需要在AndroidManifest文件中注册才可以使用。

    2. Activity生命周期和四种启动模式。

    生命周期:

    onCreate: Activity第一次创建时必然执行的方法,我们在这个方法中做一下初始化的操作,例如加载布局,初始化控件,注册点击事件等。

    onStart: 当Activity变为可见时调用该方法。

    onRestart: 该方法在Activity由停止状态转变为运行状态时调用,也就是说一个处于后台的Activity被重新启用了。(注意:后续还会调用onStart,不要以为onRestart之后就直接调用onResume了。)

    onResume: 当Activity准备好和用户交互的时候调用,此时Activity必然处于栈顶,处于运行状态。

    onPause: 当Activity不可交互时调用。例如被销毁时、按Home键返回桌面时,启动或者恢复某个Activity时。(注意:展示Dialog时不会调用onPasuse,有疑问可以自己测试。)

    onStop: 该方法在Activity完全不可见的时候调用。它和onPause()方法的主要区别在于,如果启动的新Activity是一个对话框式的activity,那么,onPause()方法会得到执行,而onStop()方法并不会执行。

    onDestroy: 这个方法在Activity被销毁之前调用,之后Activity的状态将变为销毁状态。

    启动模式:

    Standard模式:

    标准模式,Standard模式是Android的默认启动模式,无需再Manifest文件中配置,这种模式下,Activity可以有多个实例,每次启动Activity,无论任务栈中是否已经有这个Activity的实例,系统都会创建一个新的Activity实例。

    SingleTop模式:

    栈顶复用模式,该模式需要在Manifest文件中配置。
    SingleTop模式和standard模式非常相似,主要区别就是当一个singleTop模式的Activity已经位于任务栈的栈顶,再去启动它时,不会再创建新的实例,如果不位于栈顶,就会创建新的实例。

    SingleTask模式:

    栈内复用模式,该模式需要在Manifest文件中配置。

    SingleTask模式的Activity在同一个Task内只有一个实例,如果Activity已经位于栈顶,系统不会创建新的Activity实例,和singleTop模式一样。但Activity已经存在但不位于栈顶时,系统就会把该Activity移到栈顶,并把它上面的activity出栈。

    SingleInstance模式:

    单例模式,该模式需要在Manifest文件中配置。

    SingleInstance模式也是栈内单例的,但和SingleTask不同,SingleTask只是任务栈内单例,其栈内是可以有多个其他Activity实例的,且若有别的任务栈,其中也可能有该Activity;而SingleInstance模式的 Activity在整个系统里只有一个实例,切启动一SingleInstance模式的Activity时,系统会创建一个新的任务栈,并且这个任务栈只有他一个Activity。

    3. Activity的启动过程(不要回答生命周期)。

    Activity的启动过程,我们可以从Context的startActivity说起,其实现是ContextImpl的startActivity,然后内部会通过Instrumentation来尝试启动Activity,这是一个跨进程过程,它会调用ams的startActivity方法,当ams校验完activity的合法性后,会通过ApplicationThread回调到我们的进程,这也是一次跨进程过程,而applicationThread就是一个binder,回调逻辑是在binder线程池中完成的,所以需要通过Handler H将其切换到ui线程,第一个消息是LAUNCH_ACTIVITY,它对应handleLaunchActivity,在这个方法里完成了Activity的创建和启动,接着,在activity的onResume中,activity的内容将开始渲染到window上,然后开始绘制直到我们看见。

    转自:https://blog.csdn.net/u010648159/article/details/81103092

    4. Service的启动方式及生命周期(参考下方示例代码)。

    Service的启动方式分为startService和bindService两种:

    startService方式:生命周期为onCreate -> onStartCommand -> onDestroy。

    bindService方式:生命周期为onCreate -> onBind -> onUnbind -> onDestroy。

    注意:多次startService或者bindService,onCreate方法也只会调用一次。

    也可以混合启动:

    先bind后start:生命周期为onCreate -> onBind -> onStartCommand。

    先start后bind:生命周期为onCreate -> onStartCommand -> onBind。

    至于混合启动后的unbind和stop操作,结果都是一样的:

    如果先unbind,后stop:则会依次执行onUnbind和onDestroy方法。

    但是如果先stop,后unbind:则会有所不同:试图stop时,并没有任何反应,但是接下来unbind到时候,会在调用onUnbind后,直接调用onDestroy方法。

    示例代码:

        /*=============MainActivity=============*/
        //开启服务
        fun startService(){
            val intent = Intent(this, MyService::class.java)
            startService(intent)
        }
        //停止服务
        fun stopService(){
            val intent = Intent(this, MyService::class.java)
            stopService(intent)
        }
        //绑定服务
        fun bindService() {
            if (conn == null) {
                val intent = Intent(this, MyService::class.java)
                conn = object : ServiceConnection{
                    override fun onServiceDisconnected(name: ComponentName?) {
    
                    }
    
                    override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
                        myIBinder = service as MyService.MyIBinder
                    }
                }
                bindService(intent, conn!!, Context.BIND_AUTO_CREATE)
            }
        }
        //解绑服务
        fun unbindService(){
            if (conn != null) {
                unbindService(conn!!)
                conn = null
            }
        }
        /*=============MainActivity=============*/
    
        /*=============MyService=============*/
        override fun onCreate() {
            super.onCreate()
            loge("onCreate")
        }
    
        override fun onBind(intent: Intent?): IBinder? {
            loge("onBind")
            return MyIBinder()
        }
    
        override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
            loge("onStartCommand")
            return super.onStartCommand(intent, flags, startId)
        }
    
        override fun onUnbind(intent: Intent?): Boolean {
            loge("onUnbind")
            return super.onUnbind(intent)
        }
    
        override fun onDestroy() {
            super.onDestroy()
            loge("onDestroy")
        }
        /*=============MyService=============*/
    

    5. BroatcastReceiver的注册方式。

    BroatcastReceiver的注册方式分为静态注册和动态注册两种。

    动态注册广播不是常驻型广播,也就是说广播跟随Activity的生命周期。注意在Activity结束前,移除广播接收器。 静态注册是常驻型,也就是说当应用程序关闭后,如果有合适的广播,程序也会被系统调用自动运行。

    当广播为有序广播时:优先级高的先接收(不分静态和动态)。同优先级的广播接收器,动态优先于静态

    同优先级的同类广播接收器,静态:先扫描的优先于后扫描的,动态:先注册的优先于后注册的。

    当广播为默认广播时:无视优先级,动态广播接收器优先于静态广播接收器。同优先级的同类广播接收器,静态:先扫描的优先于后扫描的,动态:先注册的优先于后册的。

    6. ContentProvider:

    相关文章:https://www.jianshu.com/p/380231307070

    8. 子线程能更新UI吗?

    Android的单线程模型模型要求必须要在UI线程更新UI,否则认为是不安全的,原则上子线程是不能更新UI的。在其他线程访问UI,Android给提供了如下一些方法:

    • Activity.runOnUiThread(Runnable)
    • View.post(Runnable)
    • View.postDelayed(Runnable, long)
    • Handler
      说道低都是调度到主线程来完成更新UI的工作。
      其实如果非要直接在子线程强行更新UI,则会得到一个异常:

    android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

    实际上,通过CalledFromWrongThreadException的堆栈信息可以追踪ViewRootImpl这个类,实际上到这个是通过其中的一个checkThread方法,来检查当前访问的线程,如果不是UI线程,就会抛出该异常。
    回到问题本身,子线程真的不能更新UI吗?
    实际上在如下情况中子线程是可以更新的UI的:
    在Activity执行onCreate时,ViewRootImpl还没有实例化,加入此时立即new一个Thread更新UI,则不会抛出异常。
    另外,如果我们能够通过某种方法,在子线程中拥有ViewRootImpl,那么也可以更新UI,具体方法就不深究了。

    7. Java虚拟机和Dalvik虚拟机和Art的区别。

    相关文章:
    https://www.jianshu.com/p/713d24fa9982
    https://blog.csdn.net/evan_man/article/details/52414390

    8. 进程和线程。

    进程就是一个应用程序在处理机上的一次执行过程,它是一个动态的概念,而线程是进程中的一部分,一个进程可以包含多个线程。
    进程和线程的关系和区别如下:

    • 进程是cpu资源分配的最小单位, 线程是cpu调度的最小单位。
    • 线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。
    • 不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间。
    • 进程之间不能共享资源,而线程共享所在进程的内存空间和其它资源。
    • 一个进程内可拥有多个线程,可开启新的进程,也可开启线程。
    • 一个线程只能属于一个进程,线程可直接使用同进程的资源,线程依赖于进程而存在。

    9. 进程保活(不死进程)。

    首先明确一点,“不死进程”本身其实是一个伪命题,尽量优化APP自身才是正道。

    首先说一下进程的优先级:
    根据进程的重要性,划分5级,优先级依次递减:
    前台进程 (Foreground process)
    可见进程 (Visible process)
    服务进程 (Service process)
    后台进程 (Background process)
    空进程 (Empty process)

    保活手段:
    当前业界的Android进程保活手段主要分为** 黑、白、灰 **三种,其大致的实现思路如下:

    • 黑色保活:不同的app进程,用广播相互唤醒(包括利用系统提供的广播进行唤醒)

    所谓黑色保活,就是利用不同的app进程使用广播来进行相互唤醒。举个3个比较常见的场景:

    场景1:开机,网络切换、拍照、拍视频时候,利用系统产生的广播唤醒app

    场景2:接入第三方SDK也会唤醒相应的app进程,如微信sdk会唤醒微信,支付宝sdk会唤醒支付宝。由此发散开去,就会直接触发了下面的 场景3

    场景3:假如你手机里装了支付宝、淘宝、天猫、UC等阿里系的app,那么你打开任意一个阿里系的app后,有可能就顺便把其他阿里系的app给唤醒了。(只是拿阿里打个比方,其实BAT系都差不多)

    • 白色保活:启动前台Service

    白色保活手段非常简单,就是调用系统api启动一个前台的Service进程,这样会在系统的通知栏生成一个Notification,用来让用户知道有这样一个app在运行着,哪怕当前的app退到了后台。如音乐APP的可操作的通知。

    • 灰色保活:利用系统的漏洞启动前台Service

    灰色保活,这种保活手段是应用范围最广泛。它是利用系统的漏洞来启动一个前台的Service进程,与普通的启动方式区别在于,它不会在系统通知栏处出现一个Notification,看起来就如同运行着一个后台Service进程一样。这样做带来的好处就是,用户无法察觉到你运行着一个前台进程(因为看不到Notification),但你的进程优先级又是高于普通后台进程的。那么如何利用系统的漏洞呢,大致的实现思路和代码如下:
    思路一:API < 18,启动前台Service时直接传入new Notification(); 思路二:API >= 18,同时启动两个id相同的前台Service,然后再将后启动的Service做stop处理

    下面补充一下Android的杀进程机制:

    熟悉Android系统的童鞋都知道,系统出于体验和性能上的考虑,app在退到后台时系统并不会真正的kill掉这个进程,而是将其缓存起来。打开的应用越多,后台缓存的进程也越多。在系统内存不足的情况下,系统开始依据自身的一套进程回收机制来判断要kill掉哪些进程,以腾出内存来供给需要的app。这套杀进程回收内存的机制就叫 Low Memory Killer ,它是基于Linux内核的 OOM Killer(Out-Of-Memory killer)机制诞生。

    了解完 Low Memory Killer,再科普一下oom_adj。什么是oom_adj?它是linux内核分配给每个系统进程的一个值,代表进程的优先级,进程回收机制就是根据这个优先级来决定是否进行回收。对于oom_adj的作用,你只需要记住以下几点即可:
    进程的oom_adj越大,表示此进程优先级越低,越容易被杀回收;越小,表示进程优先级越高,越不容易被杀回收
    普通app进程的oom_adj>=0,系统进程的oom_adj才可能<0
    有些手机厂商把这些知名的app放入了自己的白名单中,保证了进程不死来提高用户体验(如微信、QQ、陌陌都在小米的白名单中)。如果从白名单中移除,他们终究还是和普通app一样躲避不了被杀的命运,为了尽量避免被杀,还是老老实实去做好优化工作吧。

    所以,进程保活的根本方案终究还是回到了性能优化上,进程永生不死终究是个彻头彻尾的伪命题!

    10. 讲解一下Context。

    先来看一下Context家族的继承树结构:


    image.png

    可以看出,Context的直接子类有两个,分别为ContextWrapper,一个是ContextImpl。顾名思义,ContextWrapper是Context的包装(封装)类,ContextImpl是Context的实现类。
    可以看到ContextWrapper有三个直接的子类,ContextThemeWrapper、Service和Application。其中,ContextThemeWrapper是一个带主题的封装类,而它有一个直接子类就是Activity。
    所以在Android工程中,Context只有3中来源或者说3中类型,分别是:Application,Activity,Service。那么也就不难统计真正的Context的数量了:

    Context数量 = Activity数量 + Service数量 + 1(Application)。

    那么ContextImpl是怎么回事呢,前面说了,这是一个Context的实现类,由它来具体实现了Context中定义的各种方法,而ContextWrapper是Context的包装类,我们看一看ContextWrapper的源码就会发现,其中有一个Context成员变量mBase,ContextWrapper将Context方法的实现全部委托给mBase负责,其中有一个方法attachBaseContext——用来给mBase赋值,且只能赋值一次,否则会抛异常,另有一个方法getBaseContext,可以用来获取mBase。

    //=========ContextWrapper部分代码=========
    public class ContextWrapper extends Context {
        Context mBase;
    
        public ContextWrapper(Context base) {
            mBase = base;
        }
        
        /**
         * Set the base context for this ContextWrapper.  All calls will then be
         * delegated to the base context.  Throws
         * IllegalStateException if a base context has already been set.
         * 
         * @param base The new base context for this wrapper.
         */
        protected void attachBaseContext(Context base) {
            if (mBase != null) {
                throw new IllegalStateException("Base context already set");
            }
            mBase = base;
        }
    
        /**
         * @return the base context as set by the constructor or setBaseContext
         */
        public Context getBaseContext() {
            return mBase;
        }
    
        @Override
        public AssetManager getAssets() {
            return mBase.getAssets();
        }
    
        @Override
        public Resources getResources() {
            return mBase.getResources();
        }
    
        @Override
        public PackageManager getPackageManager() {
            return mBase.getPackageManager();
        }
    
        @Override
        public ContentResolver getContentResolver() {
            return mBase.getContentResolver();
        }
    //=========ContextWrapper部分代码=========
    

    各种Context的作用:

    使用场景 Application Activity Service ContentProvider BroadcastReceiver
    Show a Dialog N Y N N N
    Start an Activity N(1) Y N(1) N(1) N(1)
    Layout Inflation N(2) Y N(2) N(2) N(2)
    Start a Service Y Y Y Y Y
    Bind a Service Y Y Y Y N
    Send a Broadcast Y Y Y Y Y
    Register Broadcast Receiver Y Y Y Y N(3)
    Load Resource Value Y Y Y Y Y

    注:其中ContentProvider,BroadcastReceiver指的内部方法中做为参数传递进来的Context。

    大家注意看到有一些NO后面的括号添加了一些数字,其实这些从能力上来说是YES,但是为什么说是NO呢?下面一个一个解释:

    N(1):启动Activity在这些类中是可以的,但是需要创建一个新的task。一般情况不推荐。
    N(2):在这些类中去layout inflate是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用。
    N(3):在receiver为null时允许,在4.2或以上的版本中,用于获取黏性广播的当前值。(可以无视)

    可以看出:和UI相关的方法基本都不建议或者不可使用Application,并且,前三个操作基本不可能在Application中出现。实际上,只要把握住一点,凡是跟UI相关的,都应该使用Activity做为Context来处理

    11. 理解Activity,View,Window三者关系。

    12. View的绘制流程。

    13. View,ViewGroup事件分发。

    14. 简述如何自定义控件。

    14. 保存Activity状态。

    15. Android中的几种动画。

    16. Android中跨进程通讯的几种方式。

    17. AIDL理解(此处延伸:简述Binder)。

    18. Handler的工作原理。

    19. Android内存泄露及管理。

    20. Fragment与Fragment、Activity通信的方式。

    21. Android UI适配。

    字体使用sp,使用dp,多使用match_parent,wrap_content,weight
    图片资源,不同图片的的分辨率,放在相应的文件夹下。可使用百分比代替。
    相关文章:https://www.jianshu.com/p/a4b8e4c5d9b0?tdsourcetag=s_pcqq_aiomsg

    22. app优化。

    23. 图片优化。

    24. HybridApp WebView和JS交互。

    25. 简述ANR及处理方式。

    26. 设计模式(此处延伸:Double Check的写法被要求写出来)。

    27. 简述RxJava。

    28. MVP,MVC,MVVM的区别及各自优势。

    29. 手写算法(选择冒泡必须要会)。

    30. JNI。

    31. RecyclerView和ListView的区别。

    32. Picasso,Glide对比。

    33. OKhttp, Volley, Retrofit对比。

    34. Sqlite的实现原理。

    Java:

    1. JAVA GC原理。

    2. 线程中sleep和wait的区别。

    3. Thread中的start()和run()方法有什么区别。

    4. 关键字final和static是怎么使用的。

    5. String,StringBuffer,StringBuilder区别。

    6. 讲一下内部类相关的东西(内部类的种类,内部类是如何持有外部类的)

    6. Java中重载和重写的区别。

    7. Http和https区别。(此处延伸:https的实现原理)。

    8. Http位于TCP/IP模型中的第几层?为什么说Http是可靠的数据传输协议?

    9. HTTP链接的特点。

    10. TCP和UDP的区别。

    11. Socket建立网络连接的步骤。

    12. Tcp/IP三次握手,四次挥手。

    13. Post中请求参数放在了哪个位置。

    14. HashMap是如何实现的。

    15. SparseArray和HashMap的区别。

    16. 你所了解的数据结构的知识(非科班同学要掌握一些)。

    相关文章

      网友评论

        本文标题:Android面试题汇总及答案

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