Fork me on GitHub

Android面试点整理

结合网上的Android面试点博客和自身的面试经历,整理一下可能会问到的问题。

这里放上原博客。我这里只是整理一下自己参考后得出的适合自己的答案,不一定适合所有人。

Android的启动过程

切勿回答生命周期

1、Launcher通过Binder进程间通信机制通知ActivityManagerService,它要启动一个Activity

2、ActivityManagerService通过Binder进程间通信机制通知Launcher进入Paused状态

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

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

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

Android的生命周期

基本会考,不算难也不可以丢分的地方。先放上生命周期示意图。

其中需要注意的是:

  • onCreate()和onDestroy(),处于entire lifetime(完整生命周期)。

  • onStart()和onStop(),处于visible lifetime(可见生命周期)。

  • onResume()和onPause(),处于foreground lifetime(前台生命周期)。

除以上经常提到的生命周期方法之外,还有两个常用来在横竖屏切换时用到的方法:onSaveInstanceState()和onRestoreIntanceState()。

场景模式下方法调用顺序

我曾遇到的面试问题之一,然后只答了一个场景,感觉自己被Q死了。。。。

测试环境:红米手机

  • activity首次启动

onCreate()->onStart()->onResume()

  • 按返回键返回至桌面

onPause()->onStop()->onDestroy()

桌面再点击进入的话,就会是:onCreate()->onStart()->onResume()

  • 按home键返回至桌面

onPause()->onStop()

  • 再点击应用启动activity

onRestart()->onStart()->onResume()

  • 锁屏关闭再启动

onPause()->onStop()->onRestart()->onStart()->onResume()

Android的启动模式和使用场景

Android共有四种启动模式:

  • standard
  • singleTop
  • singleTask
  • SingleInstance

既然提到了启动模式,就要先提一下Android的任务栈。在应用打开的时候,同时会创建一个任务栈,用于存储当前程序的所有activity实例,它包含了一个activity的集合。在任务栈中,只有位于栈顶的activity才可以与用户进行交互。在程序运行的过程中,任务栈会保留每一个activity的状态及信息,有序的列出它们各自的任务。在退出应用时,所有的activity会被清除出栈,任务栈被销毁。

不过这样的设计也有缺点。每开启一个新的页面,都会在任务栈中添加一个新的activity,可能会因为数据重复太多造成数据冗余,导致内存溢出(OOM);且用户应用操作深度太深的话,用户需要多次点击返回,才能将任务栈中所有activity清除出栈并销毁任务栈,这样就会使用户体验较差。

出于解决以上问题的原因,Android引入了启动模式。启动模式将决定activity跳转的过程中activity实例的生成及限制条件

standard模式

在每次跳转activity的时候,无论是否已经存在该activity的实例,系统都会在task中生成一个新的activity实例,并且放在任务栈的顶部。按下后退键,可以返回原来的activity实例。

singleTop模式

在每次跳转activity的时候,会检测一下当前任务栈栈顶是否已经存在该activity实例。如果存在,则重复利用;如果不存在,则生成新的实例。

singleTask模式

这个地方就要考虑到activity实例的入栈与出栈了。比如说,现有活动A与B,A已设置为singleTask模式。启动页面活动A,A跳转至B,B在跳转至A,A再跳转至B。在这个过程中,A页面的存在是唯一的,而B不是唯一的。这个结果与任务栈的设计有关。第一步,入栈A。第二步,入栈B。第三步,B出栈,A自动至栈顶。第四步,入栈新的B。最主要的步骤在于第三步。singleTask模式中,先检测栈中是否存在实例:如果存在,则将其之上的实例全部出栈;如果不存在,则生成新的实例。

与singleTop的不同是,前者只检查栈顶,后者检查全栈

singleInstance模式

这个模式会将跳转的新页面放入一个新的栈结构。这里要有一个例子要讲一下。

场景:A活动是standard模式,B活动是singleInsane模式。

1、A启动B,B点击返回退回A,A点击返回退出程序。

2、A启动B,B启动A(此时生成了一个新的A实例)。A点击两次返回才退回至B。B点击返回,退出程序。

这其中的主要差距在于,第二个场景中,B启动A,此时B所在的栈成为起点,A点击返回,退回的起点必须是B所在栈,然后才能推出程序。

总结

singleTop适合接收通知启动的内容显示页面。比如QQ的后台消息弹窗,在未启用QQ时,可以重复利用栈顶的消息弹窗,减少内存使用。

singleTask适合作为程序入口点,或者说重用率比较高的主界面。比如浏览器的主界面,不管怎么从别的应用启动,主界面是一定要启动的。

singleInstance适合需要与程序分离的界面,例如闹钟提醒。切忌将singleInstance页面作为中转页面。

Service的两种启动方式

startService

采用start的方法开启服务,步骤:

1、定义一个继承自service的类

2、在Mainfest.xml中配置service

3、使用context的startService(Intent)方法启动该service

4、不再使用时,使用stopService(Intent)方法停止该服务

这种启动方式下,service的生命周期如下:

onCreate()->onStartCommand()->onDestory()

一个service在它的生命周期内只会调用一次onCreate()和onDestory()方法,在之后的所有startService()方法调用后,都只会调用onStartCommand()方法。

特点:一旦服务开启,跟开启环境就没有任何关系了。且,开启环境内不能调用服务里面的方法。

bindService

采用bind的方法开启服务,步骤:

1、定义一个继承自service的类

2、在Mainfest.xml中配置service

3、使用context的bindService(Intent,ServiceConnection,int)方法启动该service

4、不再使用时,调用unbindService(ServiceConnection)方法停止该service

生命周期:

onCreate()->onBind()->onunbind()->onDestory()

注意:绑定服务不调用onstartCommand()方法

特点:bind的方法开启服务,服务的生命周期会与调用者的生命周期一致。绑定者可以调用服务里面的方法。

Broadcast的两种注册及发送方式

注册

动态注册

代码中的动态注册步骤如下:

1、实例化自定义的广播接收器

2、实例化意图过滤器,并设置要过滤的广播类型(如接受短信系统的广播)

3、使用context的registerReceiver(BroadcasrReceiver,IntentFilter,String,Handler)方法注册广播

静态注册

直接在Manifest.xml文件中的节点中配置广播接收器,like this:

<receiver android:name=".MyBroadCastReceiver">  
            <!-- android:priority属性是设置此接收者的优先级(从-1000到1000) -->
            <intent-filter android:priority="20">
            <actionandroid:name="android.provider.Telephony.SMS_RECEIVED"/>  
            </intent-filter>  
</receiver>

不同:

1、第一种不是常驻型广播,也就是说广播跟随程序的生命周期

2、第二种是常驻型,也就是说即使应用程序被关闭,如果有信息广播来,程序也会被系统调用自动运行

发送广播

广播的发送有无序广播和有序广播两种。

无序广播的使用:通过conetxt.sendBroadcast(Intent)或context.sendBroadcast(Intent,String)发送

有序广播的使用:通过context.sendOrderedBroadcast(Intent,String,BroadcastReceiver,Handler,int,String,Bundle)发送

区别:

无序广播:所有的接受者都会接收事件,不可以被拦截,不可以被修改

有序广播:按照优先级,一级一级向下传递,可以终止,也可以修改广播内容数据

HTTP与HTTPS

HTTPS(基于安全套接字层的超文本传输协议或者是HTTP over SSL)是Netscape开发的一个Web协议。

可以说:HTTPS=HTTP+SSL。

SSL,全称Secure Socket Layer,为Netscape所研发,利用数据加密技术,确保数据在网络传输过程中不会被截取及窃听。

相同点:

大多数情况下,两者相同,因为采用了同一个基础的协议。在客户端或浏览器,建一个连接到web服务器指定的端口。当服务器收到请求,返回状态码及消息。系统使用统一资源定位器URI模式,因为资源可以被唯一指定。

不同点:

1、URL开头不同,http以http://开头,https以https://开头

2、前者不加密,不安全;后者对传输数据加密,安全

3、前者标准端口是80,后者标准端口是443

4、前者无需证书,后者需要认证证书

5、在OSI网络模型中,http工作于应用层,https工作在传输层

PS:OSI网络模型,即常说的TCP/IP模型

七层从上往下依次分别为:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层。详细如下图:

HTTP里GET和POST的联系与区别

GET和POST本质上都是TCP/IP链接,为了解决请求相同可能导致的服务器处理

手写排序算法

这部分的话,主要就是对算法进行一个回顾。(冒泡选择插入要会写,其他的掌握思想)

重载与重写的区别

重写是子类对父类的允许访问的方法进行重新编写,返回值和形参均不发生改变。好处在于子类与根据需要,定义特定于自己的行为。

重载是指在一个类里面,方法名字相同,而形参不同,返回类型可同可不同。但注意,参数类型列表必须不同。

String,StringBuffer与StringBuilder的关系

String类型与StringBuffer类型的区别在于String是不可变的对象,因此在每次对String类型进行改变的时候其实都等于生成了一个新的String对象,然后将指针指向新的String对象,这样一来就会占用很多内存空间,JVM的GC也会开始工作,速度也会开始变慢。

而StringBuffer对象被改变的时候,都是对StringBuffer本身进行操作,并不会再生成新的对象。在字符串经常发生拼接的情况下,String对象会被JVM解释成StringBuffer对象的拼接,这时,StringBuffer是比String要快的。但以下情况,StringBuffer却比String要慢,比如:

String S1=”This is only a “+”simple”+”test”;
StringBuffer sb = new StringBuffer(“This is only a”).append(“simple”).append(“test”);

其实在JVM眼里,第一个的拼接几乎不存在,只相当于一个赋值,而第二个则是字符串的拼接,所以前者会更快些,但下例则会比前两者都要慢:

String S1=”This is only a”;
String S2=”simple”;
String S3=”test”;
String S4=S1+S2+S3;

这时候JVM就会当做三个String对象的拼接了。

集合类有哪些?常用的有哪些?

集合类主要有两个可实现的接口,包括Collection和Map。两个接口下各有很多已实现的类,主要如下:

1、List(存放有序、可重复)

ArrayList和LinkedList。由名称可得,Array更适合查询,Link更适合增删操作。

2、Set(存放无序、不可重复)

用的较少,有HashSet等。

3、Map(键值对、键唯一、值不唯一)

HashMap、Hashtable、LinkedHashMap和TreeMap。HashMap根据键的HashCode值存储数据,有键时访问速度很快;遍历时,取得数值的顺序随机(也可能是存在某种规律,规律未知,5个元素的时候输出顺序是1、2、5、3、4,一直如此,并未发生变化);HashMap不可同步。Hashtable则类似于HashMap的线程安全版,支持线程同步,也因此效率较低;此外不支持键或值为null。ConcurrentMap,线程安全,而且锁分离。LinkedHashMap则保证了有序性,其他与HashMap相同。TreeMap因为实现了SortMap接口,默认按升序实现排序,不允许key值为空,且非同步。

掌握每个类的循环遍历方法,如下:

1、List的遍历(略)

2、Map的遍历

有两种方法

keySet()方法

Iterator iterator = map.keySet().iterator();
while (iterator.hasNext()){
    Object key = iterator.next();
    System.out.println(key.toString()+":"+map.get(key));
}

entrySet()方法

Iterator iterator = map.entrySet().iterator();
while (iterator.hasNext()){
    Map.Entry e = (Map.Entry) iterator.next();
    System.out.println(e.getKey()+":"+e.getValue());
}

Android四大组件与五大布局

四大组件分别是Activity,Service,Content Provider和BroadcastReceiver。

activity:一个单独的activity通常就是一个单独的界面,监听并处理用户的事件。

broadcastReceiver:对外部事件进行过滤并做出反应。

service:一般是后台运行的程序,用来开发持续运行的监控类程序。

content provider:提供了一个将应用程序的指定数据集提供给其他应用程序的平台。

五大布局分别是LinearLayout(线性布局),RelativeLayout(相对布局),TableLayout(表格布局)(而不是GridLayout),FrameLayout(帧布局)和AbsoluteLayout(绝对布局)。

什么是ANR?如何避免

ANR,全称Application Not Responding,即“应用无响应”。

发生场景

APP的响应能力是由activity manager和window manager系统服务来监控的,但如果发生以下情况:

1、5秒内无法响应用户输入事件
2、BroadcastReceiver在10秒内无法结束

如何避免

不在主线程中做繁重的操作,如一次性读取加载很大的数据量,耗时的操作应尽量在子线程中完成。

Android多线程里如何更新UI

先贴出线程中更新UI经常会遇到的一种错误:AndroidRuntimeException:“only the original thread that create a view hierarchy can touch its views”.大意是只有创建视图的原线程才能更改UI视图。

在Android中,创建UI视图的main thread为UI线程,也称主线程。在主线程中,应用与UI组件发生交互,但一般考虑到操作的复杂度,都会分发到子线程中执行操作,而这些子线程不被允许更改UI视图,必须返回至主线程才能更改视图,否则会很容易报出上述错误。

更新UI的方法

  • handler.sendMessage()

在定义handler后,通过处理已封装好内容的message对象消息对视图进行更新操作,常用,代码略

  • handler.post()

handler.post()方法支持直接在当前子线程代码范围内更新视图

new Thread(){
            @Override
            public void run() {
                try {
                    //你的处理逻辑,这里简单睡眠一秒
                    this.sleep(1000);

                    mainHandler.post(new Runnable() {

                        @Override
                        public void run() {
                            //你的处理逻辑
                            titleView.setText("postRunnable——Result");
                        }
                    });
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();
  • runOnUiThread()

结构同上,不过无需使用handler对象

new Thread(){
            @Override
            public void run() {
                try {
                    //你的处理逻辑,这里简单睡眠一秒
                    this.sleep(1000);

                    runOnUiThread(new Runnable() {

                        @Override
                        public void run() {
                            titleView.setText("runOnUiThread——Result");
                        }
                    });
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();
  • view.post()
new Thread(){
            @Override
            public void run() {
                try {
                    //你的处理逻辑,这里简单睡眠一秒
                    this.sleep(1000);

                    viewPostBtn.post(new Runnable() {

                        @Override
                        public void run() {
                            titleView.setText("viewPost——Result");
                        }
                    });
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();

Bean,Model & entity

Bean

任何一个Java类都可以成为一个bean,这个类包含对象的属性,get、set方法和其他的业务逻辑。

Model

model是mvc中的概念,可以理解为view层展示数据的对象。

entity

数据表对应到实体类的映射。

-------------本文结束感谢您的阅读-------------