程序员人生 网站导航

最近收集的一些面试题

栏目:综合技术时间:2016-11-21 08:59:23

最近遇到1些比较有代表性,有点挑战性的面试题,
大概集中这几个方面:

  • 1.性能的优化
  • 2.功能的实现原理
  • 3.基础知识的掌握程度
  • 4.新技术的了解

关于这些问题,觉得下面几篇不管是文章的逻辑,文章的深度都是写得比较好的,希望对1些应聘者有所帮助.

1. JNI 的调用怎样做优化?

  • 思路:
    在 Java 中声明1个 native 方法,然后生本钱地接口的函数原型声明,再用 C/C++ 实现这些函数,并生成对应平台的动态同享库放到 Java 程序的类路径下,最后在 Java 程序中调用声明的 native 方法就间接的调用到了 C/C++ 编写的函数了,在 C/C++ 中写的程序可以避开 JVM 的内存开消过大的限制、处理高性能的计算、调用系统服务等功能.

  • 遇到的问题:
    其实在JNI中,与java最常接触的不过就是查找 class 和 ID (属性和方法 ID),但是这个查找的进程是10分消耗时间的.

  • 解决方法:
    因此在 native 里保存 class 和 member id 是很有必要的,但是class 和 member id 在1定范围内是稳定的,但在动态加载的 class loader 下,保存全局的 class 要末可能失效,要末可能造成没法卸载classloader,在诸如 OSGI(j2e的东西,自己百度) 框架下的 JNI 利用还要特别注意这方面的问题.

总结:
所以在 JNI 开发中,公道的使用缓存技术能给程序提高极大的性能。缓存有两种,分别为使用时缓存和类静态初始化时缓存,区分主要在于缓存产生的时刻。

  • 使用时缓存:
    字段 ID、方法 ID 和 Class 援用在函数当中使用的同时就缓存起来.
    判断字段 ID 是不是已缓存,如果没有先取出来存到fid_str中,下次再调用的时候该变量已有值了,不用再去JVM中获得,起到了缓存的作用。

    遇到的坑:
    但是请注意:cls_string是1个局部援用,与方法和字段 ID 不1样,局部援用在函数结束后会被 JVM 自动释放掉,这时候cls_string成了1个野针对(指向的内存空间已被释放,但变量的值依然是被释放后的内存地址,不为 NULL),当下次再调用 Java_com_xxxx_newString 这个函数的时候,会试图访问1个无效的局部援用,从而致使非法的内存访问造成程序崩溃。所以在函数内用 static 缓存局部援用这类方式是毛病的。

  • 类静态初始化时缓存:
    在调用1个类的方法或属性之前,Java 虚拟机会先检查该类是不是已加载到内存当中,如果没有则会先加载,然后紧接着会调用该类的静态初始化代码块,所以在静态初始化该类的进程当中计算并缓存该类当中的字段 ID 和方法 ID 也是个不错的选择。

两种缓存方式比较

如果在写 JNI 接口时,不能控制方法和字段所在类的源码的话,用使用时缓存比较公道。但比起类静态初始化时缓存来讲,用使用时缓存有1些缺点:

使用前,每次都需要检查是不是已缓存该 ID 或 Class 援用
如果在用使用时缓存的 ID,要注意只要本地代码依赖于这个 ID 的值,那末这个类就不会被 unload。另外1方面,如果缓存产生在静态初始化时,当类被 unload 或 reload 时,ID 会被重新计算。由于,尽可能在类静态初始化时就缓存字段 ID、方法 ID 和类的 Class 援用。

点击详情

2. 自定义View的1个绘制流程(底层)

自定义控件进程

自定义控件的流程大致分为以下几步

  • 定义属性
  • 定义xml文件
  • 自定义View获得属性
  • onMeasure()
  • onLayout()
    自定义view的目的是为了提升我们的视觉体验,所以1般我们会辅助1些动画来提升体验,为了增强交互,我们也要为其增加1些交互,所以我么需要对其中进行1些事件的监听,自定义监听器,然后在我们需要的地方进行回调,斟酌完这些,我们需要再斟酌的是如何提高这些view的复用性。

View的绘制流程

  • OnMeasure
  • OnLayout
  • OnDraw
    对自定义View,我们通常会重写这3个方法,重写那些,取决于我们的自定义View从哪里继承,然后要实现甚么样的功能。大致归纳有以下几点。

  • 继承View
    实现1些不规则的图形,需要重写onDraw方法进行绘制

  • 继承ViewGroup
    需要实现对子控件的丈量和布局
  • 继承特定View
    较容易实现
  • 继承特定ViewGroup
    无需处理丈量和布局
    点击详情1
    点击详情2

3. 非静态内部类为何会持有外部类的援用

通过反编译的结果, 普通的非static内部类比static内部类多了1个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() { //对1些死循环的耗时操作,需要设置退出线程的标识 flag mRunning = true; while(mRunning) { SystemClock.sleep(1000); } } //定义1个flag 来停止线程的运行. public void close() { mRunning = false; } } @Override protected void onDestroy() { super.onDestroy(); //activity被烧毁的时候关闭线程 mThread.close(); } }

点击详情1
点击详情2

4. 静态方法为何不能被重写

  1. 非静态方法 按重写规则调用相应的类实现方法,而静态方法只与类相干。

  2. 所谓静态,就是在运行时,虚拟机已认定此方法属于哪一个类。
    专业术语有严格的含义,用语要准确.”重写”只能适用于实例方法.不能用于静态方法.对静态方法,只能隐藏(可以重写那只是情势上的 ,其实不满足多态的特点,所以严格说不是重写)。

  3. 静态方法的调用不需要实例化吧.. 不实例化也就不能用多态了,也就没有所谓的父类援用指向子类实例.由于不能实例化 也就没有机会去指向子类的实例。所以也就不存在多态了。

静态方法不依赖类就能够访问。这就是它的用处啊,没有new对象可调用的方法。但是,重写是依赖对象的。重写父类的某1方法,而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() ,其次每次取出后都需要转换1下类的类型。如果是自定义布局的话,findViewById() 这个进程可以在构造函数中进行.

点击详情

6. view和surfaceView的区分

Android游戏开发中主要的类除控制类就是显示类,比较重要也很复杂的就是显示和游戏逻辑的处理。在J2ME中可以通过Display和Canvas来实现显示,而Android中处理显示的是View类。下面为大家简单介绍android.view.Viewandroid.view.SurfaceView
SurfaceView是从View基类中派生出来的显示类,直接子类有GLSurfaceView和VideoView,可以看出GL和视频播放和Camera摄像头1般均使用SurfaceView,到底有哪些优势呢? SurfaceView可以控制表面的格式,比如大小,显示在屏幕中的位置,最关键是的提供了SurfaceHolder类,使用getHolder方法获得,相干的有Canvas lockCanvas()、 Canvas lockCanvas(Rect dirty) 、void removeCallback(SurfaceHolder.Callback callback)、void unlockCanvasAndPost(Canvas canvas)控制图形和绘制,而在SurfaceHolder.Callback 接口回调中可以通过下面3个抽象类可以自己定义具体的实现(比如第1个更改格式和显示画面):
java
abstract void surfaceChanged(SurfaceHolder holder, int format, int width, int height) ;
abstract void surfaceCreated(SurfaceHolder holder) ;
abstract void surfaceDestroyed(SurfaceHolder holder) ;

对Surface相干的,Android底层还提供了GPU加速功能,所以1般实时性很强的利用中主要使用SurfaceView而不是直接从View构建,同时后面会讲到的OpenGL中的GLSurfaceView也是从该类实现。

7. 蓝牙

点击详情

8. 说说你比较熟习的1个开源框架的 (源码流程)

OkHttp + volley

OKHttp的优点

  • OKHttp是Android版Http客户端。非常高效,支持SPDY、连接池、GZIP和HTTP缓存。
    • 1种开放的网络传输协议,由Google开发),它为你做了很多的事情。
  • OkHttp实现的诸多技术如:连接池,gziping,缓存等。
  • OkHttp使用Okio来大大简化数据的访问与存储,Okio是1个增强 java.io 和 java.nio的库。
  • OkHttp 处理了很多网络疑问杂症:会从很多经常使用的连接问题中自动恢复。如果您的服务器配置了多个IP地址,当第1个IP连接失败的时候,OkHttp会自动尝试下1个IP。
  • OkHttp还处理了代理服务器问题和SSL握手失败问题。
    *目前,该封装库志支持:

    • 1般的get要求

    • 1般的post要求

    • 基于Http的文件上传

    • 文件下载

    • 上传下载的进度回调

    • 加载图片

    • 支持要求回调,直接返回对象、对象集合

    • 支持session的保持

    • 支持自签名网站https的访问,提供方法设置下证书就行

    • 支持取消某个要求

Volley

  • Volley是1个简化网络任务的库。他负责处理要求,加载,缓存,线程,同步等问题。它可以处理JSON,图片,缓存,文根源,支持1定程度的自定义。
  • Volley在Android 2.3及以上版本,使用的是HttpURLConnection,而在Android 2.2及以下版本,使用的是HttpClient。
  • Volley存在1个缓存线程,1个网络要求线程池(默许4个线程)。
  • 它的设计目标就是非常合适去进行数据量不大,但通讯频繁的网络操作,而对大数据量的网络操作,比如说下载文件等,Volley的表现就会非常糟
  • Volley的优点很多,可拓展、结构公道、逻辑清晰、能辨认缓存、通过统1的方式,获得网络数据,包括且不限于文本、图片等资源。
  • Volley在1开始创建要求队列的进程中,需要创建网络线程和缓存线程,同时还需要初始化基于Disk的缓存,这中间有大量的资源开消和IO操作,所有才会慢。

9. 现在市面上经常使用的适配

点击详情

10. 子线程创建的handler ,可以调用利用本身保护的handler的方法吗

1个Handler的创建它就会被绑定到这个线程的消息队列中,如果是在主线程创建的,那就不需要写代码来创建消息队列了,默许的消息队列会在主线程被创建。但是如果是在子线程的话,就必须在创建Handler之前先初始化线程的消息队列。

点击详情

11. fragment 中替换和添加的区分.哪一个有优势.

transaction.replace()
使用另外一个Fragment替换当前的,实际上就是remove()然后add()的合体

  • transaction.replace() fragment生命周期会重走.
  • transaction.add() 当使用add(),show(),hide()跳转新的Fragment时,旧的Fragment回调onHiddenChanged(),不会回调onStop()等生命周期方法,而新的Fragment在创建时是不会回调onHiddenChanged(),这点要切记.

点击详情

------分隔线----------------------------
------分隔线----------------------------

最新技术推荐