Android 面试题汇总

时间:2022-02-16 04:42:37

面试题基础储备

1、Activity相关

a、Activity的特点

1、可见  2、可交互

他之所以可交互,是因为他同时实现了Window.Callback和KeyEvent.Callback, 可以处理与窗体用户交互的事件和按键事件.这两个特点,是他和service最大的区别。一个Activity在创建与销毁的过程中,会经历一些生命周期。

b、Activity的生命周期

结论1、这个界面只要看不到了,它就一定执行了onStop方法

结论2、只要这个界面显示出来了,它就一定执行了onResume方法

结论3、onPause、onStop的情况下这个activity都有可能会系统回收。(简单    来说,只要这个activity不处于活跃状态,那么它就有可能被回收)

c、页面跳转必然会执行的方法

onResume,所以可以在onResume方法里面进行数据刷新,保证当前activity显示的都是最新状态

d、对话框的activity

启动activity默认是占满整个屏幕,如果想让这个activity以对话框的方式展示,则需要配置:android:theme="@android:style/Theme.Dialog",此时它下面的activity并不会执行onStop方法,而是仅执行onPause方法

e、横竖屏切换

默认情况下,横竖屏切换的时候会重新创建新的activity,新的那个activity会执行onCreate方法。可以通过AndroidManifest文件进行配置,让它横竖屏切换的时候不重新创建Activity,配置方法为

android:configChanges="orientation|keyboardHidden|screenSize",配置之后就不会创建新的Activity,而是执行当前activity的onConfigurationChanged。写死屏幕的方向:在AndroidManifest中配置

android:screenOrientation="landscape"

f、保存数据

除了在栈顶的activity,其他的activity在系统资源匮乏的时候,都有可能会被系统回收。如果activity被系统没有onDestory的情况下就被系统回收了,这时候系统会调用onSaveInstanceState方法,我们可以往bundle里面存放数据。在activity onCreate里面我们先判断一下bundle是不是为空,如果不为空,就代表这个activity之前被系统回收掉,应该恢复一下现场。我们就可以从bundle里面取值。

g、栈相关

概念:Android是用栈来管理Activity的,service是没有栈的。所以在service  启动activity一般要加上

intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)来开辟一个  新的栈。四大组件中只有Activity是有栈的。

启动模式:android:launchMode,

standard:默认的模式,没有限制,栈里爱有几个有几个

singleTop:栈顶只有一个,栈内可以有多个

singleTask:整个栈只有一个

singleInstance:霸道,整个栈只有自己,单实例

h、onNewIntent

当启动某一个activity,这个activity在栈中存在,并且需要被复用的情况下,(也就是配置了singleTop、singleTask、singleInstance的 属性),会调用此方法。如果需要传递参数,需要在onNewIntent里面setIntent,这样才能更新这个activity的intent

2、Service相关

a、Service的特点

不可见、不可交互,在后台运行的四大组件之一。

并不是所有的功能都需要界面。比如音乐播放。

b、Service的启动方式

startService、bindService

c、Service的生命周期

a、通过startService

Service会经历 onCreate 到onStart,然后处于运行状态,stopService的时候调用onDestroy方法。

这种方式,activity和service是相互独立的。如果是调用者自己直接退出而没有调用stopService的话,Service会一直在后台运行

b、通过bindService

Service会运行onCreate,然后是调用onBind, 这个时候调用者和Service绑定在一起。调用者解绑了,Service就会调用onUnbind->onDestroyed方法。

这种方式,activity就和service相互捆绑在一起了。所谓绑定在一起就共存亡了。调用者也可以通过调用unbindService方法来停止服务,这时候Service就会调用onUnbind->onDestroyed方法。

简单来说,startService的方式启动之后,Activity是调用不到Service里面的方法。

bindService方式启动之后,可以得到Service的实例,进而调用Service里面的方法。startService和bindService可以结合使用。无论怎么结合,Service的实例只有一个。

d、为什么使用Service

普通的线程也可以达到在后台做事情的功能,那么为什么使用 Service呢?是因为Service是系统的组件,它的优先级比普通的线程要高,不容易被系统回收。而且线程不好控制,Service相对好控制一些。运行在前台的Activity是不会被系统回收的,而Service如果不想被系统回收,就需要在Service中设置一下

startForeground(int,Notification)

具体的使用场景有:

a、拥有长连接QQ

b、定时轮询

c、服务里面注册广播接收者。有些广播接收者只能通过代码注册,比如屏幕锁屏、  屏幕解锁、电量发生变化等。

e、IntentService

普通的service ,默认运行在ui main 主线程.Sdk给我们提供的方便的、带有异步处理的service类,我们可以在OnHandleIntent() 处理耗时的操作

四大组件都是运行在主线程中

3、数据相关

a、存储

在Android中数据可以存储在四个地方

内存:读写速度最快,临时的值,程序退出或者界面退出就没有了

SharedPreference:持久存储,主要保存一些配置信息,如一个功能的开关

Sqlite:持久存储,关系型数据库,针对数据之间有很强关系的情况

文件:持久存储,针对数据量较大、并且没啥关联的数据

关于如何使用Sqlite来存储数据,传统的方式我们会使用到SQLiteOpenHelper ,然后会自己写一些sql语句,在真实的开发当中,我们往往会借助于一些第三方框架帮我们处理数据相关的逻辑,这样可以帮助我们帮主要精力花在其他功能实现上。我比较常用的是LitePal,它可以帮我们很轻松的进行数据库操作。

b、传递

a、通过intent传递

基本的数据类型:String、int、float等

对象:这个对象必须继承Parcelable接口。继承Parcelable接口代表这个对象可以   被序列化到内存中,和Serializable类似,只不过Serializable是将对象写到   文件当中。

b、通过Application传递

Application的特点:一个应用程序运行的时候只有一个,它的生命周期是最长的,比Activity、Service都长,只要这个程序在运行,无论是否在前台,它都会有一个Application对象。往Application存某一些对象,在页面跳转的时候会非常方便。

c、通过View来传递数据。在给View设置点击事件的时候,可以通过view.setTag来传递所需要传递的数据

c、解析

XML

效率低,体积大

JSON

效率高,体积小,使用广,可以使用Gson解析,也可以使用JSONObject进行解析,fastjson\其他第三方框架

复杂的结构:使用Gson,创建javabean来进行解析

简单的结构:{"success":"true"},直接使用JSONObject进行解析. 如果使用JSONObject进行解析的话,有几点需要记忆:

{}---JSONObject

[]---JSONArray

无符号:基础数据类型

d、ContentProvider

目的:将自己应用的数据提供给其他应用

把自己的数据通过uri的形式共享出去,这个uri是事先约定好的。

android  系统下不同程序 数据默认是不能共享访问,通过ContentProvider可以将自己应用的数据提供给别的应用。

我们在写内容提供者的时候,需要写一个类继承ContentProvider,然后实现里面的增删改查方法

query(Uri, String[], String, String[], String)

insert(Uri, ContentValues)

update(Uri, ContentValues, String, String[])

delete(Uri, String, String[])

工作中用的很少,我们几乎不需要写ContentProvider,因为没有这个需求。可能会用到的是调用系统的ContentProvider,比如获取系统联系人,系统短信。

4、广播相关

a、广播发送

广播的发送有两类,一种是系统本身就有的,一种是我们自己写的广播。

发送方式:

有序广播sendOrderedBroadcast

按顺序依次的发送给每一个接收者,而每一个接收者在收到这个广播的时候,可以将这个广播abort掉,也可以将这个广播继续传递到下一个广播接收者。广播接收者在注册的时候可以指定优先级,用于提高接收到广播的顺序。

无序广播sendBroadcast

不能被abort掉

b、广播接收

广播接收者注册的方式也有两种,一种是动态注册,一种是静态注册。有一些系统   的广播只能使用动态注册,这种广播产生的频率是比较高的。比如电量变化的广播,    屏幕解锁的广播。电量变化的广播。

c、广播的作用

广播一般是用于跨进程通讯的时候。两个进程间要进行通讯的情况下,使用广播显  得非常的方便。如打电话的时候状态栏颜色变了,此时就是可以使用广播在电话的进  程中发送一个广播,然后再状态栏的进程当中进行接收。接收到之后改变状态栏颜色。  onReceive方法也是在主线程当中运行的。在广播中启动Activity也是需要配置

intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)来开辟一个新的栈。

5、跨进程访问

a、Android中跨进程访问的方式:

主要有三种:1、广播 2、AIDL  3、intent启动其他应用activity、内容提供者

b、什么是AIDL:

AIDL的全称是android interface definition language  接口定义语言。它所做的事情就是跨进程调用另外一个服务里面的方法的。所以aidl就得使用到bindService方法。在手机卫士中挂断电话用到过AIDL。我们要获取手机系统的电话服务,用到了aidl 它其实工作的原理就是绑定到一个远程的服务上。然后这个远程服务会返回回来一个代理对象。这个代理对象里面的方法,就是我们定义的aidl里面的方法。

aidl的写法也非常简单,主要分两种。调用系统服务的话,直接找到那个服务的aidl拷贝到我们的项目中就可以了。另外一种是我们自己写aidl,主要注意的一点是aidl是没有访问修饰符的。因为aidl本身就是公开的,就是为了给别人调用的,不需要私有的private方法。

其实,在实际的开发过程中,比较少会写这个aidl,但是你得懂它相关的知识点。

在面试的时候,这个也是经常被问的。所以大家要熟悉这个问题的回答方法。

我以前面试别人的时候,只要他能回答上几个关键点,我就会让他过我这关噢。

1、跨进程  2、aidl在服务端和客户端都需要有一份  3、通过绑定bindService的方式获取远程服务对象的代理IBinder,就可以调用相关的方法。

c、跨进程的底层原理

广播、aidl 的底层实现机制是Binder,Binder是基于共享内存的一种IPC方式

如何自己实现进程间的通信?

6、Handler相关

a、为什么使用Handler

Handler是用来进行线程间的通信。因为Android系统有一个特性,ui操作必须得   在主线程当中进行。所以在子线程进行完耗时操作的时候,如何告诉主线程进行ui       界面更新,这就需要使用到线程间的通讯了。

b、Handler、Looper、MessageQueue、Message的关系

任何一个线程,无论是子线程还是主线程,都维护着一个消息队列,这个消息队列   就是MessageQueue,有了这个消息队列之后,是需要不断的从这个消息队列里面取   出消息进行处理的,这个就是Looper做的事情。那么,谁往这个消息队列里面丢消   息呢?那就是Handler了。

Looper是用来管理所属线程的消息队列MessageQueue的。

每一个线程都需要有一个looper,每一个looper管理一个MessageQueue.

Handler.sendMessage的意思是将某一个message放到MessageQueue中去,looper   是个死循环,不断的读MessageQueue中的新消息。

要让looper的死循环运行起来,得调用Looper.loop()方法。

我们通常都会在子线程中,发一个消息到主线程中的messagequeue中去。

Handler到底是往主线程的MessageQueue发送消息呢还是往子线程的   MessageQueue发送消息呢?这取决于Handler在哪里创建。如果Handler在主线程     中创建,那么这个Handler就会把消息发到主线程的消息队列,如果Handler是在子     线程中创建,这个Handler就会把消息发到子线程的消息队列。这里需要注意的是,   子线程的Looper需要我们自己手动启动,要调用Looper.prepare()和Looper.loop()方   法,主线程的Looper系统已经帮我们启动了,因此我们不需要为主线程的     Looper调用loop()方法

c、子线程-->主线程   主线程-->子线程?

view.post(r)或者activity.runOnUiThread(r)都是将runnable对象丢到了主线程的消息队列中。

7、ANR相关

a、什么是ANR:

ANR是android not response   安卓无响应,这里指的是主线程无响应。将耗时的     操作放在子线程中进行可以有效的避免ANR。

b、出现的原因:消息的超时信息

出现ANR的根本原因是主线程堵塞了,来不及处理消息。任何UI操作都是一个消   息,比如触摸、按键,弹框,setText等,当这些消息得不到及时的处理,就会出现   ANR了。每一个发往主线程的消息其实都带有一个超时时间,超过了这个时间,这   个消息还没有处理,就会出现ANR

Activity的超时:5s

BroadcastReceiver超时:10s

Service超时:20s

c、解决的方法

如果出现了ANR,我们可以通过log信息以及traces.txt文件进行分析。

traces.txt里面记录的是stack信息。

traces.txt的路径:/data/anr/traces.txt

8、ListView的相关

a、ListView为什么要优化?

因为不优化的话,滑动ListView会卡顿。卡顿的原因在于getView太耗时了。

getView是在主线程执行的,如果这里面太耗时的话,ui肯定卡顿。耗时主要集中在两个方面:加载布局文件、findViewById

b、ListView怎么优化

减少加载布局文件的次数:使用缓存convertView

减少findViewById的次数:使用ViewHolder

c、数据错乱的问题

i、正是因为使用了缓存,所以才有可能造成数据错乱。所以在刷新数据的时候,一定要考虑周全。

ii、消息处理时机引起的数据错乱

d、ListView展示多种布局类型

i、告诉系统你有几种布局类型getViewTypeCount

ii、告诉系统,什么情况下显示哪种布局类型getItemViewType(int position)

iii、在getView中根据getItemViewType的返回值加载对应的布局文件。在刷新数据的时候也得根据getItemViewType的返回值进行数据刷新。

9、自定义控件相关

a、自定义属性

步骤:

1、起一个名字

在values的目录下需要建立一个文件,叫做attrs.xml 这个文件是定义属性规则的。一个属性的名字、取值类型、取值范围

2、用这个属性

在布局文件中需要加入命名空间

3、得到这个属性的值

最后在自定义控件中获取这些值。

b、绘制流程

涉及的方法:

onMeasure:计算大小

onLayout:计算位置

onDraw:开始绘制

调用顺序是onMeasure-->onLayout--->onDraw

这三个方法是任何一个View在屏幕上呈现出来的时候都一定会调用的三个方法,三个方法的作用不一样。

其他知识:

int widthMeasureSpec---封装了两个信息,一个是具体大小的值,一个是模式

模式就是父控件对你的限制。

int widthSize = MeasureSpec.getSize(widthMeasureSpec);// 获取宽度

int widthMode = MeasureSpec.getMode(widthMeasureSpec);// 宽度的模式

// MeasureSpec.AT_MOST;--->wrap_content至多的模式。

// MeasureSpec.EXACTLY;--->确定的模式,match_parent,写死成多少dp

// MeasureSpec.UNSPECIFIED;--->未确定的模式 ScrollView:不对孩子的高度进行限定

10、动画相关

分类

a、帧动画

帧动画,就像GIF图片

b、补间动画tween

只是改变了View对象绘制的流程,而没有改变View对象本身。AlphaAnimation、   RotateAnimation、ScaleAnimation、TranslateAnimation

c、属性动画

Android 3.0中才引进,更改的是对象的实际属性。如果要在2.x版本也要支持   属性动画,则需要加入兼容包nineoldandroids.jar

ValueAnimator、ObjectAnimator的使用

具体面试题:59

11、设计模式相关

a、单例:保证就只有一个实例

单例的写法:

1、私有的构造方法

2、私有的,静态的对象instance

3、公开的,静态的getInstance方法

注意事项:在getInstance的方法前加上线程同步synchronized

b、适配器:数据和View的桥梁。

c、观察者:本质就是回调,一堆回调

d、MVC:MVC 是 Model、View、Controller 三部分组成的。其中 View 主要由 xml 布局文件,或者用代码编写动态布局来体现。Model 是数据模型,其实类似 javabean,不过这些 JavaBean 封装了对数据库、网络等的操作。Controller 一般由 Activity 负责,它根据用户的输入,控制用户界面数据的显示及更新model 对象的状态,它通过控制 View 和 Model 跟用户进行交互。

MVC在Android当中体现的并不是很明显,在web当中体现的明显些。

回答设计模式相关的问题时,应该结合自身的开发经历进行开展。

12、屏幕适配相关

a、概述:

用dp  不用px  这仅仅是适配的一部分而已。这里需要注意的一点是,android是不可能做到完全适配每一个屏幕的,只能达到大体相近。如果你要达到完全适配,你就必须在每一个drawable-目录下都放入整套切图,而且要建立不同分辨率的layout-854*480的布局文件,这简直是不可想象。没有一个公司会这么做。大部分的人是怎么做的呢?

b、切图适配:

一般都是找一个使用比较广泛的分辨率当作标准。比如1280x720  密度为2,然   后设计师就会根据这个分辨率进行设计,然后切图。我们就需要将设计师出的切图   放到drawable-xhdpi目录下就可以了。一般情况下,我们不需要在drawable-hdpi   目录、drawable-mdpi目录、drawable-xxhdpi目录下放切图了,只在drawable-xhdpi下放一套就够了。在真正运行的时候,系统会自动对图片进行处理。这时候,绝大部分切图显示都是正常的。如果你觉得哪几张图片显示不清晰了,就只要再出这几张切图放到drawable-xxhdpi目录或者其他目录即可。

c、布局适配:

尽量用一些确定的东西来确定不确定的东西。多用RelativeLayout、LinearLayout

整体是一个LinearLayout,垂直方向。中间的edittext是match_parent.最底下整体布局应该是RelativeLayout,OK按钮是layout_alignParentRight,确定好OK之后,在确定cancel的值,切勿不要先确定cancel的位置再确定ok的位置。

按照上诉布局做法,在超大的屏幕上显示的时候发现,edittext变长了点,但是整个的位置结构是没变的,这个布局的适配就算是过关的。

其实屏幕适配总结起来就几个方面:布局适配、图片适配、单位适配、代码适配

13、图片相关

a、图片的缓存(多图片的加载)

任何一种缓存的目的是都是为了加载更快,那么存在哪里呢?存在内存中是最好的,   因为内存的读取速度最快,但是内存的大小是受限制的。所以,我们只能在内存中   加载一部分图片。使用LRUCache可以对内存中的图片进行有效管理。

三级缓存:内存缓存--本地缓存--网络缓存

b、大图片的加载

如果一张图片有几十M,那么这一张图片一加载肯定就OOM了,根本就不需要使   用LRUCache,所以加载大图片的时候需要注意:

1、仅请求图片的大小,inJustDecodeBounds = true,仅请求图片大小,而不会   加载图片到内存;

2、得到图片的大小,再得到所需要显示控件的大小,根据这两个值,合理设置   BitmapFactory.Options的inSampleSize值,减少图片内存占用

14、View的事件传递

View:只有dispatchTouchEvent、onTouchEvent

ViewGroup:dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent

事件传递dispatchTouchEvent  为的就是找到响应的那个View  这个方法的返回值会使用 onInterceptTouchEvent的返回值,true代表找到响应的View,false代表没有 找到响应的View

事件拦截onInterceptTouchEvent 返回值为true代表将事件拦截在此ViewGroup中,此View 就是响应的那个ViewGroup

事件响应onTouchEvent  true代表消费了这个事件,这个事件就不往上传递,false代表没有消费此事件,此事件继续向上传递。

可以以爷爷、父亲、孩子吃苹果的例子加强记忆。一个苹果到了爷爷的手中,爷爷可以决定自己吃,也可以决定把苹果给父亲吃。如果爷爷吃了这个苹果,父亲肯定就得不到这个苹果。如果爷爷把苹果给了父亲,那父亲得到苹果之后也有两个选择,自己把苹果吃了,或者把苹果给孩子。孩子拿到苹果了,可以选择吃,也可以选择不吃。如果不吃的话,孩子没办法把苹果向下传递,他就会把苹果又还给父亲。父亲拿到苹果之后,可以吃了它,也可以再把它还给爷爷。爷爷拿到苹果之后也同样有两种选择。

苹果从爷爷-父亲-孩子,代表事件传递

苹果从孩子-父亲-爷爷,代表事件消费

事件传递的方向是由父控件到子控件,事件响应的方向是从子控件到父控件。

15、内存泄露相关

垃圾回收机制:垃圾回收器仅会回收没有人引用的对象。

内存泄露

内存泄漏本身不会产生什么危害,真正有危害的是内存泄漏的堆积。Android应用内存泄漏的的原因有以下几个:

1、register之后没有unregister

2、查询数据库后没有关闭游标cursor  file没有close
3、构造Adapter时,没有使用 convertView 重用 (内存的溢出)
4、Bitmap对象不在使用时调用recycle()释放内存 
5、对象被生命周期长的对象引用,如activity被静态集合引用导致activity   不能释放

内存泄漏如何解决:

通过内存分析工具 MAT(Memory Analyzer Tool),找到内存泄露的对象

生成hprof文件可以通过adt的工具也可以通过代码生成。debug的dump方法

16、提高ui流畅度

1、优化布局层次。不要不断的嵌套LinearLayout ,多使用RelativeLayout 尽可能的减少布局的层次。左边是图片,右边是文字的,可以就使用一个TextView来完成。

详见:提高UI流畅度_第6期.pdf文件

2、合理使用控件

ListView嵌套GridView

ScrollView嵌套ListView

ListView嵌套ListView

这些情况都应该避免。

17、优化性能

内存:

a、内存溢出(主要是图片,其次是无用的对象太多)

b、内存泄露

布局:

a、尽可能减少布局的嵌套层级

尽量多用RelativeLayout可以很有效的减少布局的嵌套层级。也可以使用hierarchyviewer这个工具来检查布局层次

b、不用设置不必要的背景,避免过度绘制
比如父控件设置了背景色,子控件完全将父控件给覆盖的情况下,那么父控件就没有必要设置背景。

c、使用<include>标签复用相同的布局代码
d、使用<merge>标签减少视图层次结构

e、通过<ViewStub>实现 View 的延迟加载

资源:

f、线程池的使用

18、使用过的一些工具

monkey、traceview、mat、beyond compare、hierarchyviewer

monkey:可以使用Monkey测试工具帮助我们完成程序的健壮性。

命令如下:adb shell monkey -p 包名 --ignore-crashes --ignore-timeouts 10000

具体工作的使用场景将会和logcat命令结合起来。

adb logcat -v time > D:\log.txt

traceview:分析程序执行的方法效率,可以图形化的表现出方法的执行时长。从而帮助开发者优化性能。

hierarchyviewer:分析布局视图

来源于www.itcast.cn  之zheng_teacher