Android高级开发工程师面试问答

结合自己之前去很多大公司的面试经历和自己面别人的一些题,这里做一些总结,Android面试中常见的面试题。

1.要做一个尽可能流畅的ListView,你平时在工作中如何进行优化的?

Item布局,层级越少越好,使用hierarchyview工具查看优化。

复用convertView

使用ViewHolder

item中有图片时,异步加载

快速滑动时,不加载图片

item中有图片时,应对图片进行适当压缩

实现数据的分页加载

2.Android的Handler运行机制?

要解释Handler的运行机制就要讲几个对象:Message、Handler、Message Queue、Looper。Handler获取当前线程中的looper对象,looper用来从存放Message的 MessageQueue中取出Message,再有Handler进行Message的分发和处理。

Message Queue(消息队列):用来存放通过Handler发布的消息,通常附属于某一个创建它的线程,可以通过Looper.myQueue()得到当前线程的消息队列
Handler:可以发布或者处理一个消息或者操作一个Runnable,通过Handler发布消息,消息将只会发送到与它关联的消息队列,然也只能处理该消息队列中的消息
Looper:是Handler和消息队列之间通讯桥梁,程序组件首先通过Handler把消息传递给Looper,Looper把消息放入队列。Looper也把消息队列里的消息广播给所有的
Handler:Handler接受到消息后调用handleMessage进行处理
Message:消息的类型,在Handler类中的handleMessage方法中得到单个的消息进行处理
在单线程模型下,为了线程通信问题,Android设计了一个Message Queue(消息队列), 线程间可以通过该Message Queue并结合Handler和Looper组件进行信息交换。下面将对它们进行分别介绍:

1. Message
Message消息,理解为线程间交流的信息,处理数据后台线程需要更新UI,则发送Message内含一些数据给UI线程。
2. Handler
Handler处理者,是Message的主要处理者,负责Message的发送,Message内容的执行处理。后台线程就是通过传进来的 Handler对象引用来sendMessage(Message)。而使用Handler,需要implement 该类的 handleMessage(Message)方法,它是处理这些Message的操作内容,例如Update UI。通常需要子类化Handler来实现handleMessage方法。
3. Message Queue
Message Queue消息队列,用来存放通过Handler发布的消息,按照先进先出执行。
每个message queue都会有一个对应的Handler。Handler会向message queue通过两种方法发送消息:sendMessage或post。这两种消息都会插在message queue队尾并按先进先出执行。但通过这两种方法发送的消息执行的方式略有不同:通过sendMessage发送的是一个message对象,会被 Handler的handleMessage()函数处理;而通过post方法发送的是一个runnable对象,则会自己执行。
4. Looper
Looper是每条线程里的Message Queue的管家。Android没有Global的Message Queue,而Android会自动替主线程(UI线程)建立Message Queue,但在子线程里并没有建立Message Queue。所以调用Looper.getMainLooper()得到的主线程的Looper不为NULL,但调用Looper.myLooper() 得到当前线程的Looper就有可能为NULL。对于子线程使用Looper,API Doc提供了正确的使用方法:这个Message机制的大概流程:
1. 在Looper.loop()方法运行开始后,循环地按照接收顺序取出Message Queue里面的非NULL的Message。
2. 一开始Message Queue里面的Message都是NULL的。当Handler.sendMessage(Message)到Message Queue,该函数里面设置了那个Message对象的target属性是当前的Handler对象。随后Looper取出了那个Message,则调用 该Message的target指向的Hander的dispatchMessage函数对Message进行处理。在dispatchMessage方法里,如何处理Message则由用户指定,三个判断,优先级从高到低:

  1. Message里面的Callback,一个实现了Runnable接口的对象,其中run函数做处理工作;
  2. Handler里面的mCallback指向的一个实现了Callback接口的对象,由其handleMessage进行处理;
  3. 处理消息Handler对象对应的类继承并实现了其中handleMessage函数,通过这个实现的handleMessage函数处理消息。
    由此可见,我们实现的handleMessage方法是优先级最低的!
    3. Handler处理完该Message (update UI) 后,Looper则设置该Message为NULL,以便回收!
    在网上有很多文章讲述主线程和其他子线程如何交互,传送信息,最终谁来执行处理信息之类的,个人理解是最简单的方法——判断Handler对象里面的Looper对象是属于哪条线程的,则由该线程来执行!
    1. 当Handler对象的构造函数的参数为空,则为当前所在线程的Looper;

3.Android的Activity的四种启动模式和用途?

standerd

默认模式,可以不用写配置。在这个模式下,都会默认创建一个新的实例。因此,在这种模式下,可以有多个相同的实例,也允许多个相同Activity叠加。应用场景:绝大多数Activity。

singleTop

栈顶复用模式,如果要开启的activity在任务栈的顶部已经存在,就不会创建新的实例,而是调用 onNewIntent() 方法。避免栈顶的activity被重复的创建。

singleInstance

“singleInstance”独占一个task,其它activity不能存在那个task里;如果它启动了一个新的activity,不管新的activity的launch mode 如何,新的activity都将会到别的task里运行(如同加了FLAG_ACTIVITY_NEW_TASK参数)。

singleTask

栈内复用模式, activity只会在任务栈里面存在一个实例。如果要激活的activity,在任务栈里面已经存在,就不会创建新的activity,而是复用这个已经存在的activity,调用 onNewIntent() 方法,并且清空这个activity任务栈上面所有的activity。

4.解释一下activity、 intent 、intent filter、service、Broadcase、BroadcaseReceiver?

一个activity呈现了一个用户可以操作的可视化用户界面;一个service不包含可见的用户界面,而是在后台运行,可以与一个activity绑定,通过绑定暴露出来接口并与其进行通信;一个broadcast receiver是一个接收广播消息并做出回应的component,broadcast receiver没有界面;一个intent是一个Intent对象,它保存了消息的内容。对于activity和service来说,它指定了请求的操作名称和待操作数据的URI,Intent对象可以显式的指定一个目标component。如果这样的话,android会找到这个component(基于manifest文件中的声明)并激活它。但如果一个目标不是显式指定的,android必须找到响应intent的最佳component。它是通过将Intent对象和目标的intent filter相比较来完成这一工作的;一个component的intent filter告诉android该component能处理的intent。intent filter也是在manifest文件中声明的。

5.Serializable和Parcelable的区别?

在使用内存的时候,Parcelable 类比Serializable性能高,所以推荐使用Parcelable类。
1.Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC。
传智播客武汉校区就业部出品 务实、创新、质量、分享、专注、责任
32
2.Parcelable不能使用在要将数据存储在磁盘上的情况。尽管Serializable效率低点,但在这
种情况下,还是建议你用Serializable 。
实现:
1.Serializable 的实现,只需要继承 Serializable 即可。这只是给对象打了一个标记,系统会
自动将其序列化。
2.Parcelabel 的实现,需要在类中添加一个静态成员变量 CREATOR,这个变量需要继承
Parcelable.Creator 接口。

<span style="font-size:14px;">public class MyParcelable implements Parcelable {  
    private int mData;  

    public int describeContents() {  
        return 0;  
    }  

    public void writeToParcel(Parcel out, int flags) {  
        out.writeInt(mData);  
    }  

    public static final Parcelable.Creator<MyParcelable> CREATOR = new Parcelable.Creator<MyParcelable>() {  
        public MyParcelable createFromParcel(Parcel in) {  
            return new MyParcelable(in);  
        }  

        public MyParcelable[] newArray(int size) {  
            return new MyParcelable[size];  
        }  
    };  

    private MyParcelable(Parcel in) {  
 mData = in.readInt();  
    }  
}</span> 

public class MyParcelable implements Parcelable {

    private int mData;

    public int describeContents() {

        return 0;

    }

    public void writeToParcel(Parcel out, int flags) {

        out.writeInt(mData);

    }

    public static final Parcelable.Creator<MyParcelable> CREATOR = new Parcelable.Creator<MyParcelable>() {

        public MyParcelable createFromParcel(Parcel in) {

            return new MyParcelable(in);

        }

        public MyParcelable[] newArray(int size) {

            return new MyParcelable[size];

        }

    };

    private MyParcelable(Parcel in) {

        mData = in.readInt();

    }

}

6.什么是内存泄漏,android在什么情况下容易产生内存泄漏?

说到内存泄漏就不得不提内存溢出。

内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。

内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重。内存溢出导致了内存泄漏。

在Android中常见的内存泄漏原因:

1. 资源释放问题
程序代码的问题,长期保持某些资源,如Context、Cursor、IO流的引用,资源得不到释放
造成内存泄露。
2. 对象内存过大问题
保存了多个耗用内存过大的对象(如Bitmap、XML文件),造成内存超出限制。
3. static关键字的使用问题
static是Java中的一个关键字,当用它来修饰成员变量时,那么该变量就属于该类,而不是
该类的实例。所以用static修饰的变量,它的生命周期是很长的,如果用它来引用一些资源耗费
过多的实例(Context的情况最多),这时就要谨慎对待了。
public class ClassName {
private static Context mContext;
//省略
}
以上的代码是很危险的,如果将 Activity 赋值到 mContext 的话。那么即使该 Activity 已经
onDestroy,但是由于仍有对象保存它的引用,因此该Activity依然不会被释放。

4. 线程导致内存溢出
线程产生内存泄露的主要原因在于线程生命周期的不可控。

5,数据库游标忘记回收等

那么针对上面的问题我们怎么避免呢

1、图片过大导致OOM
Android 中用bitmap时很容易内存溢出,比如报如下错误:Java.lang.OutOfMemoryError :
bitmap size exceeds VM budget。
解决方法:
方法1: 等比例缩小图片

BitmapFactory.Options options = new BitmapFactory.Options();   
options.inSampleSize = 2; //Options 只保存图片尺寸大小,不保存图片到内存   
BitmapFactory.Options opts = new BitmapFactory.Options();   
opts.inSampleSize = 2;   
Bitmap bmp = null;   
bmp = BitmapFactory.decodeResource(getResources(), mImageIds[position],opts);   
//回收  
bmp.recycle();//  

方法2:对图片采用软引用,及时地进行recyle()操作

SoftReference<Bitmap> bitmap = new SoftReference<Bitmap>(pBitmap);   
if(bitmap != null){   
    if(bitmap.get() != null && !bitmap.get().isRecycled()){   
        bitmap.get().recycle();   
 bitmap = null; }   
    }  

2、查询数据库没有关闭游标
程序中经常会进行查询数据库的操作,但是经常会有使用完毕Cursor后没有关闭的情况。如果
我们的查询结果集比较小,对内存的消耗不容易被发现,只有在常时间大量操作的情况下才会出现内
存问题,这样就会给以后的测试和问题排查带来困难和风险。

3、构造Adapter时,没有使用缓存的 convertView

在使用ListView的时候通常会使用Adapter,那么我们应该尽可能的使用ConvertView。

为什么要使用convertView?

当convertView为空时,用setTag()方法为每个View绑定一个存放控件的ViewHolder对象。

当 convertView 不为空,重复利用已经创建的 view 的时候,使用 getTag()方法获取绑定的

ViewHolder对象,这样就避免了findViewById对控件的层层查询,而是快速定位到控件。

4、Bitmap对象不再使用时调用recycle()释放内存

有时我们会手工的操作Bitmap对象,如果一个Bitmap对象比较占内存,当它不再被使用的时

候,可以调用Bitmap.recycle()方法回收此对象的像素所占用的内存,但这不是必须的,视情况而定。

7. 简述下Android JNI调用过程?

  1. 安装和下载Cygwin,下载AndroidNDK
  2. 在ndk项目中JNI接口的设计
  3. 使用C/C++实现本地方法
  4. JNI生成动态链接库.so文件
  5. 将动态链接库复制到java工程,在java工程中调用,运行java工程即可

8.对于Android 的安全问题,你知道多少?

  1. 错误导出组件
  2. 参数校验不严
  3. WebView引入各种安全问题,webview中的js注入
  4. 不混淆、不防二次打包
  5. 明文存储关键信息
  6. 错误使用HTTPS
  7. 山寨加密方法
  8. 滥用权限、内存泄露、使用debug签名

9.如何缩减APK包大小?

代码

保持良好的编程习惯,不要重复或者不用的代码,谨慎添加libs,移除使用不到的libs。

使用proguard混淆代码,它会对不用的代码做优化,并且混淆后也能够减少安装包的大小。

native code的部分,大多数情况下只需要支持armabi与x86的架构即可。如果非必须,可以考虑拿掉x86的部分。

资源

使用Lint工具查找没有使用到的资源。去除不使用的图片,String,XML等等。 assets目录下的资源请确保没有用不上的文件。

生成APK的时候,aapt工具本身会对png做优化,但是在此之前还可以使用其他工具如tinypng对图片进行进一步的压缩预处理。

jpeg还是png,根据需要做选择,在某些时候jpeg可以减少图片的体积。 对于9.png的图片,可拉伸区域尽量切小,另外可以通过使用9.png拉伸达到大图效果的时候尽量不要使用整张大图。

策略

有选择性的提供hdpi,xhdpi,xxhdpi的图片资源。建议优先提供xhdpi的图片,对于mdpi,ldpi与xxxhdpi根据需要提供有差异的部分即可。

尽可能的重用已有的图片资源。例如对称的图片,只需要提供一张,另外一张图片可以通过代码旋转的方式实现。

能用代码绘制实现的功能,尽量不要使用大量的图片。例如减少使用多张图片组成animate-list的AnimationDrawable,这种方式提供了多张图片很占空间。

10.Android与服务器交互的方式中的对称加密和非对称加密是什么?

对称加密,就是加密和解密数据都是使用同一个key,这方面的算法有DES。

非对称加密,加密和解密是使用不同的key。发送数据之前要先和服务端约定生成公钥和私钥,使用公钥加密的数据可以用私钥解密,反之。这方面的算法有RSA。ssh 和 ssl都是典型的非对称加密。

11.谈谈你对Android中Context的理解?

Context:包含上下文信息(外部值) 的一个参数. Android 中的 Context 分三种,Application Context ,Activity Context ,Service Context.

它描述的是一个应用程序环境的信息,通过它我们可以获取应用程序的资源和类,也包括一些应用级别操作,例如:启动一个Activity,发送广播,接受Intent信息等

12.如何把一个应用设置为系统应用?

成为系统应用,首先要在 对应设备的 Android 源码 SDK 下编译,编译好之后:

此 Android 设备是 Debug 版本,并且已经 root,直接将此 apk 用 adb 工具 push 到 system/app 或 system/priv-app 下即可。

如果非 root 设备,需要编译后重新烧写设备镜像即可。

有些权限(如 WRITE_SECURE_SETTINGS ),是不开放给第三方应用的,只能在对应设备源码中编译然后作为系统 app 使用。

以上就列举这么多了,其他的大家可以自行搜索。

    原文作者:疯狂的程序猿丶
    原文地址: https://www.jianshu.com/p/609afda9d0f1
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞