面试题

142 阅读10分钟

网络

1.Https

20 张图彻底弄懂 HTTPS 的原理!

  • 使用对称密钥:加密和解密使用的是同一个密钥。弊端:最开始的时候怎么将这个对称密钥发送出去呢?如果对称密钥在发送的时候就已经被拦截,那么发送的信息还是会被篡改和窥视

  • 使用非对称密钥:双方必须协商一对密钥,一个私钥一个公钥。用私钥加密的数据,只有对应的公钥才能解密,用公钥加密的数据, 只有对应的私钥才能解密。A将自己的公钥发给B,B以后给A发消息时候用公钥加密后发送,A收到消息后用自己的私钥解密。弊端:非对称密钥(RSA)加密和解密速度慢

  • 非对称密钥+对称密钥:A将自己的公钥发给B,B用公钥将对称密钥加密发给B,这样双方就安全地传递了对称加密的密钥,既解决了密钥的传递问题, 又解决了RSA速度慢的问题。弊端:第一次传递公钥时有风险

  • 数字证书。假如一开始A将自己的公钥发给B时,中间被拦截,拦截者替换成自己的公钥,这样还是会有安全问题。解决办法:数字证书

2.三次握手

其实就是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。进行三次握手的主要作用就是为了确认双方的接收能力和发送能力是否正常

  • 第一次握手:客户端发送网络包,服务端收到了。 这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的。
  • 第二次握手:服务端发包,客户端收到了。 这样客户端就能得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常。
  • 第三次握手:客户端发包,服务端收到了。 这样服务端就能得出结论:客户端的接收、发送能力正常,服务器自己的发送、接收能力也正常。

因此,需要三次握手才能确认双方的接收与发送能力是否正常。

内存优化

1.什么是ANR造成ANR的常见原因

什么是ANR?

在Android中,如果主线程被长时间阻塞,导致无法响应用户的操作,即造成ANR(Application Not Responding)。通常的表现是弹出一个应用无响应的对话框,让用户选择强制退出或者等待。

ANR的常见原因:

  1. 应用在主线程上进行长时间的计算。
  2. 应用在主线程上执行耗时的I/O的操作。
  3. 主线程处于阻塞状态,等待获取锁。
  4. 主线程与其他线程之间发生死锁。
  5. 主线程在对另一个进程进行同步Binder调用,而后者需要很长时间才能返回。(如果我们知道调用远程方法需要很长时间,我们应该避免在主线程调用)

上述原因都会造成主线程被长时间阻塞,导致无法响应用户的操作,从而造成ANR。

2. 简单说下Android内存泄露的原因

  • Handler 引起的内存泄漏
  • 单例模式引起的内存泄漏
  • 非静态内部类创建静态实例引起的内存泄漏
  • 非静态匿名内部类引起的内存泄漏
  • 注册/反注册未成对使用引起的内存泄漏
  • 资源对象没有关闭引起的内存泄漏
  • 集合对象没有及时清理引起的内存泄漏

3.app性能优化

https://www.jianshu.com/p/b3b09fa29f65

Android基础

1.Activity lifecycle

onCreate() 创建活动,做一些数据初始化操作

onStart() 由不可见变为可见

onResume() 可以与用户进行交互,位于栈顶

onPause() 暂停,启动或恢复另一个活动时调用

onStop() 停止,变为不可见

onDestroy() 销毁

onRestart() 由停止状态变为运行状态

2.简单说明Activity的四种启动方式的特点和列举一种情况。

①.standard模式

a.Activity的默认启动模式

b.每启动一个Activity就会在栈顶创建一个新的实例。例如:闹钟程序

缺点:当Activity已经位于栈顶时,而再次启动Activity时还需要在创建一个新的实例,不能直接复用。

②.singleTop模式

特点:该模式会判断要启动的Activity实例是否位于栈顶,如果位于栈顶直接复用,否则创建新的实例。 例如:浏览器的书签

缺点:如果Activity并未处于栈顶位置,则可能还会创建多个实例。

③.singleTask模式

特点:使Activity在整个应用程序中只有一个实例。每次启动Activity时系统首先检查栈中是否存在当前Activity实例,如果存在

则直接复用,并把当前Activity之上所有实例全部出栈。例如:浏览器主界面

④.singleInstance模式

特点:该模式的Activity会启动一个新的任务栈来管理Activity实例,并且该势力在整个系统中只有一个。无论从那个任务栈中启动该Activity,都会是该Activity所在的任务栈转移到前台,从而使Activity显示。主要作用是为了在不同程序中共享一个Activity

3.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

  • 9.0

  • 10.0

4.  作图简单解释一下 Handler机制的实现原理。

5.简单说明View的事件传递和事件分发的主要有三个关键方法是什么?

事件传递:

Activity调用dispathTouchEvent()方法,把事件传递给Window;

Window再将事件交给DecorView(DecorView是View的根布局);

DecorView再传递给ViewGroup;

Activity ——> Window ——> DecorView ——> ViewGroup——> View

ViewRoot只是ViewTree的管理者,和View没有关系, 真正的根结点是DecorView。

事件分发:

dispatchTouchEvent() 分发

onInterceptTouchEvent() 拦截 ,只有ViewGroup独有此方法

onTouchEvent() 处理触摸事件

6. Java中成静态内部类和非静态内部类特点

  • 静态内部类:和外部类没有什么"强依赖"上的关系,耦合程度不高,可以单独创建实例。由于静态内部类与外部类并不会保存相互之间的引用,因此在一定程度上,还会节省那么一点内存资源

  • 内部类中需要访问有关外部类的所有属性及方法

设计模式

1.MVVP 有什么优缺点?并简单说明一下 LiveData 和 ViewModel 的作用。

3.MVVM通过数据驱动来自动完成的,数据变化后会自动更新UI,UI的改变也能自动反馈到数据层 MvvmMvp比较相似,不同的是Mvvm中可以用DataBinding或者LiveData动态绑定数据,进一步降低耦合

  • Activity负责UI操作,ViewModel负责数据逻辑。两者通过LiveData进行关联。ViewModel返回LiveData实例和Activity绑定,ViewModel有变化时可以自动更新UI,Activity也可以通过ViewModel发起数据请求
  • 问题:

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

2.Double Check Lock 实现单例

public static TestInstance getInstance(){ //1
    if (mInstance == null){ //2
        synchronized (TestInstance.class){ //3
            if (mInstance == null){ //4
                mInstance = new TestInstance(); //5
            }
        }
    }
    return mInstance;
}

第一层判断主要是为了避免不必要的同步
第二层的判断则是为了在 null 的情况下创建实例。mInstance = new TestInstance(); 这个步骤,其实在jvm里面的执行分为三步:
1.在堆内存开辟内存空间;
2.初始化对象;
3.把对象指向堆内存空间;
由于在 JDK 1.5 以前 Java 编译器允许处理器乱序执行。不过在 JDK 1.5 之后,官方也发现了这个问题,故而具体化了 volatile ,即在 JDK 1.6 以后,只要定义为 private volatile static DaoManager3 sinstance ; 就可解决 DCL 失效问题

Java 基础

1.线程的sleep方法

  • 不会释放锁
    通过synchronized同步块实现锁机制。线程tv1睡眠三秒后,线程tv2里的run()方法代码才执行,因为它获取不到锁发生了阻塞

        var t1: Thread = object : Thread() {
        override fun run() {
              synchronized(any){
                  println("线程一开始睡眠")
                  Thread.sleep(3000)
                  println("线程一睡眠结束")
               }
            }
        }
        var t2: Thread = object : Thread() {
            override fun run() {
               synchronized(any){
                   println("线程2开始睡眠")
                   Thread.sleep(3000)
                   println("线程2睡眠结束")
               }
            }
        }
        t1.start()
        t2.start()
          
        //日志输出
        14:13:24.660 1805-1836/ I/System.out: 线程一开始睡眠
        14:13:27.663 1805-1836/ I/System.out: 线程一睡眠结束
        14:13:29.555 1805-1837/ I/System.out: 线程2开始睡眠
        14:13:32.556 1805-1837/ I/System.out: 线程2睡眠结束
    复制代码
    
  • 线程在sleep时是可以被中断的
    线程1在sleep时候被2中断

        var t1: Thread = object : Thread() {
            override fun run() {
                println("线程一开始睡眠")
                try {
                    Thread.sleep(30000)
                } catch (e: InterruptedException) {
                    println("===error=== ")
                }
                println("线程一睡眠结束")
            }
        }
        var t2: Thread = object : Thread() {
            override fun run() {
                Thread.sleep(3000)
                t1.interrupt()
            }
        }
        t1.start()
        t2.start()
    复制代码
    
  • sleep(0)

    • 在线程没退出之前,线程有三个状态,就绪态,运行态,等待态

    • sleep(n)之所以在n秒内不会参与CPU竞争,是因为,当线程调用sleep(n)的时候,线程是由运行态转入等待态,线程被放入等待队列中,当n秒后,线程才重新由等待态转入就绪态,被放入就绪队列中,等待队列中的线程是不参与cpu竞争的,只有就绪队列中的线程才会参与cpu竞争

    • 所谓的cpu调度,就是根据一定的算法(优先级,FIFO等。。。),从就绪队列中选择一个线程来分配cpu时间。

    • sleep(0)之所以马上回去参与cpu竞争,是因为调用sleep(0)后,因为0的原因,线程直接回到就绪队列,而非进入等待队列,只要进入就绪队列,那么它就参与cpu竞争

2.多线程并发问题

  • 多线程为什么会有并发问题

1.Java 内存模型规定了所有的变量都存储在主内存中,每条线程有自己的工作内存。

2.线程的工作内存中保存了该线程中用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。

3.线程访问一个变量,首先将变量从主内存拷贝到工作内存,对变量的写操作,不会马上同步到主内存。

4.不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。

  • 并发三要素

原子性:在一个操作中,CPU 不可以在中途暂停然后再调度,即不被中断操作,要么执行完成,要么就不执行。

可见性:多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

有序性:程序执行的顺序按照代码的先后顺序执行。

  • 如何做(重点)

一、volatile

二、Synchronized

三、ReentrantLock

四、并发包