Android面试题


Java基础


1.HashMap实现原理,若是hashCode冲突怎么办,为何线程不安全,与Hashtable有什么区别

  • 主要经过计算数据的hashCode来插入
  • hashCode相同的元素插入同一个链表,才用数组+链表方式存储
  • 可能会有多个线程同时put数据,若同时push了hashCode相同数据,后面的数据可能会将上一条数据覆盖掉 Hashtable几乎在每一个方法上都加上synchronized(同步锁),实现线程安全

2.synchronized 修饰实例方法和修饰静态方法有什么不同

public synchronized void run() {
    System.out.println(1);
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
    }
    System.out.println(2);
}
复制代码
  • synchronized修饰普通方法时锁对象是this对象,而使用两个对象去访问,不是同一把锁
Demo demo = new Demo();
new Thread(() -> demo.run()).start();
Demo demo2 = new Demo();
new Thread(() -> demo2.run()).start();
复制代码

结果为: 1 1 2 2java

不一样步。但若是使用同一对象访问,结果才是同步的node

Demo demo = new Demo();
new Thread(() -> demo.run()).start();
new Thread(() -> demo.run()).start();
复制代码

输出结果:1 2 1 2android

  • 当synchronized修饰静态方法时,锁对象为当前类的字节码文件对象。使用不一样的对象访问,结果是同步的,由于当修饰静态方法时,锁对象是class字节码文件对象,而两个对象是同一个class文件,因此使用的是一个锁

3.final 、finally、finalize 区别

  1. final关键字用于基本数据类型前:这时代表该关键字修饰的变量是一个常量,在定义后该变量的值就不能被修改。
    final关键字用于方法声明前:这时意味着该方法时最终方法,只能被调用,不能被覆盖,可是能够被重载。
    final关键字用于类名前:此时该类被称为最终类,该类不能被其余类继承。算法

  2. 用try{ }catch(){} 捕获异常时,不管室友有异常,finally代码块中代码都会执行。sql

  3. finalize方法来自于java.lang.Object,用于回收资源。 能够为任何一个类添加finalize方法。finalize方法将在垃圾回收器清除对象以前调用数据库

4. Java中成静态内部类和非静态内部类特色

  • 静态内部类:和外部类没有什么"强依赖"上的关系,耦合程度不高,能够单首创建实例。因为静态内部类与外部类并不会保存相互之间的引用,所以在必定程度上,还会节省那么一点内存资源
  • 内部类中须要访问有关外部类的全部属性及方法

5.强引用、弱引用、软引用和虚引用

  • 强引用:当内存空间不足时,Java虚拟机宁愿抛出OutOfMemoryError错误也不会回收,直接new出来的就是强引用
  • 软引用:内存空间充足时,垃圾回收器不会回收它;若是内存空间不足了,就会回收这些对象的内存。
    当内存不足时,JVM首先将软引用中的对象引用置为null,而后通知垃圾回收器进行回收
if(JVM内存不足) {
        // 将软引用中的对象引用置为null
        str = null;
        // 通知垃圾回收器进行回收
        System.gc();
   }
复制代码
  • 弱引用:在垃圾回收器线程扫描它所管辖的内存区域的过程当中,一旦发现了只具备弱引用的对象,无论当前内存空间足够与否,都会回收它的内存。不过,因为垃圾回收器是一个优先级很低的线程,所以不必定会很快发现那些只具备弱引用的对象
  • 虚引用:虚引用顾名思义,就是形同虚设。与其余几种引用都不一样,虚引用并不会决定对象的生命周期。若是一个对象仅持有虚引用,那么它就和没有任何引用同样,在任什么时候候均可能被垃圾回收器回收

6.原子变量Atomic

还在用Synchronized?Atomic你了解不?apache

7.多线程并发问题

多线程并发问题编程

8.主线程是否能够直接捕获子线程的异常?

try{
    new Thread(){
        public void run(){
            if (...) throw new RuntimeException(); 
        }
    }.start();
}catch(Exception e){
}
复制代码

答案不能。设计模式

  • 线程代码不能抛出任何checked异常。全部的线程中的checked异常都只能被线程自己消化掉
  • 子线程代码抛出运行级别异常以后,线程会中断。主线程不受这个影响,不会处理这个RuntimeException,并且根本不能catch到这个异常。会继续执行本身的代码

9.Java内存模型

因此线程方法的异常只能本身来处理,线程的问题应该线程本身自己来解决,而不要委托到外部数组


Android基础知识


1.Looper总结

  • Looper经过prepare方法进行实例化,先从他的成员变量sThreadLocal中拿取,没有的话就new 一个Looper,而后放到sThreadLocal中缓存。每一个线程只能建立一个Looper实例
private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}
复制代码
  • Looper经过loop方法开启循环队列,里面开启了死循环,没有msg时候会阻塞
  • 在ActivityThread的main方法中也就是Activity启动的时候,已经调用了Looper.prepareMainLopper()方法

2.ThreadLocal在Looper中的使用

为了解决多个线程访问同一个数据问题,同步锁的思路是线程不能同时访问一片内存区域.而ThreadLocal的思路是,干脆给每一个线程Copy一份一摸同样的对象,线程之间各自玩本身的,互相不影响对方 常见ThreadLocal应用场景:确保在每个线程中只有一个Looper的实例对象

  • ThreadLocal的set方法
public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
}
复制代码
  • ThreadLocal的get方法
public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
 }
复制代码

简而言之:先拿到当前线程,再从当前线程中拿到ThreadLocalMap,经过ThreadLocalMap来存储数据。(ThreadLocalMap是Thread的成员变量)

3.Service 和 IntentService

Activity对事件响应不超过5秒,BroadcastReceiver执行不超过10秒,Service耗时操做为20秒。不然系统会报ANR

  • 使用startService()方法启用服务后,调用者与服务之间没有关连。调用者直接退出而没有调用stopService的话,Service会一直在后台运行
  • 使用bindService()方法启用服务,调用者与服务绑定在一块儿了,调用者一旦退出,服务也就自动终止
  • IntentService是Service的子类,会建立子线程来处理全部的Intent请求,其onHandleIntent()方法实现的代码,无需处理多线程问题

4.FragmentPageAdapter和FragmentPageStateAdapter的区别

  • FragmentPageAdapter在每次切换页面的的时候,没有彻底销毁Fragment,适用于固定的,少许的Fragment状况。默认notifyDataSetChanged()刷新是无效的

  • FragmentPageStateAdapter在每次切换页面的时候,是将Fragment进行回收,适合页面较多的Fragment使用,这样就不会消耗更多的内存

5.Sqlite数据库,什么是事务

事务是由一个或多个sql语句组成的一个总体,若是全部语句执行成功那么修改将会所有生效,若是一条sql语句将销量+1,下一条再+1,假若第二条失败,那么销量将撤销第一条sql语句的+1操做,只有在该事务中全部的语句都执行成功才会将修改加入数据库中
sqlite数据库相关操做,主要包括建立和增删改查,事务

6.怎么作Sqlite数据库升级

  1. 直接删除老数据库,但会形成数据丢失,通常不采用
  2. 对数据库进行升级,参考SQLite数据库版本升级

7.invalidate与requestLayout区别

  • view调用invalidate将致使当前view的重绘,viewGroup调用invalidate会使viewGroup的子view调用draw
  • requestLayout方法只会致使当前view的measure和layout,而draw不必定被执行。只有当view的位置发生改变才会执行draw方法

8.View和ViewGroup区别

  • ViewGrouponInterceptTouchEvent默认返回false,即不拦截事件,View没有拦截事件方法,View默认时消耗事件的
  • ViewGroup默认不会调用onDraw方法,View默认会调用onDraw方法。能够经过setWillNotDraw(boolean willNotDraw)来指定是否调用onDraw方法
/** * If this view doesn't do any drawing on its own, set this flag to * allow further optimizations. By default, this flag is not set on * View, but could be set on some View subclasses such as ViewGroup. * * Typically, if you override {@link #onDraw(android.graphics.Canvas)} * you should clear this flag. * * @param willNotDraw whether or not this View draw on its own */
    public void setWillNotDraw(boolean willNotDraw) {
        setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
    }
复制代码

9.android版本新特性

  • 5.0
    • 引入Material Design主题
  • 6.0
    • 运行时权限
  • 7.0
    • 文件读写权限适配FileProvider
    • 移除了对 Apache HTTP 客户端的支持,建议使用 HttpURLConnection 代替。继续使用 Apache HTTP API,必须先在 build.gradle 文件中配置:
    android {
            useLibrary 'org.apache.http.legacy'
        }
    复制代码
  • 8.0
    • 广播限制。不能在AndroidManifest文件对有些广播进行静态注册
  • 9.0

Android框架知识


1.buttnife实现原理

经过注解处理器动态生成java文件,在java文件中进行findViewById和setOnClickListener操做

2. EventBus实现原理

经过观察者设计模式,先经过注册的方式将指定的类加到一个表里面,等发送消息时轮训那个表,依据注解和注解的value找到匹配的方法,而后执行该方法

3.LiveData原理

LiveData通知其余组件原理主要是观察者设计模式。其优势有

  • 听从应用程序的生命周期,如在Activity中若是数据更新了但Activity已是destroy状态,LivaeData就不会通知Activity(observer)
  • 不会形成内存泄漏

4.Lifecycle

简单来讲,就是可让你本身的类拥有像 activity 或 fragment 同样生命周期的功能。继承Lifecycle 的组件将生命周期脱离出 activity 而转到本身身上
使用步骤:
(1) 继承DefaultLifecycleObserver

class TestObserver implements DefaultLifecycleObserver {
     @Override
     public void onCreate(LifecycleOwner owner) {
         // your code
     }
 }
复制代码

(2) LifecycleOwner是只有一个方法getLifecycle()的接口,是让拥有生命周期的东西实现好比(activity)用来获取Lifecycle。在Android Support Library 26.1.0 及其以后已经activity 和 fragment 已经默认实现了LifecycleOwner 因此在 activity 里咱们能够直接:

getLifecycle().addObserver(new MyObserver());
复制代码

这样咱们的MyObserver就会感知 activity 的生命周期了

5.FlowableObservable

RxJava1中使用ObservableObserver创建起订阅关系,但会产生背压问题。Rxjava2使用FlowableSubscriber替换RaJava1的ObservableObserverFlowable是在Observable的基础上优化后的产物,Observable能解决的问题Flowable也都能解决。可是并不表明Flowable能够彻底取代Observable,Flowable运行效率要比Observable慢得多。 只有在须要处理背压问题时,才须要使用Flowable

  • 当上下游在不一样的线程中,经过Observable发射,处理,响应数据流时,若是上游发射数据的速度快于下游接收处理数据的速度,这样对于那些没来得及处理的数据就会形成积压,这些数据既不会丢失,也不会被垃圾回收机制回收,而是存放在一个异步缓存池中,若是缓存池中的数据一直得不处处理,越积越多,最后就会形成内存溢出,这即是响应式编程中的背压(backpressure)问题
  • 若是可以肯定:
    1.上下游运行在同一个线程中
    2.上下游工做在不一样的线程中,可是下游处理数据的速度不慢于上游发射数据的速度
    3.上下游工做在不一样的线程中,可是数据流中只有一条数, 则不会产生背压问题,就没有必要使用Flowable,以避免影响性能。

6.app优化

  • 内存优化:使用leakcanary抓取内存泄露,或者使用android studio抓取内存信息,经过Profiler分析内存泄露状况
  • 体积优化
    • 不复杂图片使用svg代替png。换肤时使用着色器,可减小图片资源
    • build文件配置
      • 保留指定语言
      • 保留指定so库架构
      • 开启混淆压缩

7.Rxjava中关于Disposable

Rxjava容易遭层内存泄漏。在订阅了事件后没有及时取阅,致使在activity或者fragment销毁后仍然占用着内存,没法释放。而disposable,能够用来取消订阅
参考Rxjava关于Disposable你应该知道的事


设计模式


1.装饰设计模式

  • 当不适合采用生成子类的方式对已有类进行扩充时,能够采用装饰设计模式
  • 不适合采用生成子类的方式对已有类进行扩充缘由:会使类更加臃肿。子类会继承父类全部非private的变量和方法,而后再进行扩充。而使用装饰设计模式扩充的类,只须要增长扩种那部分功能便可
  • 使用场景:RecyclerView自己是不支持添加底部和头部的,那么采用装饰设计模式能够对其进行功能扩展。装饰设计模式 RecyclerView添加头部和底部

2.MVC、MCP、MVVP 的区别

1.MVC Android传统就是用MVC模式,Modle(逻辑)和V(View)直接交互,耦合度过高,MVC中是容许Model和View进行交互的,而MVP中很明显,Model与View之间的交互由Presenter完成。还有一点就是Presenter与View之间的交互是经过接口的

2.MVP 当View 须要更新数据时,首先去找 Presenter,而后 Presenter 去找 Model 请求数据,Model 获取到数据以后通知 Presenter,Presenter 再通知 View 更新数据,这样 Model 和 View就不会直接交互了,全部的交互都由 Presenter 进行,Presenter 充当了桥梁的角色。很显然,Presenter 必须同时持有 View 和 Model 的对象的引用,才能在它们之间进行通讯

存在问题:

  • 内存泄露:因为Presenter常常性的须要执行一些耗时操做那么当咱们在操做未完成时候关闭了Activity,会致使Presenter一直持有Activity的对象,形成内存泄漏
  • 随着业务逻辑的增长,UI的改变多的状况下,这样就会形成View的接口会很庞大。而MVVM就解决了这个问题

解决办法: 在Presenter中使用弱引用,将view的引用加到弱引用中去 每一个Activity都有BaseActivity,BaseActivity中

3.MVVM经过双向绑定的机制

  • 问题: 看起来MVVM很好的解决了MVC和MVP的不足,可是因为数据和视图的双向绑定,致使出现问题时不太好定位来源,有可能数据问题致使,也有可能业务逻辑中对视图属性的修改致使

算法


1.反转单链表

Node node4 = new Node(4, null);
Node node3 = new Node(3, node4);
Node node2 = new Node(2, node3);
Node node1 = new Node(1, node2);
Node pHead = node1;//头结点
复制代码

这组链表从1到4排序,要求反转后4到1

public static Node reverseList(Node pHead) {
        Node pReversedHead = null; //反转事后的单链表存储头结点
        Node pNode = pHead; //当前节点
        Node pPrev = null; //前一结点
        while (pNode != null) {
            //1.记录next,下一步:更新当前节点的上一节点和自己。最后移动一位
            Node pNext = pNode.next;
            if (pNext == null) {
                //到了尾节点
                pReversedHead = pNode;
            }
            pNode.next = pPrev;
            pPrev = pNode;
            pNode = pNext;
        }

        return pReversedHead;
}
复制代码

输出

pHead = reverseList(pHead);//反转以后头结点
while (pHead != null) {
    System.out.println(pHead.key);
    pHead = pHead.next;
}
复制代码
相关文章
相关标签/搜索