Android面试题(三)

1、**Handler的使用场景?它有什么作用? **

在使用线程间通信的时候会使用到Handler,它的作用就是实现线程间的通讯。

一般来说,如果在Activity中有一些耗时的操作,比如说访问网络、读写sd卡等,那么此时我们通常会弹出一个加载框,然后会启动一个子线程,让子线程去做耗时的操作。当子线程任务完成之后,那么此时子线程需要通知主线程,让主线程将加载框消失,这时候就需要使用到Handler了。

在我们使用广告轮播条的时候,我们要让广告轮播条自动滑动起来,这时候,我们也会使用到Handler,让它发送一个延时消息。

所以总结来说,Handler的作用有两点:1、发送消息 2、处理消息

还需要注意的是,Handler在哪个线程中创建,那么这个Handler维护的就是那个线程的消息队列。

在回答Handler的使用的时候,我们也可以结合谷歌电子市场这个项目说说对Handler的使用。我们会在Application的onCreate方法中把主线程的Handler给创建出来,然后通过封装一些方法让别人可以很方便的往主线程的消息队列发消息,就可以避免出现主线程的Handler创建太多的实例对象,也可以方便代码的书写。

2、你能说说**Handler的机制吗? **

说到Handler的机制,有几个非常重要的对象,分别是:Handler、Message、MessageQueue和Looper

任何一个线程,无论是子线程还是主线程,都维护着一个消息队列,这个消息队列就是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()方法

** **

3、如何从主线程发消息给子线程?主要注意什么?** **

大多情况下,我们都是从子线程发送消息到主线程中,让主线程进行一些ui操作,其实主线程也是可以发送消息给子线程的。

如果需要实现主线程发送消息给子线程,那么就需要往子线程的消息队列MessageQueue里面放消息。那么谁能往子线程的MessageQueue放消息呢?答案就是子线程的Handler,所以,我们要创建出子线程的Handler对象。

这时候需要注意的是,当我们在子线程创建了Handler对象,子线程内部的Looper对象是不工作,所以不能形成消息循环。要让Looper工作起来,得调用Looper.prepare()和Looper.loop()方法。具体的代码如下:

public Handler mHandler;

class LooperThread extends Thread {

public void run() {

Looper.prepare();

mHandler = new Handler() {

public void handleMessage(Message msg) {

}

};

Looper.loop();

}

}

那么现在有人就有疑问了,我们绝大多数情况下使用Handler并没有调用Looper.prepare()和Looper.loop()方法。其实主线程的这两个方法系统在应用程序一启动的时候就帮我们调用了。具体代码如下:

在ActivityThread中的main方法:

public static final void main(String[] args) {

...

Looper.prepareMainLooper();

...

Looper.loop();

...

}

4、你有没有使用过**AsyncTask****,怎么使用?**** **

使用过,但是用的不多。AsyncTask是用来处理异步任务的,和我们自己创建子线程,然后通过handler进行通信的效果差不多,只不过AsyncTask内部维护了一个线程池,用这个线程池来控制住线程的数量。

使用AsyncTask的话,一般我们会new出这个类的对象,然后执行execute方法。

new AsyncTask<String, String, String>() {

// 2. 运行在主线程中 , 做一些准备操作 .

public void onPreExecute() {

}

// 3. 运行在子线程中 , 做一些耗时的任务 .

public String doInBackground(String... params) {

return null;

}

// 4. 运行主线程中 , result 就是 doInBackground 方法返回的值 . 做一些收尾操作 .

public void onPostExecute(String result) {

}

}.execute(String... params);    // 1. 开始执行异步任务 .

** **

5**、说说你对Application的理解?你在项目中使用到Application吗?具体拿它做什么?**** **

Application可以理解为是一个程序的入口,运行中的应用程序必然会创建Application的实例。它的特点是:生命周期长,onCreate可以认为是程序第一个执行的方法,并且运行在主线程中。在项目中,会有几种场景使用到Application

a、在onCreate中可以判断软件是否第一次运行

@Override

public void onCreate() {

super.onCreate();

SharedPreferences  sp = getSharedPreferences("setting",                    Context.MODE_WORLD_WRITEABLE);

boolean firstStart = sp.getBoolean("version_1", false);

if(!firstStart) {

// 这里可以做一些类似于初始化内置数据的工作

Editor editor = sp.edit();

editor.putBoolean("version_1", true);

editor.commit();

}

}

b、由于生命周期长,可以存储一些数据,也可以帮忙传递数据

c、捕获全局异常,避免弹出Force Close框

public void onCreate() {

super.onCreate();

Thread.setDefaultUncaughtExceptionHandler(this);

}

@Override

public void uncaughtException(Thread thread, Throwable ex) {

Intent intent = new Intent(getApplicationContext(), MainActivity.class);

PendingIntent restartIntent = PendingIntent.getActivity(

getApplicationContext(), 0, intent,

Intent.FLAG_ACTIVITY_NEW_TASK);

AlarmManager mgr = (AlarmManager) getSystemService(Context.ALARM_SERVICE);

mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 1000,

restartIntent);

}

d、获取context对象,这样的话,别的地方就可以非常方便的创建View了。这里需要注意一点的是,创建dialog是不能使用Application的context的,需要使用activity的context

e、创建主线程的handler,供别人使用

f、获取主线程的线程id

@Override

public void onCreate() {

super.onCreate();

//context 经常使用到。 Toast 、 new View

context = getApplicationContext();

// 得到主线程的 handler 对象,维护的是主线程的 MessageQueue

handler = new Handler();

// 哪个方法调用了 myTid , myTid 返回的就是那个方法所在的线程 id

mainThreadId = android.os.Process.myTid();

}

6、**Android 中布局的优化措施都有哪些? **

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

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

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

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

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

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

9、图片的优化 对于程序来说,图片的优化主要分为大图片的优化和多图片优化。 对于单张大图片的优化来说,一般的思路是这样的:我们的手机是不需要显示太高分辨率的图片,因为手机的分辨率就那么高,图片的分辨率再高也是没有什么效果的。这种情况下,我们会选择对图片进行缩放。一般我们会先得到控件的大小,然后再得到图片的大小。根据他们的值,计算出一个比例进行缩放。具体的代码如下: 仅请求图片的大小,inJustDecodeBounds = true,仅请求图片大小,而不会加载图片到内存; public Bitmap decodeBitmap(){ BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; // 通过这个bitmap获取图片的宽和高 Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/test.jpg", options); float realWidth = options.outWidth; float realHeight = options.outHeight; // 计算缩放比 int scale = (int) ((realHeight > realWidth ? realHeight : realWidth) / 控件的高度或宽度); if (scale <= 0) { scale = 1; } options.inSampleSize = scale; options.inJustDecodeBounds = false; // 注意这次要把options.inJustDecodeBounds 设为 false,这次图片是要读取出来的。 bitmap = BitmapFactory.decodeFile("/sdcard/MTXX/test.jpg", options); return bitmap; } 而图片优化的另外一方面则是关于多图片的优化。为了程序的流畅度,我们往往会把之前创建好图片缓存起来,以便下一次显示这张图片的话可以更快的加载。通常是内存缓存--本地缓存。而我们应用程序的内存大小是有限的,我们不能无限制的将所有的图片都缓存到内存中,这就需要控制住内存中图片的缓存数量。通常我们会使用LRUCache来对内存中的图片数量进行一个控制。其实在xutils中的BitmapUtils它自身就带来LRUCache功能。 使用缓存不是为了解决OOM,恰恰正是因为使用了缓存,才造成了OOM

10、内存泄露和内存溢出的差别 内存泄露和内存溢出是完全不同的两个概念。内存泄露指的是由于开发人员的疏忽,使得一些不可能被使用到的对象无法被垃圾回收器回收,随着时间的堆积,内存不断增加,直到内存溢出。 所以内存泄露会造成内存溢出,而内存溢出,不一定是内存泄露造成的。 内存泄漏本身不会产生什么危害,真正有危害的是内存泄漏的堆积。Android应用内存 泄漏的的原因有以下几个: a、register之后没有unregister,add之后没有remove b、查询数据库后没有关闭游标cursor file没有close c、构造Adapter时,没有使用 convertView 重用 d、Bitmap对象不在使用时调用recycle()释放内存 e、对象被生命周期长的对象引用,如activity被静态集合引用导致activity不能释放

11、怎么解决OOM异常 分析方向分为两个,一个是分析图片,绝大多数的OOM都是由于图片引起的。二是分析是否存在内存泄露的情况。

12、如何进行内存分析** **

heap工具

Heap 视图中部有一个Type叫做data object,即数据对象,也就是我们的程序中大量存在的类类型的对象。在data object 一行中有一列是“Total Size”,其值就是当前进程中所有Java 数据对象的内存总量,一般情况下,这个值的大小决定了是否会有内存泄漏。

如果这个值不断的增加,那么就判断可能有内存泄露的情况。如果这个值稳定在一定范围之内,会变大,也会变小,那么就说明此时的内存状态挺好。 **

mat:mat是一个更为强大的内存分析工具,可以通过内存快照分析出内存的信息。

有的公司自己也会开发出专门分析内存的工具。

13、写出几种你认为可以提高**Android程序运行效率的方法? **

1、释放主线程

2、优化界面显示的布局,包括层次、背景

3、尽量减少对象的创建,比如我们经常会在getView中new 出一个时间监听者

4、避免内存泄露

可以通过traceview工具来查看方法运行的时间。

14、**Android **中如何访问网络 Android 提供了 org.apache.http.HttpClientConnection 和 java.net.HttpURLConnection 两个连接网络对象。除此 之 外 一 般 我 比 较 喜 欢 使 用 xUtils 中的 HttpUtils 功能 , 该 模 块 底 层 使 用 的 就 是org.apache.http.client.HttpClient,使用起来非常方便。还有一些其他的网络访问框架:

OKhttp、Volley等

15、与服务器接口的访问** **

与服务器访问的方式有HTTP和SOCKET两种。

Http:比如说新闻列表啥的,都是事先和服务器的开发人员确定好具体的访问地址,然后客户端使用get或者post请求去访问那个地址就能得到具体的数据了。

Socket:,服务端开启ServerSocket,客户端开启 socket,然后客户端跟服务端建立长连接,这样实现了客户端跟服务端数据的即时通信    保证数据安全。像推送、聊天一般就是使用这种访问方式。

关于数据安全:我们的数据有些是需要安全设置的有些不需要,我们的新闻类数据不需要特殊的添加安全设置,而用户注册,用户登录以及用户隐私数据保存是考虑安全性的。用户的密码等信息肯定不能进行明文传输的,我们将用户的密码在本地进行了MD5 算法的加密,然后再传输。同时保存在本地的时候也是加密后的数据。还有需要安全性更高的数据需要通过我们自定义协议通过Socket 传输。

16、数据传递** **

a、intent:intent可以传递一些基本的数据类型:String、int、float等,也可以传递对象,这个对象必须实现Parcelable接口。实现Parcelable接口代表这个对象可以被序列化到内存中,和Serializable类似,只不过Serializable是将对象写到文件当中。

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

c、可以通过view.setTag来进行数据的传递。

17、如何将一个** java 对象序列化到文件里,Android如何通过intent传递对象? **

在 java 中能够被序列化的类必须先实现 Serializable 接口,该接口没有任何抽象方法只是起到一个标记作用。

// 对象输出流

ObjectOutputStream objectOutputStream = new ObjectOutputStream(new

FileOutputStream(new File("D://obj")));

objectOutputStream.writeObject(new User("zhangsan", 100));

objectOutputStream.close();

// 对象输入流

ObjectInputStream objectInputStream = new ObjectInputStream(new

FileInputStream(new File("D://obj")));

User user = (User)objectInputStream.readObject();

System.out.println(user);

objectInputStream.close();

Activity如果希望通过Intent来传递一个对象的话,这个对象必须实现Parcelable接口。实现Parcelable接口代表这个对象可以被序列化到内存中

19、在项目中,你是如何做版本适配** **

对于版本适配的话,我们在应用程序中通常最低只会适配到2.2,不过由于现在市场上绝大多数的手机系统版本已经是4.0以上的了,现在也慢慢的越来越少的适配4.0以下的系统。

在适配的时候,主要有几个jar我们会经常使用到:v4包,v7包,nineoldandroid包。

v4:Fragment、ViewPager

v7:ActionBar的适配

nineoldandroid:属性动画的适配

有时候我们也会在代码中进行系统版本号的判断。比如更改状态栏颜色。我们会判断系统版本是不是大于等于4.4,如果是的话才会调用相关的api,否则我们就不修改状态栏颜色。

** **

20、屏幕适配** **

布局适配:多用相对布局

图片适配:在不同的文件夹下放下对应的图片。drawable-xhdpi、drawable-xxhdpi

单位适配:在布局文件中尽量写dp,在java代码中尽量将dp转化成px

代码适配:有时候需要根据屏幕的宽高动态计算控件的宽高

21、自定义控件,有没有自己写过自定义控件** **

这个可以结合自己开发经历来说,比如下拉刷新ListView、滑动开关都是比较好说的。

说到自定义控件,还有几个知识点可能会被问到。

a、自定义属性

b、view的绘制流程:onMeasure、onLayout、onDraw

22、如何实现**SlidingMenu的效果 **

可以借助ViewDragHelper来帮助我们完成控件拖动的效果。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值