专栏名称: 飞翔的超人
Android开发
今天看啥  ›  专栏  ›  飞翔的超人

Android面试题

飞翔的超人  · 掘金  ·  · 2019-08-22 12:25

文章预览

阅读 24

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 2

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

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

输出结果:1 2 1 2

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

3.final 、finally、finalize 区别

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

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

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


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的成员变量)


Android框架知识


1.buttnife实现原理

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

2. EventBus实现原理

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


设计模式


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;
}
复制代码
………………………………

原文地址:访问原文地址
快照地址: 访问文章快照
总结与预览地址:访问总结与预览