面试移动开发

Android面试题汇总及答案

2019-05-24  本文已影响30人  flypple

汇总一下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给提供了如下一些方法:

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. 进程和线程。

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

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

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

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

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

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

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

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

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

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

灰色保活,这种保活手段是应用范围最广泛。它是利用系统的漏洞来启动一个前台的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. 你所了解的数据结构的知识(非科班同学要掌握一些)。

上一篇下一篇

猜你喜欢

热点阅读