最近收集的一些面试题

最近遇到一些比较有代表性,有点挑战性的面试题,
大概集中这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();  
  }  
}  

点击详情1
点击详情2

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

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

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

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

静态方法不依赖类就可以访问。这就是它的用途啊,没有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.Viewandroid.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(),这点要切记.

点击详情

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值