Android面试题集锦(五)

时间:2022-05-14 15:04:37

2016.8.26更新........................................................................

(78):使用MAT进行内存泄漏检查步骤总结
        我们通常情况下是可以通过DDMS的Devices界面选中想要观察的进程,通过点击Update Heap并且点击Cause GC的方式查看当前应用程序的内存使用情况的,如果在反复操作某一功能时,导致应用程序的内存持续增高而不会下降的话,那么我们就认为出现了内存泄漏现象了,那么这时候仅仅只是知道有内存泄漏现象了,具体的问题是出在代码的那个位置通过DDMS根本没法查看到,这时候就需要用到MAT(Memory Analyzer)工具了,使用该工具有一下几个步骤:
        (1):生成一个.hprof文件,具体来讲的话,就是在Devices界面中选中我们想要查看的应用程序进程,点击Dump HPROF file按钮,隔一会就生成包名.hprof文件,这个文件实际上就是应用程序当前的内存镜像了;
        (2):如果我们使用独立版本的MAT的话,还需要将.prof文件通过SDK的hprof-conv工具进行转换,因为我们生成的.hprof文件是Dalvik格式的,我们需要将其转换为J2SE格式,具体是通过hprof-conv命令完成的;
        (3):通过MAT工具打开转换后的.hprof文件,它里面有两个视图是我们经常使用的,一个是Histogram(柱状图),这个视图会列出内存中每个对象的名字、数量和大小;Dominator Tree(支配树)会将所有内存中的对象按大小进行排序,我们可以分析对象之间的引用结构;
        (4):为了分析内存泄漏,我们需要分析Dominator Tree里面的内存信息,一般是从大到小进行排查,如果我们怀疑某一个对象可能会产生内存些泄漏的话,可以选中该对象,点击右键-->Path To GC Roots-->exclude wake/soft references,至于为什么要除去软引用和弱引用,因为两者被回收的概率比较大,不太会造成内存泄漏现象;
        (5):当然在Histogram和Dominator Tree界面中是支持检索功能的,具体来说就是界面的第一行中可以输入我们的正则表达式来进行查找;

(79):如何为我们的apk瘦身?

        (1):剔除掉冗余的代码与不必要的jar包;具体来讲的话,我们可以使用SDK集成的ProGuard混淆工具,它可以在编译时检查并删除未使用的类、字段、方法和属性,它会遍历所有代码找到无用处的代码,所有那些不可达的代码都会在生成最终apk文件之前被踢除掉,同时他还会重命名类、属性、接口;在使用ProGuard的时候需要注意,它默认情况下是会对第三方库进行混淆的,而第三方库有的已经进行过混淆,有的使用了Java反射技术,所以我们在进行代码混淆的时候要排除这些第三方库,具体来讲的话就是在混淆规则文件中添加一些规则;也可以使用AndResGuard来剔除哪些冗余代码,他可以做到直接处理安装包而不依赖于源码,也不依赖编译过程,也就是说仅仅输入一个安装包,就得到一个混淆包;

        (2):剔除无用的资源;使用ProGuard的话,仅仅是对代码进行了分析,但是对于图片资源的话,就束手无策了,这个时候可以使用Lint工具,它是一个静态代码分析器,你只需要通过调用./gradlew lint命令就可以查找到所有无用的资源文件,在检查结束之后会提供一份详细的资源文件清单,只要我们不通过反射来访问这些无用的资源,就可以放心的删除这些资源了;有一点需要注意,Lint会分析res下面的资源文件,但是会跳过assets下面的资源文件,Lint不能判断某个asset文件在项目中是否可用,因此这个文件夹下面的内容就需要我们自己去维护了;

        (3):对资源文件进行取舍;对Android所有屏幕密度下的文件夹全部都提供一套图片资源的做法是非常不理智的,虽然Android支持多种屏幕密度,但是这并不代表我们需要为每一种屏幕密度都提供一整套资源,我们应该尽量使用一套图片资源,对于一些图片在不同分辨率手机上出现差异较大的情况再去考虑在相应文件夹下放入特定的图片;尽量重用图片,比如对称图片的话,只需要提供一张,另外一张可以通过代码旋转的方式实现;去除那些无用的类库,比如我们在使用第三方库的时候,可能只用到库中的一部分功能,对于那些没用到的功能部分,能不引入就不引入;

        (4):对图片资源进行优化,在不降低图片的显示效果、保证APK显示效果的前提下压缩图片文件的大小,我们可以使用tinypng工具来实现,他的原理是通过合并图片中相似的颜色,将24位的png图片压缩成小得多的8位色值的图片,并且去掉了图片中不必要的metadata元数据,这种方式几乎能完美支持原图片的透明度;此外我们可以使用webp格式的图片,这是google推出来的意图改变web图片jpg、png、gif三分天下局势的一种图片格式,在同画质的情况下,他所占用的空间是最少的,并且支持无损和有损压缩、alpha通道;

(80):Android反编译步骤

        (1):首先我们应该了解清楚的就是反编译用到的工具有哪些:Dex2jar、jd-gui;

        (2):首先我们需要解压我们的.apk文件,解压完成之后会发现里面存在一个classes.dex名字的文件,接着我们使用Dex2jar工具将.dex类型的文件转换为一系列的.jar文件,在.jar文件中存放的是一系列的.class文件,Dex2jar是命令行工具;

        (3):有了一系列的.class文件之后,我们可以使用jd-gui工具来查看这些.class文件,在里面,你会发现存在大量的a,b,c.....命名的类、接口、属性等等,原因就是apk文件在打包发布的时候被ProGuard进行了混淆;

2016.8.27更新........................................................................

(81):Json与XML想比有什么优劣势

        (1):在编码难度方面,Json的编码明显比XML的编码更加容易,我们即使不借助工具也是可以写出Json代码的;
        (2):在解码难度方面,XML解析得考虑子节点和父节点,而Json解析难度几乎为0;
        (3):在扩展性方面,两者均有良好的扩展性;
        (4):Json相对于XML来讲的话,数据体积更小;
        (5):对Json数据的解析熟读要远远快于XML数据;
        (6):Json对数据的描述性要比XML更差;

(82):Android N新特性

        (1):Multi-Window Support(分屏多窗口支持),全新的Android N支持两种新的窗口模式,一种是Side-By-Side模式:两个App瓜分一个屏幕,一种是One-above-the-other:一个App浮动在另一个App之上;
        (2):Direct Replay & Bundle Notifications(快速回复和归拢通知),用户可以在通知栏快速回复消息、邮件等通信信息;归拢通知就是可以将同一个App发送过来的消息放到同一个通知里面,这样的话避免了一条消息就有一个通知的情况,减少通知占用的空间;
        (3):Data Saver(节约数据),一个系统级的控制开关,如果开启的话,会通知所有的App我当前的流量是有限的,大家不要用太多,当App收到这个通知后,就会尽量减少网络请求,节省用户流量;
        (4):支持java 8
        (5):Java Android Compiler Kit全新的jack编译工具集,不再需要我们操心65k方法的限制问题,因为jack在Compiler的时候就已经解决了这个问题;
        (6):Background Optimizations(后台优化),Android N为了节省内存和电量,移除了三种广播通知,分别是ACTION_NEW_PICTURE:拍摄图片广播,ACTION_NEW_VIDEO:拍摄视频广播,CONNECTIVITY_ACTION:用户网络发生变化广播,这三类广播的移除对于创建不被杀死的Service以及推送服务来讲的话是致命的一击;
        (7):Scoped Directory Access(特定文件夹权限),有的时候App需要读写特定的文件夹,我们可以申请READ_EXTERNAL_STORAGE或者WRITE_EXTERNAL_STORAGE权限,但是这个总会让用户感觉不太放心,这个时候可以使用特定文件夹权限;

(83):Android JNI开发步骤(借助于NDK)

        Android为了便于开发人员调用C、C++等本地代码,为我们封装了一层接口,也就是JNI(Java Native Interface Java本地接口),我们都知道Java是跨平台语言,正是因为跨平台导致了Java程序在和本地交互的时候出现了短板,使得他与本地代码的交互能力不是很强,于是Java为我们提供了JNI来专门和本地代码来进行交互,具体到Android中,为我们提供了NDK来方便的通过JNI访问本地代码,接下来我们简单描述下通过NDK开发JNI的流程,具体的讲解可以查看这篇博客:
        先来说说通过JNI调用本地代码的原理:
        (1):我们把C/C++源程序编译打包成动态库(dll或者so),存放在我们项目中的lib目录下;
        (2):在java程序中我们调用带native修饰的方法,他的具体实现是在C/C++程序中的,系统会调用动态库中相应的实现方法,如果有需要的话,还会返回处理结果;
        开发步骤:
        (1):首先创建一个Android工程,和我们平常创建没什么区别;
        (2):添加本地动态库支持,具体就是右击刚刚创建的Android项目,找到Android Tools,接着找到Android Tools中的Add Native Support,然后输入动态库的名称(系统会为我们自动加上前缀"lib"和后缀"so"的),创建完成之后会在项目中生成一个jni的目录,目录里面会有一个.cpp以及Android.mk文件;
        (3):创建两个文件,一个是java源文件,这个文件中其实就是对native方法的声明而已,我们称之为代理动态库方法;另一个是C/C++源文件,是对代理方法的实现;首先对于Java代理文件,静态导入动态库,并编写好代理方法,然后利用javah命令生成C语言的方法签名,接着复制方法签名到C/C++源文件中,补充参数名和方法体就可以了;
        (4):接着我们需要完成C/C++源文件的方法体内容,也就是我们具体的业务逻辑操作部分了;
        (5):最后就是在我们的程序中调用native方法来调用C/C++代码了;

2016.8.29更新........................................................................

(84):Android启动一个应用的流程是怎样的呢?

        见博客:我眼中的Activity的工作过程

(85):为什么Zygote死掉之后会重启呢?

        在init进程启动之后解析init.rc文件的时候,Zygote是被作为服务定义的,并且被声明为是自动重启的,因此一旦Zygote进程退出的话,init是会收到子进程退出信号进而重新启动Zygote服务,同样重启之后Zygote也会fork出System Server子进程,在System Server被Zygote作为子进程启动之后,Zygote会通过信号监听子进程的状态,一旦System Server进程退出,那么他会杀死Zygote进程,进而会发信号给init进程,init进程同样会重启Zygote服务的,这就是为什么Zygote死掉之后还是会重启;

(86):进程间的通信方式有哪些?各有什么优缺点?

        进程间通信方式有:消息队列、管道方式、共享内存、信号量机制、Socket、Binder
        对于消息队列、管道方式而言,通信过程是需要两次拷贝操作的,具体来讲的话第一次是将数据写到消息队列或者管道里面,第二次就是从消息队列或者管道中读取数据;而对于共享内存方式的话,是不需要拷贝操作的,但是在处理同步问题上面是很容易引起死锁的;而对于信号量机制的话,主要是用于进程或者线程间同步操作的,不太适合于进程间的通信操作;而我们通常用到的Socket相对来说是可以做到进程间通信的,但是他的传输效率相对来说是比较低的,只要使用于网络或者不同设备之间的通信,同时呢,在安全性方面做的也不是很到位;对于我们Android的Binder而言,是可以做到安全性检测的,具体来讲就是通过为每一个应用程序分配一个唯一的UID,在Server端我们可以使用这个UID来进行身份识别,如果识别不通过的话可以拒绝服务,其实Android中的权限控制就是用来干这个的,如果用户允许的话,那么在提供服务的时候就不会出现拒绝的情况了;

(87):Android性能优化

        参看博客:Android性能优化建议

(88):Android操作系统的四层架构

        Applications(应用程序层)、Application Framework(应用程序框架层)、Libraries(系统库)以及Android Runtime、Linux Kernal(Linux内核)

2016.9.4更新........................................................................

(89):Actvity中Window的创建过程

        Activity的启动过程相对来说还是比较复杂的,最终都会执行到ActivityThread里面的performLaunchActivity方法来执行整个启动过程,在这个方法里面会利用Instrumentation对象通过反射创建出Activity对象,接着创建与当前Activity相关的ContextImpl上下文环境对象,其实在创建ContextImpl的时候是会为我们创建一个参数为Display对象的WindowManagerImpl对象出来的,用来管理我们随后的创建出来的Window对象,只不过因为现在Window对象还没创建出来,所以现在的WindowManagerImpl是没有和任何Window对象发生联系的,因为Android程序的运行是需要上下文环境支持的,因而接着会调用Activity的attach方法将当前创建好的Activity与ContextImpl进行绑定,在attach方法里面除了要将ContextImpl上下文和当前Activity绑定之外,还会创建一个PhoneWindow对象出来,并且为该PhoneWindow设置回调CallBack接口,这样的话当PhoneWindow接收到外界的状态变化之后就会回调Activtiy中的相应方法了,接着调用setWindowManager方法,为我们创建的PhoneWindow对象设置WindowManager管理器,具体来讲就是将我们在创建ContextImpl的时候创建的WindowManagerImpl对象和当前的PhoneWindow绑定到一起就可以了,到此为止的话,Window以及其对应的WindowManager已经创建好了,那么有了Window之后,我们的View是怎么添加上去的呢?因为我们知道View是Android中视图的呈现方式,但是View是不能够单独存在的,他是必须附在Window上面的,这里就用到我们平常为Activity设置布局的setContentView上面了,Activity的具体实现都会转交给它里面的Window来实现,调用了PhoneWindow的setContentView首先会查看DecorView存不存在,不存在的话则创建一个DecorView出来,但是此时的DecorView还只是空白的,需要通过generateLayout方法加载具体的布局到DecorView上面,具体的布局文件的话可能和系统版本以及主题有关系,随后会通过LayoutInflater的inflate通过pull解析xml的方式将我们自己的布局文件添加到DecorView里面的mContentParent中,注意只是添加到mContentParent中,接着回调Activity的onContentChanged方法,告诉Activity DecorView已经初始化好了,即Activity的布局文件已经成功的添加到了DecorView的mContentParent里面啦,但是这个时候DecorView还是没有被WindowManager添加到Window上面的,因为View不能单独存在,所以我们需要将DecorView添加到Window里面,具体来讲的话执行的是ActivityThread的handleResumeActivity方法,在handleResumeActivity方法里面首先会执行

performResumeActivity方法,performResumeActivity里面会通过performResume方法执行我们Activity的生命周期函数onResume,接着会执行Activity的makeVisible方法,通过WindowManager的addView方法将当前DecorView添加到Window里面,并且随后将DecorView显示出来,这样的话在调用setContentView之后,我们的Activity就显示出来我们想要的布局了,这只是Windwo的初始化过程了,那么Window的添加/删除/更新操作是怎么做的呢?

(89):Window的添加过程

        其实就我个人理解的话,Window的添加过程实际上可以理解为Window中View的添加过程,因为Window是因为View的存在才有意义的,没有View的话,可以认为Window没有存在的意义,在Window的创建过程中我们就已经知道了在创建Window之后会为Window设置一个WindowManager管理器,具体这个管理器的类型是WindowManagerImpl类型的,那么具体的添加操作就该在WindowManagerImpl里面了,调用的是它里面的addView方法,而WindowManagerImpl里面所有方法的实现都是通过桥接模式转交给WindowManagerGlobal实现的,具体的实现过程就转到WindowManagerGlobal里面了,查看他里面的addView方法,发现做了一下几件事,(1):首先进行了一些输入参数的检查,不合法的话抛出相应异常信息,(2):接着创建了一个ViewRootImpl对象,在创建ViewRootImpl的时候会创建一个Session对象出来,而Session对象的创建是通过调用的WindowManagerService的openSession方法了,(3):随后会将我们当前的ViewRootImpl对象放到mRoots列表中,将当前的View对象放到mViews中,将当前的LayoutParams对象放到mParams中,(4):最后调用ViewRootImpl的addView方法,这个方法里面真正的进行View添加操作是通过创建ViewRootImpl的时候创建的Session对象完成的,而在Session中添加操作实际上是通过WindowManagerService完成的,后期的添加操作是有涉及到IPC通信的;想要了解更多添加操作的细节,可以查看这篇博客

(90):Window的删除过程

        Window的删除过程整个执行流程其实和添加过程很类似的,同样是通过WindowManager实现的,他是在创建Window的时候被绑定,WindowManager的实现类是WindowManagerImpl,而WindowManagerImpl的具体实现会通过桥接模式转交给WindowManagerGlobal,因而只需要查看WindowManagerGlobal中的removeView方法即可了,在该方法里面做了一下几件事:(1):首先通过数组遍历的方式查找当前View的索引值,(2):接着找到当前索引值所对应的ViewRootImpl,(3):通过ViewRootImpl的die方法来删除当前Window,die方法里面会执行ViewRootImpl的doDie方法,而在doDie方法里面会通过创建ViewRootImpl的时候创建的Session对象来进行具体的删除操作,而Session里面得删除操作同样也是通过WindowManagerService的removeWindow操作完成真正的删除Window过程的;(4):在将当前Window删除之后,    还需要将当前View从mViews列表中删除,将当前ViewRootImpl从mRoots删除,将当前LayoutParams从mParams中删除;想要了解更多添加操作的细节,可以查看这篇博客

(91):Window的更新过程

        说到Window的更新过程,个人认为其实也就是Window里面View的更新过程了,和创建/删除过程一样,最后照样也是到了WindowManagerGlobal里面执行具体的操作了,具体来讲的话就是执行updateViewLayout方法了,这个方法里面做了以下几件事:(1):首先获取到最新的View的LayoutParams属性,然后将其设置到我们当前需要更新的View上面;(2):接着通过数组遍历的方式找到当前View对应的索引值,同时将该索引值对应的LayoutParams从mParams里面删除,并且找到该索引值对应的ViewRootImpl对象;(3):调用ViewRootImpl的setLayoutParams方法进行具体的更新操作,其实更新操作就是重绘Window里面的View罢了,同样也是通过创建ViewRootImpl的Session对象进行的,而Session对象里面的执行过程实际上是通过WindowManagerService的relayoutWindow实现的;想要了解更多添加操作的细节,可以查看这篇博客

(92):Activity、Window、DecorView、ViewRootImpl之间的区别与联系

        (1):在我们通过startActivity之后,经过层层方法调用链,最终会通过Instrumentation利用反射的方式创建一个Activity对象出来;

        (2):在调用Activity的attach的时候会将通过Policy的makeNewWindow创建一个PhoneWindow对象出来;

        (3):而DecorView是我们在调用setContentView的时候,如果之前没有创建过的话,会通过installDecor方法来进行创建;

        (4):而对于ViewRootImpl的话,我们每调用一次addView向Window添加一个View就会创建一个ViewRootImpl对象出来的,但是只有一个ViewRootImpl会被添加到WindowManagerService里面,即只有DecorView所在的ViewRootImpl会被添加到WindowManagerService里面,其他的只是存储在WindowManagerGlobal里面的ViewRootImpl列表里面而已啦;

        经过上面的分析,我们可以认为:每一个Activity有一个Window,每一个Window都有一个DecorView,而每一个View在被添加的时候都会创建一个ViewRootImpl,但是Window所在的ViewRootImpl应该是属于DecorView这个View的,其实ViewRootImpl我们可以认为是View的操作者一样,因为事件分发机制,View视图的绘制都是从他开始的;

(93):使用SharedPreference的步骤

        (1):通过getSharedPreference获得SharedPreference对象;

        (2):通过SharedPreference对象的edit方法启动编辑;

        (3):通过SharedPreference的putString方法进行写数据操作;

        (4):调用SharedPreference的apply方法提交数据;

        (5):最后通过SharedPreference的getString方法获取写到SharedPreference里面的数据;

(94):如图

Android面试题集锦(五)

       原因在于查看ServiceDispatcher源码中的doConnected方法,会发现如下一段代码:

// If there is a new service, it is now connected.
if (service != null) {
mConnection.onServiceConnected(name, service);
}

        这里的service实际上是IBinder类型的,也就是我们Service里面onBind方法的返回值了;

2016.9.8更新........................................................................

(95):SurfaceView与View的区别

        出现SurfaceView的原因在于:虽然说通常情况下View已经可以满足大部分的绘图需求了,但是在有些时候还是有缺陷的,View是通过刷新来重绘视图的,Android系统通过发出VSYNC信号来进行屏幕的重绘,刷新的时间间隔是16ms,如果在16ms内刷新完成的话是没有什么影响的,但是如果刷新的时候执行的操作逻辑太多,那么会出现卡顿的现象,SurfaceView就是解决这个问题的;
        (1):View主要用于主动更新的情况下,而SurfaceView主要用于被动更新,例如频繁的刷新;
        (2):View在主线程中对画面进行更新,而SurfaceView通常会通过一个子线程来进行更新;
        (3):View在绘图的时候是没有使用双缓冲机制的,而SurfaceView在底层实现中使用了双缓冲机制;