最近遇到一些比较有代表性,有点挑战性的面试题,
大概集中这4个方面:
- 1.性能的优化
- 2.功能的实现原理
- 3.基础知识的掌握程度
- 4.新技术的了解
关于这些问题,觉得下面几篇无论是文章的逻辑,文章的深度都是写得比较好的,希望对一些应聘者有所帮助.
1. JNI 的调用怎么做优化?
思路:
在 Java 中声明一个 native 方法,然后生成本地接口的函数原型声明,再用 C/C++ 实现这些函数,并生成对应平台的动态共享库放到 Java 程序的类路径下,最后在 Java 程序中调用声明的 native 方法就间接的调用到了 C/C++ 编写的函数了,在 C/C++ 中写的程序可以避开 JVM 的内存开销过大的限制、处理高性能的计算、调用系统服务等功能.遇到的问题:
其实在JNI中,与java最常接触的无非就是查找 class 和 ID (属性和方法 ID),但是这个查找的过程是十分消耗时间的.解决方法:
因此在 native 里保存 class 和 member id 是很有必要的,但是class 和 member id 在一定范围内是稳定的,但在动态加载的 class loader 下,保存全局的 class 要么可能失效,要么可能造成无法卸载classloader,在诸如 OSGI(j2e的东西,自己百度) 框架下的 JNI 应用还要特别注意这方面的问题.
总结:
所以在 JNI 开发中,合理的使用缓存技术能给程序提高极大的性能。缓存有两种,分别为使用时缓存和类静态初始化时缓存,区别主要在于缓存发生的时刻。
使用时缓存:
字段 ID、方法 ID 和 Class 引用在函数当中使用的同时就缓存起来.
判断字段 ID 是否已经缓存,如果没有先取出来存到fid_str中,下次再调用的时候该变量已经有值了,不用再去JVM中获取,起到了缓存的作用。遇到的坑:
但是请注意:cls_string是一个局部引用,与方法和字段 ID 不一样,局部引用在函数结束后会被 JVM 自动释放掉,这时cls_string成为了一个野针对(指向的内存空间已被释放,但变量的值仍然是被释放后的内存地址,不为 NULL),当下次再调用 Java_com_xxxx_newString 这个函数的时候,会试图访问一个无效的局部引用,从而导致非法的内存访问造成程序崩溃。所以在函数内用 static 缓存局部引用这种方式是错误的。类静态初始化时缓存:
在调用一个类的方法或属性之前,Java 虚拟机会先检查该类是否已经加载到内存当中,如果没有则会先加载,然后紧接着会调用该类的静态初始化代码块,所以在静态初始化该类的过程当中计算并缓存该类当中的字段 ID 和方法 ID 也是个不错的选择。
两种缓存方式比较
如果在写 JNI 接口时,不能控制方法和字段所在类的源码的话,用使用时缓存比较合理。但比起类静态初始化时缓存来说,用使用时缓存有一些缺点:
使用前,每次都需要检查是否已经缓存该 ID 或 Class 引用
如果在用使用时缓存的 ID,要注意只要本地代码依赖于这个 ID 的值,那么这个类就不会被 unload。另外一方面,如果缓存发生在静态初始化时,当类被 unload 或 reload 时,ID 会被重新计算。因为,尽量在类静态初始化时就缓存字段 ID、方法 ID 和类的 Class 引用。
2. 自定义View的一个绘制流程(底层)
自定义控件过程
自定义控件的流程大致分为以下几步
- 定义属性
- 定义xml文件
- 自定义View获取属性
- onMeasure()
- onLayout()
自定义view的目的是为了提升我们的视觉体验,所以一般我们会辅助一些动画来提升体验,为了增强交互,我们也要为其增加一些交互,所以我么需要对其中进行一些事件的监听,自定义监听器,然后在我们需要的地方进行回调,考虑完这些,我们需要再考虑的是如何提高这些view的复用性。
View的绘制流程
- OnMeasure
- OnLayout
OnDraw
对于自定义View,我们通常会重写这三个方法,重写那些,取决于我们的自定义View从哪里继承,然后要实现什么样的功能。大致归纳有以下几点。继承View
实现一些不规则的图形,需要重写onDraw方法进行绘制- 继承ViewGroup
需要实现对于子控件的测量和布局 - 继承特定View
较容易实现 - 继承特定ViewGroup
无需处理测量和布局
点击详情1
点击详情2
3. 非静态内部类为什么会持有外部类的引用
通过反编译的结果, 普通的非static内部类比static内部类多了一个field: final com.qihoo.browser.OuterClass this$0
;在默认的构造方法中,用外部类的对象对这个filed赋值.
用intellijidea打开OuterClass$NormallInnerClass.class
, 可以看到内部类调用外部类的方法就是通过这个filed实现的. 这也就是static 内部类无法调用外部类普通方法的原因,因为static内部类中没有这个field: final com.qihoo.browser.OuterClass this$0
;
解决方法
publicclass MainActivity extends Activity {
privateMyThread mThread;
@Override
protectedvoid onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
exampleThree();
}
privatevoid exampleThree() {
mThread = new MyThread();
mThread.start();
}
//static内部类可以避免,持有外部类的引用.
private static class MyThread extends Thread {
private boolean mRunning = false;
@Override
public void run() {
//对一些死循环的耗时操作,需要设置退出线程的标识 flag
mRunning = true;
while(mRunning) {
SystemClock.sleep(1000);
}
}
//定义一个flag 来停止线程的运行.
public void close() {
mRunning = false;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
//activity被销毁的时候关闭线程
mThread.close();
}
}
4. 静态方法为什么不能被重写
非静态方法 按重写规则调用相应的类实现方法,而静态方法只与类相关。
所谓静态,就是在运行时,虚拟机已经认定此方法属于哪个类。
专业术语有严格的含义,用语要准确.”重写”只能适用于实例方法.不能用于静态方法.对于静态方法,只能隐藏(可以重写那只是形式上的 ,并不满足多态的特征,所以严格说不是重写)。静态方法的调用不需要实例化吧.. 不实例化也就不能用多态了,也就没有所谓的父类引用指向子类实例.因为不能实例化 也就没有机会去指向子类的实例。所以也就不存在多态了。
静态方法不依赖类就可以访问。这就是它的用途啊,没有new对象可调用的方法。然而,重写是依赖对象的。重写父类的某一方法,而static不依赖类。
5. listView放100个布局,如何做优化(全部加载) 想显示就显示,不显示就不显示
善用自定义 View,自定义 View 可以有效的减小 Layout 的层级。
5.1 listView的终极优化(推荐)
包括利用好 ConvertView、利用好 ViewType、Layout 层次结构、ViewHolder、使用自定义布局、保证 Adapter 的 hasStableIds() 返回 true、Item 不能太高、getView() 中要做尽量少的事情、ListView 中元素避免半透明、尽量开启硬件加速、 AnimationCache、 ScrollingCache 和 SmoothScrollbar。
个人收获:
比viewHolder更有效的方式是使用自定义布局,
自定义布局有个好处就是可以省略 ViewHolder。说出来可能你不会信, ViewHolder 首先会占用 setTag() ,其次每次取出后都需要转换一下类的类型。如果是自定义布局的话,findViewById() 这个过程可以在构造函数中进行.
6. view和surfaceView的区别
Android游戏开发中主要的类除了控制类就是显示类,比较重要也很复杂的就是显示和游戏逻辑的处理。在J2ME中可以通过Display和Canvas来实现显示,而Android中处理显示的是View类。下面为大家简单介绍android.view.View
和android.view.SurfaceView
。
SurfaceView是从View基类中派生出来的显示类,直接子类有GLSurfaceView和VideoView,可以看出GL和视频播放以及Camera摄像头一般均使用SurfaceView,到底有哪些优势呢? SurfaceView可以控制表面的格式,比如大小,显示在屏幕中的位置,最关键是的提供了SurfaceHolder类,使用getHolder方法获取,相关的有Canvas lockCanvas()、 Canvas lockCanvas(Rect dirty) 、void removeCallback(SurfaceHolder.Callback callback)、void unlockCanvasAndPost(Canvas canvas)
控制图形以及绘制,而在SurfaceHolder.Callback
接口回调中可以通过下面三个抽象类可以自己定义具体的实现(比如第一个更改格式和显示画面):
abstract void surfaceChanged(SurfaceHolder holder, int format, int width, int height) ;
abstract void surfaceCreated(SurfaceHolder holder) ;
abstract void surfaceDestroyed(SurfaceHolder holder) ;
对于Surface相关的,Android底层还提供了GPU加速功能,所以一般实时性很强的应用中主要使用SurfaceView而不是直接从View构建,同时后面会讲到的OpenGL中的GLSurfaceView也是从该类实现。
7. 蓝牙
8. 说说你比较熟悉的一个开源框架的 (源码流程)
OkHttp + volley
OKHttp的优点
- OKHttp是Android版Http客户端。非常高效,支持SPDY、连接池、GZIP和HTTP缓存。
- 一种开放的网络传输协议,由Google开发),它为你做了很多的事情。
- OkHttp实现的诸多技术如:连接池,gziping,缓存等。
- OkHttp使用Okio来大大简化数据的访问与存储,Okio是一个增强 java.io 和 java.nio的库。
- OkHttp 处理了很多网络疑难杂症:会从很多常用的连接问题中自动恢复。如果您的服务器配置了多个IP地址,当第一个IP连接失败的时候,OkHttp会自动尝试下一个IP。
OkHttp还处理了代理服务器问题和SSL握手失败问题。
*目前,该封装库志支持:一般的get请求
一般的post请求
基于Http的文件上传
文件下载
上传下载的进度回调
加载图片
支持请求回调,直接返回对象、对象集合
支持session的保持
支持自签名网站https的访问,提供方法设置下证书就行
支持取消某个请求
Volley
- Volley是一个简化网络任务的库。他负责处理请求,加载,缓存,线程,同步等问题。它可以处理JSON,图片,缓存,文本源,支持一定程度的自定义。
- Volley在Android 2.3及以上版本,使用的是HttpURLConnection,而在Android 2.2及以下版本,使用的是HttpClient。
- Volley存在一个缓存线程,一个网络请求线程池(默认4个线程)。
- 它的设计目标就是非常适合去进行数据量不大,但通信频繁的网络操作,而对于大数据量的网络操作,比如说下载文件等,Volley的表现就会非常糟糕
- Volley的优点很多,可拓展、结构合理、逻辑清晰、能识别缓存、通过统一的方式,获取网络数据,包括且不限于文本、图片等资源。
- Volley在一开始创建请求队列的过程中,需要创建网络线程和缓存线程,同时还需要初始化基于Disk的缓存,这中间有大量的资源开销和IO操作,所有才会慢。
9. 现在市面上常用的适配
10. 子线程创建的handler ,可以调用应用本身维护的handler的方法吗
一个Handler的创建它就会被绑定到这个线程的消息队列中,如果是在主线程创建的,那就不需要写代码来创建消息队列了,默认的消息队列会在主线程被创建。但是如果是在子线程的话,就必须在创建Handler之前先初始化线程的消息队列。
11. fragment 中替换和添加的区别.哪个有优势.
transaction.replace()
使用另一个Fragment替换当前的,实际上就是remove()然后add()的合体
- transaction.replace() fragment生命周期会重走.
- transaction.add() 当使用add(),show(),hide()跳转新的Fragment时,旧的Fragment回调onHiddenChanged(),不会回调onStop()等生命周期方法,而新的Fragment在创建时是不会回调onHiddenChanged(),这点要切记.