记录2019年安卓面试

  • 你在项目里都用到了那些设计模式,以及你了解知道的设计模式,和运用场景。

a. 建造者模式:将一个复杂的对象的构建拆分,一个复杂的对象可能存在多种不同的表现形式,使用同样的构造过程,可以创建出来不同的表现。

比如:常见的安卓使用这种模式的有AlertDialog,在开发的例子Camera开发过程中,可能需要设置一个初始化的相机配置,设置摄像头方向,闪光灯开闭,成像质量等等,这种场景下就可以使用建造者模式。

b.装饰者模式:动态地给一个对象添加一些额外的职责。装饰者可以在不改变原有类的结构上增强类的功能。

比如:需要扩展一个类的功能,或给一个类增加附加功能时,可以使用装饰者模式,这样的话就可以使用装饰者模式,比如在项目里面封装好的网络请求,在后期开发中,需要添加新的功能,可以在向外包装一层。

c.观察者模式:定义对象间的一种一个对多个的依赖关系,当一个对象的状态发生变化的时候,所有依赖它的对象都要得到通知并自动更新。

比如:项目中有用到的地方可以说在网络框架这里,封装网络状态的时候,跟住不同的网路状态来设置不同的功能,比如WIFI,移动网络,或者是无网络的时候的一些处理,可以通过观察网络的状态来实现。

  • 说说java的线程,以及你对线程池的理解。
线程的创建方法

1.继承Thread类实现多线程
2.实现Runnable接口
3.实现Callable接口

线程与进程的区别

进程包括线程,一个进程可以有多个线程,不同的进程使用不同的内存空间,所有的线程同享同一个内存空间。

Java中Runnable和Callable有什么不同?

Runnable和Callable都代表着在不同线程里面执行的任务。Ruannable是在java1.0出来的,而Callable是在java1.5后出来的,主要区别是Callable的call()方法有返回值或者抛出异常,而runnable的run()没有这个功能。Callable可以返回装载有计算结果的Future对象。

java中的关键字volatile是什么

volatile关键字只能作用在成员变量上,被volatile修饰的变量,可以保证下一个读取操作会在前一个写操作之后发生。

一个线程发生异常会怎么样?

简单的说,如果异常没有被捕获的话,该线程就会被停止。
对于没有捕获的异常,我们的方法是通过给线程设置UncaughtExceptionHandler,即对未知异常的处理,这时你就可以进行相关日志操作了.

class MyExceptionHandler implements UncaughtExceptionHandler {
    @Override
    public void uncaughtException(Thread t, Throwable e) {//在这你可以记录相关错误日志到文件中
        System.out.printf("An exception has been captured\n");
      System.out.printf("Thread:%s\n", t.getName());
      System.out.printf("Exception: %s: %s:\n", e.getClass().getName(), e.getMessage());
      System.out.printf("Stack Trace:\n");
      e.printStackTrace();
      System.out.printf("Thread status:%s\n", t.getState());
}
//然后把你创建的异常处理类,设置给你的线程就可以了。
  Thread thread = new Thread(new ThreadTest1()); 
        thread.setUncaughtExceptionHandler(new MyExceptionHandler());
        thread.start();
如何在多线程中共享数据

这个问题你首先要想到的是我们常举得例子,卖票和存款的例子。
卖票的例子是做的同一个操作,也就是多个线程使用同一个runnable就可以。
存款的例子是让封装共同数据,让共享数据成为成员变量,书写不同的runnable,来调用共享类的成员变量,使用像阻塞队列这样并发的数据结构。用wait和notify方法可以实现生产者消费者模型。实现数据的共享。

java中堆和栈有什么不同

每个线程都有自己的栈内存,而多个线程共享同一个堆内存。最主要的区别就是栈内存用来存储局部变量和方法调用。而堆内存用来存储Java中的对象。无论是成员变量,局部变量,还是类变量,它们指向的对象都存储在堆内存中。

活锁与死锁

活锁:优先级相同的线程,互相礼让,都不执行,最终导致活锁
死锁:多个线程需要互相等待对方的操作资源的时候,其他线程持有资源不释放而导致的,常见的情况就是线程持有的锁不同而导致的。其实就是互相不礼让,最终导致死锁。
饿死:有一个线程一直礼让,最终导致这个线程的任务一直不执行。

单例模式的双检锁问题
public class Singleton {
 //正确的是加上 volatile关键字  private static volatile Singleton uniqueInstance; 
    private static Singleton uniqueInstance; 


    private Singleton(){
    }

    public static Singleton getInstance(){
        if(uniqueInstance == null){ //#1
            synchronized(Singleton.class){ //#2
                if(uniqueInstance == null){ //#3
                    uniqueInstance = new Singleton(); //#4
                    System.out.println(Thread.currentThread().getName() + ": uniqueInstance is initalized..."); //#5.1
                } else {
                    System.out.println(Thread.currentThread().getName() + ": uniqueInstance is not null now..."); //#5.2
                }
            }
        }
        return uniqueInstance;
    }
}

情况假如线程1进来,走到#1,判断实例为空,让出cpu的执行权给线程2.
线程2进来执行#1 判断为空,让出cpu的执行权,给线程1。
线程1一直走到#2,#3,#4,出去,线程2进来,由于线程2在#1中持有的实例为空,所以也就会走#2,#3,#4.从而创建出两个对象。
加上volatile后,这个关键字可以及时更新每个线程里面的数据变化,也就是可以保证下一个读取操作会在前一个写操作之后发生。

什么是线程池,如何使用
  • 什么是线程池:java提供了Executor接口的实现用于创建线程池,线程池主要是用来管理线程的。
    其中有四个常用的线程池
    ①newSingleThreadExecutor
    单个线程的线程池,即线程池中每次只有一个线程工作,单线程串行执行任务
    ②newFixedThreadExecutor(n)
    固定数量的线程池,没提交一个任务就是一个线程,直到达到线程池的最大数量,然后后面进入等待队列直到前面的任务完成才继续执行
    ③newCacheThreadExecutor(推荐使用)
    可缓存线程池,当线程池大小超过了处理任务所需的线程,那么就会回收部分空闲(一般是60秒无执行)的线程,当有任务来时,又智能的添加新线程来执行。
    ④newScheduleThreadExecutor
    大小无限制的线程池,支持定时和周期性的执行线程
线程池的工作原理

线程池可以减少创建和销毁线程的次数,从而减少系统资源的消耗,当一个任务提交到线程池时

a. 首先判断核心线程池中的线程是否已经满了,如果没满,则创建一个核心线程执行任务,否则进入下一步

b. 判断工作队列是否已满,没有满则加入工作队列,否则执行下一步

c. 判断线程数是否达到了最大值,如果不是,则创建非核心线程执行
任务,否则执行饱和策略,默认抛出异常
结合项目的例子:多张图片的上传,在使用线程池后可以减少上传的时间。原本上传需要9秒,用线程池后需要2秒。

  • 内存泄漏问题
  1. 非静态内部类造成的内存泄漏
    非静态的内部类会持有外部类的指引,当在内部类中做一些不可控制生命周期的操作就有可能会产生内存泄漏,比如在线程中执行耗时操作就有可能发生内存泄漏,导致外部类无法被回收,直到耗时任务结束,解决办法是在页面退出时结束线程中的任务
  2. 非静态内部类的静态实例造成的内存泄漏。
    非静态内部类会持有外部类的指引,如果非静态内部类的静态实例,就会长期持有外部类的指引,这样外部类就不会被系统回收,解决方法是把非静态内部类写成静态内部类。
  3. handler内存泄漏
    handler也是非静态内部类造成的内存泄漏,因为handler内部有一个MessageQueue用来存放message,有些message不能被及时处理会长时间存在于内存中,导致handler无法被回收,如果handler属于非静态内部类,所以持有外部类的指引,导致外部类不能被回收,解决方法1.使用静态handler,外部类引用使用弱引用处理2.在退出页面时移除消息队列中的消息。
  4. Context导致的内存泄漏
    跟住场景需要使用Activity还是Application的Context,因为他们的生命周期不一样,一些不需要使用Activity的Context的地方就换成Application的Context。最常见的就是单例传入的上下文引起的内存泄漏,比如传入一个Activity的Context被静态类引用,导致无法回收。
  5. 静态View引起的内存泄漏
    使用静态View可以避免每次启动Activity都去读取并渲染View,但是静态View会持有Activity的引用,导致无法回收,解决办法是在Activity销毁的时候将静态View设置为null(View一旦被加载到界面中将会持有一个Context对象的引用,在这个例子中,这个context对象是我们的Activity,声明一个静态变量引用这个View,也就引用了activity)
  6. 资源对象未关闭导致的内存泄漏
    file,Cursor,内部都设置了缓存,在不适用的时候一定要关闭。
  7. bitmap造成的内存泄漏
    bitmap是比较占内存的,所以一定要在不使用的时候及时进行清理,避免静态变量持有大的bitmap对象
  8. 集合造成的内存泄漏
    集合用于保存对象,如果集合越来越大,不进行合理的清理,尤其是入股集合是静态的
  9. 监听器未关闭造成的内存泄漏
    注册的广播啊,服务啊等,不用的时候一定要取消掉。
  • onRestart的调用场景

按home键后再回到界面
从A界面跳到B界面,又按返回键返回A界面
从本Activity切换到其他的应用,然后再从其他应用切换回来,会调用onRestart();

  • 横竖屏切换时生命周期

1、不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行两次

2、设置Activity的android:configChanges="orientation"时,切屏还是会重新调用各个生命周期,切横、竖屏时只会执行一次

3、设置Activity的android:configChanges="orientation|keyboardHidden"时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法

  • SurfaceView与View的区别。
  1. View是底层单缓冲机制,SurfaceView是双缓冲机制
  2. View主要试用于主动更新,SurfaceView适用于被动更新,频繁更新。
    3 .View的绘制是在UI主线程,SurfaceView的绘制是单独开一个线程。

SurfaceView的内容不在应用窗口上,所以不能使用变换(平移、缩放、旋转等)。也难以放在ListView或者ScrollView中,不能使用UI控件的一些特性比如View.setAlpha()

View:显示视图,内置画布,提供图形绘制函数、触屏事件、按键事件函数等;必须在UI主线程内更新画面,速度较慢。

SurfaceView:基于view视图进行拓展的视图类,更适合2D游戏的开发;是view的子类,类似使用双缓机制,在新的线程中更新画面所以刷新界面速度比view快,Camera预览界面使用SurfaceView。

  • Android 中的线程有那些,原理与各自特点
  • ANR的原因

1.耗时的网络访问
2.大量的数据读写
3.数据库操作
4.硬件操作(比如camera)
5.调用thread的join()方法、sleep()方法、wait()方法或者等待线程锁的时候
6.service binder的数量达到上限
7.system server中发生WatchDog ANR
8.service忙导致超时无响应
9.其他线程持有锁,导致主线程等待超时
10.其它线程终止或崩溃导致主线程一直等待

你可能感兴趣的:(记录2019年安卓面试)