2018最新 Android 面试题总结(三)
Q:项目中如何做性能优化的?
举例说明项目注意了哪些方面的性能优化,如布局优化、绘制优化、内存泄漏优化、 响应速度优化、列表优化、Bitmap优化、 线程优化......
性能优化一
性能优化二
Q:了解哪些性能优化的工具?
性能优化工具
思路:做项目时是否使用过的系统自带的性能优化工具?公司是否有自己的性能优化工具?实现原理怎样的? LeakCanary工具
Q:布局上如何优化?
布局优化的核心就是尽量减少布局文件的层级,常见的方式有:
多嵌套情况下可使用RelativeLayout减少嵌套。
布局层级相同的情况下使用LinearLayout,它比RelativeLayout更高效。
使用<include>标签重用布局、<merge>标签减少层级、<ViewStub>标签懒加载(适合在某些条件显示的布局,如:网络错误,数据加载失败等)。
Q:内存泄漏是什么?为什么会发生?常见哪些内存泄漏的例子?都是怎么解决的?
内存泄漏(Memory Leak)是指程序在申请内存后,无法释放已申请的内存空间。简单地说,发生内存泄漏是由于长周期对象持有对短周期对象的引用,使得短周期对象不能被及时回收。常见的几个例子和解决办法:
单例模式导致的内存泄漏:单例传入参数this来自Activity,使得持有对Activity的引用。
解决办法:传参context.getApplicationContext()
Handler导致的内存泄漏:Message持有对Handler的引用,而非静态内部类的Handler又隐式持有对外部类Activity的引用,使得引用关系会保持至消息得到处理,从而阻止了Activity的回收。
解决办法:使用静态内部类+WeakReference弱引用;当外部类结束生命周期时清空消息队列。
线程导致的内存泄漏:AsyncTask/Runnable以匿名内部类的方式存在,会隐式持有对所在Activity的引用。
解决办法:将AsyncTask和Runnable设为静态内部类或独立出来;在线程内部采用弱引用保存Context引用
资源未关闭导致的内存泄漏:未及时注销资源导致内存泄漏,如BraodcastReceiver、File、Cursor、Stream、Bitmap等。
解决办法:在Activity销毁的时候要及时关闭或者注销。
BraodcastReceiver:调用unregisterReceiver()注销;
Cursor,Stream、File:调用close()关闭;
动画:在Activity.onDestroy()中调用Animator.cancel()停止动画
引申:谈谈项目中是如何注意内存泄漏的问题
内存泄漏和内存溢出的区别
内存泄漏(Memory Leak)是指程序在申请内存后,无法释放已申请的内存空间。是造成应用程序OOM的主要原因之一。
内存溢出(out of memory)是指程序在申请内存时,没有足够的内存空间供其使用。
Q:什么情况会导致内存溢出?
内存泄漏是导致内存溢出的主要原因;直接加载大图片也易造成内存溢出
引申:谈谈如何避免内存溢出(如何避免内存泄漏、避免直接加载大图片)
开源框架(略)
Q:使用那些版本控制工具?Git和SVN的区别?`
参考回答:Git和SVN的区别有以下几点:
Git是分布式的,而SVN是集中式的(核心区别)
Git按元数据方式存储内容,而SVN按文件存储内容
在Git上每个工作成员可以任意在自己的本地版本库开启无限个分支且互不影响,而对于SVN分支是一个完整的目录且这个目录拥有完整的实际文件
Git没有一个全局的版本号,而SVN有
Git 的内容完整性要优于SVN
在Git中的绝大多数操作都只需要访问本地文件和资源,不必联网就可以看到所有的历史版本记录,而SVN 却需要联网
引申:谈谈两种版本控制工具的优缺点:SVN与GIT的优缺点对比
Q:了解Git工具吗?用过哪些命令?解决冲突时git merge和git rebase的区别?`
通过图记忆Git常用命令,详见Git、GitHub、Stash
image.png
参考回答: 常用命令见图,源自一篇文章,教你学会Git
合并用到的命令git merge与git rebase的区别是,git merge会生成一个新的节点,并将之前的提交分开显示;git rebase操作不会生成新的节点,而是将两个分支
融合成一个线性的提交。
Q:内存泄漏的场景,Handler内存泄漏的原因以及解决方法
原因:使用Handler发送一条延时消息,然后关闭Activity,在Activity被关闭之后,Message将在消息队列中存在10分钟才能被执行到,这个Message持有一个对Handler的引用,Handler又持有一个对当前Activity的引用,这些引用会在Message被执行之前一直保持,这样当前Activity就不会被垃圾回收机制回收,从而导致内存泄漏。
解决方法
1.定义一个静态的Handler,这样Acitivity就不会被泄漏了,同时让Handler持有一个对Activity的弱引用,这样就可以happy的在Handler中调用Activity中的资源或者方法了。
2.如果在关闭Activity之后,不需要对消息队列中的消息进行处理了,可以在onDestory方法中加入下面这段代码:
Q:Handler机制,主线程如何向子线程发送消息,Handler能否多进程通信
private final int MSG_HELLO = 0;
private Handler mHandler;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
new CustomThread().start();//新建并启动CustomThread实例
findViewById(R.id.send_btn).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {//点击界面时发送消息
String str = "hello";
Log.d("Test", "MainThread is ready to send msg:" + str);
mHandler.obtainMessage(MSG_HELLO, str).sendToTarget();//发送消息到CustomThread实例
}
});
}
//採用內部类的方式定义一个类继承自Thread并重写run()方法,在主线程将会新建线程对象并开启这个线程
class CustomThread extends Thread {
@Override
public void run() {
//**下面的代码是从工作线程接收主线程消息的关键代码**
//建立消息循环的步骤
Looper.prepare();//1、初始化Looper:让Looper开始工作,从消息队列里取消息,处理消息。
mHandler = new Handler(){//2、绑定handler到CustomThread实例的Looper对象
public void handleMessage (Message msg) {//3、定义处理消息的方法
switch(msg.what) {
case MSG_HELLO:
Log.d("Test", "CustomThread receive msg:" + (String) msg.obj);
}
}
};
Looper.loop();//4、启动消息循环
//注意:loop里面是一个循环,在调用looper.loop()后,这个循环就是一个死循环,有消息就处理,无消息就挂起!
//也就是说,写在looper.loop()这行代码下的其他代码都不会再执行了,除非你调用mHandler.getLooper().quit()后,
//loop才会中止,其后的代码才能得以运行!需要注意,退出之后这个线程也就无法继续监听主线程发来的消息了!!
//
}
}
Q:单例模式的几种实现具体的区别,工厂模式的种类以及区别
Q:Android的触摸事件分发流程
Q:Android第三方框架的源码实现原理
Q:Activity的启动流程,这个有点意思
Q:数据的存储方式与其的应用场景,ContentProvider的底层实现,如何保证多进程读写安全
Q:hashmap的底层实现
Q:静态广播和动态广播区别,service启动的两种方式区别
Q:场景题分析:手机扫网页端的二维码如何提起的登录的过程,是如何实现的?
Q:代理模式
Q:单例模式以及双重锁原理
Q:观察者模式应用场景
Q:单例模式的几种实现具体的区别,工厂模式的种类以及区别
多线程并发,sychronized,类锁和对象锁