最近Android面试的知识点总结 | 掘金技术征文

890 阅读29分钟

面试所问到的知识点

  • jvm内存结构
  • JVM虚拟机了解吗
  • 说一下强引用 软引用 弱引用 虚引用
  • 虚引用与软引用和弱引用的一个区别
  • 线程的几种状态
  • 线程同步
  • java排序
  • Thread类中的start()和run()方法有什么区别?
  • wait和notify的区别
  • 抽象类和接口的区别
  • java的常用集合
  • 对线程池的理解
  • activity启动过程
  • Activity的四种启动模式、应用场景?了解哪些Activity常用的标记位Flags吗?
  • Fragment最多嵌套多少层,生命周期又是怎样去控制,子Fragment怎样去管理他的状态
  • Fragment懒加载
  • Android的事件分发,然后会举个例子让你解决问题。
  • 滑动冲突
  • binder机制
  • 简历上的源码:rxJava源码、retrofit源码、okhttp源码、glide源码、热修复源码、Leakcanary原理、Butterknife的原理等等
  • DexClassLoader和PathClassLoader的区别
  • 热修复框架的区别(回答完热修复会问)
  • rxjava的操作符
  • Rxjava的背压技术
  • rxJava怎样处理异步线程的
  • dagger2的父子adapter怎么样去实例化
  • Handler原理。消息队列的类型是什么
  • 主线程中的Looper.loop()一直无限循环为什么不会造成ANR?
  • ANR定位工具
  • 怎么避免ANR
  • 怎么精致化APK
  • webview的优化
  • 子线程怎么初始化一个looper handler
  • 子线程与子线程的通信
  • view绘制流程,刷新逻辑
  • 自定义view的调用流程
  • Android获取view的宽高;Activity在哪个生命周期可以拿到宽高
  • View.post和handler.post的区别
  • requestlayout调用流程,和invalide区别
  • MMKV和sharepreference
  • sharepreference里面的apply方法和commit有什么区别?
  • 对MVP、mvvm自己的理解
  • ANdroid进程间通信
  • Android打包不可混淆哪些资源
  • Tcp三次握手连接和四次挥手断开过程详解
  • http和https的区别
  • webview加载https注意什么
  • 了解websocket吗
  • 说一下tcp/ip协议
  • 说一下kotlin的Coroutine协程
  • 说一下kolin的backing field
  • 准备五六个设计模式
  • 安卓各个版本的不同

JVM虚拟机-java虚拟机

blog.csdn.net/qq_41701956… github.com/LixyAndroid…

jvm内存结构

程序计数器:虚拟字节码指令的地址

  • 内存空间小,它可以看成当前线程所执行的字节码的行号指示器。字节码解释器工作时通过程序计数器来选取下一个执行的字节码。分支、循环、跳转、异常处理等都需要依赖程序计数器来完成。
  • 为了确保线程切换后能恢复到正确的执行位置,每个线程都需要有独立的程序计数器,各条线程之间计数互不影响,即线程私有内存。
  • 线程如果正在执行一个java方法,那么这个计数器记录的是正在执行的虚拟机的字节码指令的地址;如果执行的是native方法,则值为Undefined。
  • 此区域是唯一一个在java虚拟机规范中没有规定何OutOfMemoryError情况的区域。

Java虚拟机栈:java方法(局部变量表、操作数栈、动态链接、方法出口)

  • 线程私有,生命周期和线程一致。
  • 描述的是 Java 方法执行的内存模型:每个方法在执行时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行结束,就对应着一个栈帧从虚拟机栈中入栈到出栈的过程。
  • 当线程请求的栈深度大于虚拟机所允许的深度,会抛出StackOverflowError;当虚拟机栈可以动态扩展时,如果扩展时无法申请到足够的内存,则会抛出OutOfMemoryError异常。

本地方法栈:native方法

  • 区别于 Java 虚拟机栈的是,Java 虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。
  • 也会有StackOverflowError 和 OutOfMemoryError 异常。

方法区:类信息、常量、静态变量、即时编译器编译后的代码

  • 各个线程共享的内存区域,用来存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。
  • 当方法区无法满足内存分配需要的时候,将抛出OutOfMemoryError异常。运行时常量池属于方法区的一部分,用于存放编译期间生成的各种字面量的符号引用,这部分内容将在类加载后进入方法区的运行时常量池存放。在运行期间也可能有新的常量放入常量池中,例如通过String.intern()方法。

Java堆:对象实例、数组

  • 这块区域是 JVM 所管理的内存中最大的一块。线程共享,主要是存放对象实例和数组。内部会划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)。可以位于物理上不连续的空间,但是逻辑上要连续。

java内存结构

java是在JVM所虚拟出的内存环境中运行的,内存分为三个区:堆、栈和方法区。

  • 栈(stack):是简单的数据结构,程序运行时系统自动分配,使用完毕后自动释放。优点:速度快。
  • 堆(heap):用于存放由new创建的对象和数组。在堆中分配的内存,一方面由java虚拟机自动垃圾回收器来管理,另一方面还需要程序员提供修养,防止内存泄露问题。
  • 方法区(method):又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。

引用计数器算法

堆中每个对象都有一个计数器,当对象被被其他对象引用时,计数器+1,当引用失效时-1,如果引用计数器为0时,该对象就会被回收。 缺点:当两个对象互相引用,而他们两个没有被用到时,这两个对象不会被回收。

可达性分析算法

以GCRoot作为起点开始搜索,在引用链上的对象不可被回收,不在引用链上的对象可以被回收。

哪些可以作为GCRoot对象呢?
  • 活的线程
  • 方法区中的静态变量和常量引用的对象
  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 本地方法栈中JNI(即一般说的native方法)引用的对象

retrofit源码

  • retrofit使用时创建retrofit对象,然后创建一个网络请求接口。
  • 创建对象的时候,使用构造者模式,增加网络请求适配器工厂(addCallAdapterFactoty),把对象进行平台适配,比如如果用rxjava的话,就会传RXjavaCallAdapterFactoty.creat(),这个对象其实就是创建了一个带参数的RXjavaCallAdapterFactoty对象。
  • 然后,增加数据转换器工厂(addConverterFactory),为retrofit添加合适的转化器,默认是用BuiltInConverters转化器,如果我们用gson的话就必须添加gsonConverterFactory.create(),这个对象其实是创建了一个带有gson对象的gsonConverterFactory实例。
  • 创建retrofit实例之后,retrofit会使用代理模式,通过Proxy.newProxyinterface创建代理,当网络接口方法被调用的时候,会调用newProxyinterface里面的invocationHandler接口的invoke方法。 在invoke方法里面,会将接口方法、方法注解、类型等信息封装到servicemethod对象里面,然后根据servicemethod和接口方法参数创建retrofit的call对象:okhttcall,最后,servicemethod将okhttcall转化成各个平台的call。
  • 整个过程,请求网络的对象还是okhttp的call来完成,retrofit负责请求前将参数注解方法解析,请求后将网络数据转化成我们所需要的java对象。

热修复源码

juejin.cn/post/684490…

Handler原理,消息队列的类型

  • 主线程main()在创建的时候,会默认调用looper.prepareMainLooper()方法,他的目的是创建一个属于主线程的looper对象,和MessageQueue对象。
  • 在handler创建实例的时候,指定了looper对象,因为looper对象绑定了线程,所以handler也绑定了looper所在的线程,然后复写handleMessage方法以便于处理消息的回调。
  • 在子线程中创建Message对象的时候,有两种方法,一种是new Message(),另一种的话是Message.obtin(),Message维护了一个消息池,通过obtain获取,是从消息池里面获取,获取不到再创建,建议obtin,避免每次new出来都重新分配内存
  • 子线程调用handler.sendMessage(message),获取到消息队列,内部最终会获取到handler所在线程的消息队列,然后调用MessageQueue.enqueueMessage()方法,把handler对象赋值给Message消息的target属性,然后将handler发送的消息加入到消息队列。
  • 主线程会调用looper.loop()方法,他无限循环,取出从消息队列中取消消息msg,并且调用一个handler的实例target的dispatchMessage()方法( msg.target.dispatchMessage(msg);),将消息通过handleMessage方法或者handleCallback()里面的run方法分发出去。
  • 消息队列的类型:单链表类型,以时间顺序存储,取的时候从表头取。

Thread避免ANR

  • 非静态内部类或者匿名内部类默认持有外部类的引用。
  • thread实现是匿名内部类或者内部类。 工作线程在处理任务,这时候外部类销毁的时候,由于工作线程的实例持有外部类引用,将使外部类无法GC回收,从而导致ANR。
避免ANR:
  • 方法1:因为静态内部类不默认持有外部类的引用,所以可以将thread设置成静态内部类。
  • 方法2:当外部类结束生命周期时,强制结束线程。在onDestroy里面调用Thread.stop();

handler避免ANR

  • 原因:主线程的Looper对象的生命周期 = 该应用程序的生命周期 在Java中,非静态内部类 & 匿名内部类都默认持有外部类的引用
  • 在handler中如果消息没有处理完,handleer中的message对象持有handler引用,非静态内部类/匿名内部类handler持有activity的引用,当activity destroy的时候,将无法回收。
避免ANR:
  • 方法一:将非静态内部类换成静态的
  • 方法二:在activity销毁的时候把消息清空,调用mHandler.removeCallbacksAndMessages(null);

子线程怎么初始化一个looper handler

  • 方法一 :在子线程中调用looper.prepare(); looper.loop(); 在子线程获取当前的looper对象:Looper.myLooper()
private void createHandler(){
    new Thread(new Runnable() {
        @Override public void run() {
            Looper.prepare();
            Handler andler = new Handler(Looper.myLooper());
            Looper.loop();//传入子线程Looper
        }
    }).start();
}

//推送任务到消息队列
private void postRun(){
    mHandler.post(new Runnable() {
        @Override public void run() {
            //执行耗时操作
            SystemClock.sleep(5000);
            
            //更新UI
            MainActivity.this.runOnUiThread(new Runnable() {
                @Override public void run() {
                    actTextReslt.setText("完成操作");
                }
            });
        }
    });
}
  • 方法二:HandlerThread(内部创建消息队列,外部通过handler通知HandlerThread执行)
private Handler mHandler;
//创建子线程handler
private void createHandler(){
    HandlerThread handlerThread = new HandlerThread("myHandlerThread", Process.THREAD_PRIORITY_BACKGROUND);
    handlerThread.start();//必须开启
    mHandler = new Handler(handlerThread.getLooper());
}
//推送任务到消息队列
private void postRun(){
    mHandler.post(new Runnable() {
        @Override public void run() {
            //执行耗时操作
            SystemClock.sleep(5000);

            //更新UI
            MainActivity.this.runOnUiThread(new Runnable() {
                @Override public void run() {
                    actTextReslt.setText("完成操作");
                }
            });
        }
    });
}

子线程与子线程的通信

接受信息的子线程声明handler,记得写looper,传递信息的子线程发送消息就好。 blog.csdn.net/yh186681971…

ANR定位工具

获取view的宽高

  • 通过onWindowFocusChanged方法
  • 通过View.post()来实现
  • 通过ViewTreeObserver的OnGlobalLayoutListener回调

通过onWindowFocusChanged方法

Activity的窗口得到焦点时,View已经初始化完成,此时获取到的View的宽高是准确的

public class GetHeightSampleActivity extends AppCompatActivity {

    TextView textView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_get_height);
        textView = findViewById(R.id.tv);
    }

//通过onWindowFocusChanged方法
    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        if (hasFocus) {
            Log.w("tv_width", "" + textView.getWidth());
            Log.w("tv_height", "" + textView.getHeight());
        }
    }
}

通过ViewTreeObserver的OnGlobalLayoutListener回调

viewtree有任何变化都会调用此方法

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_get_height);
    textView = findViewById(R.id.tv);

    final ViewTreeObserver viewTreeObserver = textView.getViewTreeObserver();
    viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            viewTreeObserver.removeOnGlobalLayoutListener(this);
            Log.w("tv_width", "" + textView.getWidth());
            Log.w("tv_height", "" + textView.getHeight());
        }
    });
}
通过View.post()来实现

View.post()会等到view绘制完才会回调回来

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_get_height);
    textView = findViewById(R.id.tv);

    textView.post(new Runnable() {

        @Override
        public void run() {
            Log.w("tv_width", "" + textView.getWidth());
            Log.w("tv_height", "" + textView.getHeight());
        }
    });
}

线程的几种状态

1.新建(NEW)

新建状态,线程创建且没有执行start方法时的状态

2.可运行(RUNNABLE)

可运行状态,线程已经启动,但是等待相应的资源(比如IO或者时间片切换)才能开始执行

3.运行(RUNNING)

可运行状态的线程获取了CPU,执行程序代码。

4.阻塞(BLOCKED)

阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。

阻塞的情况分三种:

1.等待阻塞:

运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。

2.同步阻塞:

运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。

3.其他阻塞:

运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。

5.死亡(DEAD)

线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

Thread类中的start()和run()方法有什么区别?

start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,这和直接调用run()方法的效果不一样。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。

线程同步

用synchronized关键字。因为java为每一个对象都设置了内置锁,当使用synchronized关键字修饰方法时,内置锁就会保护这个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

  • 锁非静态方法:锁住的是当前对象
  • 锁静态方法,此时如果调用该静态方法,将会锁住整个类。
  • 锁代码块:同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。
//锁代码块的几种情况
public void test(){
    //传this,是对象锁
        synchronized (this) {
            // TODO
        }
    }
    
    public void method2(){     
    //传类,类锁。作用:这个类的所有对象
        synchronized (ObjectLock.class) {
            try {
                System.out.println("do method2..");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
     private Object lock = new Object();
    public void method3(){      //任何对象锁
        synchronized (lock) {
            try {
                System.out.println("do method3..");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

wait和notify

  • wait():使一个线程处于等待状态,并且释放所持有的对象的lock。wait()使用notify或者notifyAlll或者指定睡眠时间来唤醒当前等待池中的线程。
  • sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。
  • notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。
  • Allnotity():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。

事件分发

upload-images.jianshu.io/upload_imag…

APK精致化

  • svg矢量图:如果有大量套图问题的话可以使用svg定义xml图形,他是可缩放的矢量图可以配置兼容哪些套图(vectorDrawables.generatedDensities('xhdpi','xxhdpi')),在安卓中是通过vectory来对svg进行支持的,所以使用的时候先将svg转换成android的vectory
  • 图片转化成webp格式:webp格式的体积更小,质量损失小到可以忽略不计。
  • tint着色器:只有颜色不同的图片,可以用tint着色器减少对图片的需求量,减少apk体积。
  • build里面配置resConfigs('zh-rCN','ko') ,可以只保留两国语言:apk里面有resources.arsc文件,string里面包含很多国家的语言,可以设置resConfigs,只保留自己所需要的语言。
  • so库配置:当引入so库的时候 modle的build.gradle会引入jniLibs.srcDirs=['libs'],这样打出来的apk特别大,可以在defaultConfig里面配置ndk {abiFilters('armeabi','armeabi-v7a)},不适用于定制化开发(eg:PDA设备)、内部开发要求版本性兼容性高的。
  • 移除无用资源:利用AS: Analyze,Run inspection by Name,unused resource
  • 代码混淆
  • 启用shrinkResources资源缩减:(设置shrinkResources true)它不需要物理上的压缩,可以自定义要保留的资源,在路径res---raw---keep.xml。
  • 微信资源混淆工具AndResGuard+7zip压缩:微信资源混淆主要为了混淆资源ID长度(例如将res/drawable/welcome.png混淆为r/s/a.png),同时用7zip压缩可以减少apk提交,提升反破解的难度

requestlayout调用流程,和invalide区别

  • requestlayout()调用父容器的requestlayout(),层层向上,直到Decorview也就是根view,根view传递给ViewRootImpl接受处理,ViewRootlImpl会调用measure()、layout()、draw()三大流程,对每一个有标记位的view及他的子view都进行measure()、layout()、draw()。
  • invalidate()最终会调用view的invalideInternal(),他会判断是否重新绘制,为view设置标记位,把重新绘制的区域传递给父容器,父容器计算得出自身需要绘制的区域,直到传到ViewRootImpl中国,最终触发performTravserals()对view进行重绘制。

区别

invalidate():

  • invalidate()不会导致measure()和layout()被调用,父view不会执行draw()
  • viewgroup调用invalidate()会使子view调用draw()
  • invalidate()必须在ui线程中调用,如果非ui线程调用则用postinvalidate()
  • 如果viewgroup它的子view变化,调用incalidate(),因为对viewgroup而言,他的属性没变化

requestlayout()

  • 子view布局发生变化时,父布局会变化。这个方法不能在正在布局的时候调用。
  • 调用这个方法导致布局重绘,调用measure()、layout()、draw()

view绘制流程,刷新逻辑

blog.csdn.net/weixin_4309… www.jianshu.com/p/a5ea8174d…

滑动冲突

www.jianshu.com/p/d82f426ba…

sharepreference里面的apply方法和commit有什么区别?

SharedPreference 相关修改使用 apply 方法进行提交会先写入内存,然后异步写入磁盘,commit 方法是直接写入磁盘。如果频繁操作的话 apply 的性能会优于 commit,apply会将最后修改内容写入磁盘。 但是如果希望立刻获取存储操作的结果,并据此做相应的其他操作,应当使用 commit。

MVP自己的理解

用法:www.jianshu.com/p/ae0b21d32… blog.csdn.net/vector_yi/a… Model-View-Presenter。 在MVP模式里通常包含4个要素:

(1)View:负责绘制UI元素、与用户进行交互(在Android中体现为Activity;

(2)View interface:需要View实现的接口,View通过View interface与Presenter进行交互,降低耦合,方便进行单元测试;

(3)Model:逻辑处理、负责存储、检索、操纵数据等(有时也实现一个Model interface用来降低耦合);

(4)Presenter:作为View与Model交互的中间纽带(Model对象调用其方法),处理与用户交互的负责逻辑。

优点
  • 降低耦合度,实现了Model和View真正的完全分离,可以修改View而不影响Model。
  • 模块职责划分明显,层次清晰。
  • 隐藏数据。
  • Presenter可以复用,一个Presenter可以用于多个View,而不需要更改Presenter的逻辑(当然是在View的改动不影响业务逻辑的前提下)。
  • 利于测试驱动开发。以前的Android开发是难以进行单元测试的(虽然很多Android开发者都没有写过测试用例,但是随着项目变得越来越复杂,没有测试是很难保证软件质量的;而且近几年来Android上的测试框架已经有了长足的发展——开始写测试用例吧),在使用MVP的项目中Presenter对View是通过接口进行,在对Presenter进行不依赖UI环境的单元测试的时候。可以通过Mock一个View对象,这个对象只需要实现了View的接口即可。然后依赖注入到Presenter中,单元测试的时候就可以完整的测试Presenter应用逻辑的正确性。
  • View可以进行组件化。在MVP当中,View不依赖Model。这样就可以让View从特定的业务场景中脱离出来,可以说View可以做到对业务完全无知。它只需要提供一系列接口提供给上层操作。这样就可以做到高度可复用的View组件。 代码灵活性
缺点
  • Presenter中除了应用逻辑以外,还有大量的View->Model,Model->View的手动同步逻辑,造成Presenter比较笨重,维护起来会比较困难。 由于对视图的渲染放在了Presenter中,所以视图和Presenter的交互会过于频繁。
  • 如果Presenter过多地渲染了视图,往往会使得它与特定的视图的联系过于紧密。一旦视图需要变更,那么Presenter也需要变更了。 额外的代码复杂度及学习成本。
  • MVP在实现代码简洁的同时,额外增加了大量的接口、类,不方便进行管理,于是Contract就登场了
public interface MainContract {
    interface Model {
        Flowable<BaseObjectBean<LoginBean>> login(String username, String password);
    }

    interface View extends BaseView {
        @Override
        void showLoading();

        @Override
        void hideLoading();

        @Override
        void onError(Throwable throwable);

        void onSuccess(BaseObjectBean<LoginBean> bean);
    }

    interface Presenter {
        /**
         * 登陆
         *
         * @param username
         * @param password
         */
        void login(String username, String password);
    }
}

单例模式

饿汉式 (可用)

就是在类定义的时候就实例化了(因为饿,主观能动性强 - . -)。

public class Singleton {
    private final static Singleton INSTANCE = new Singleton();
    
    private Singleton(){}
    
    public static Singleton getInstance(){
        return INSTANCE;
    }
}
普通的懒汉式 (线程不安全,不可用)

这种写法有个致命的问题,就是多线程的安全问题。假设对象还没被实例化,然后有两个线程同时访问,那么就可能出现多次实例化的结果,所以这种写法不可采用。

public class Singleton {

    private static Singleton instance = null;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

}
同步方法的懒汉式 (可用)

这种写法是对getInstance()加了锁的处理,保证了同一时刻只能有一个线程访问并获得实例,但是缺点也很明显,因为synchronized是修饰整个方法,每个线程访问都要进行同步,而其实这个方法只执行一次实例化代码就够了,每次都同步方法显然效率低下,为了改进这种写法,就有了下面的双重检查懒汉式。

public class Singleton {

    private static Singleton instance = null;

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
双重检查懒汉式 (可用,推荐)

这种写法用了两个if判断,也就是Double-Check,并且同步的不是方法,而是代码块,效率较高,是对第三种写法的改进。为什么要做两次判断呢?这是为了线程安全考虑,还是那个场景,对象还没实例化,两个线程A和B同时访问静态方法并同时运行到第一个if判断语句,这时线程A先进入同步代码块中实例化对象,结束之后线程B也进入同步代码块,如果没有第二个if判断语句,那么线程B也同样会执行实例化对象的操作了。

public class Singleton {

    private static volatile Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

}
静态内部类 (可用,推荐)

因为类的静态属性只会在第一次加载类的时候初始化,也就保证了SingletonInstance中的对象只会被实例化一次,并且这个过程也是线程安全的。

public class Singleton {

    private Singleton() {}

    private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }

}

Activity的四种启动模式、应用场景?了解哪些Activity常用的标记位Flags?

  • standard:

  • singleTop:说明:分两种处理情况:须要创建的Activity已经处于栈顶时,此时会直接复用栈顶的Activity。不会再创建新的Activity;若须要创建的Activity不处于栈顶,此时会又一次创建一个新的Activity入栈,同Standard模式一样。 应用:假设你在当前的Activity中又要启动同类型的Activity,此时建议将此类型Activity的启动模式指定为SingleTop,能够降低Activity的创建,节省内存!

  • singleTask: 若须要创建的Activity已经处于栈中时,此时不会创建新的Activity,而是将存在栈中的Activity上面的其他Activity所有销毁,使它成为栈顶。 应用:主页

  • singleInstance

注意:复用Activity时的生命周期回调 这里还须要考虑一个Activity跳转时携带页面參数的问题。

由于当一个Activity设置了SingleTop或者SingleTask模式后,跳转此Activity出现复用原有Activity的情况时,此Activity的onCreate方法将不会再次运行。onCreate方法仅仅会在第一次创建Activity时被运行。

而一般onCreate方法中会进行该页面的数据初始化、UI初始化,假设页面的展示数据无关页面跳转传递的參数,则不必操心此问题,若页面展示的数据就是通过getInten() 方法来获取,那么问题就会出现:getInten()获取的一直都是老数据,根本无法接收跳转时传送的新数据!这时我们须要另外一个回调 onNewIntent(Intent intent)方法。此方法会传入最新的intent,这样我们就能够解决上述问题。这里建议的方法是又一次去setIntent。然后又一次去初始化数据和UI。

启动模式的两种使用方式

1.在 Manifest.xml中指定Activity启动模式

2.启动Activity时。在Intent中指定启动模式去创建Activity

 Intent intent = new Intent();
        intent.setClass(context, MainActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);

注意:以上两种方式都能够为Activity指定启动模式,可是二者还是有差别的。

(1)优先级:动态指定方式即另外一种比第一种优先级要高,若两者同一时候存在,以另外一种方式为准。

(2)限定范围:第一种方式无法为Activity直接指定 FLAG_ACTIVITY_CLEAR_TOP 标识,另外一种方式无法为Activity指定 singleInstance 模式。

Activity 的 Flags

  • FLAG_ACTIVITY_SINGLE_TOP: 作用是为Activity指定 “SingleTop”启动模式,跟在AndroidMainfest.xml指定效果同样。
  • FLAG_ACTIVITY_NEW_TASK: 作用是为Activity指定 “SingleTask”启动模式。跟在AndroidMainfest.xml指定效果同样。
  • FLAG_ACTIVITY_CLEAN_TOP: 具有此标记位的Activity,启动时会将与该Activity在同一任务栈的其他Activity出栈。一般与SingleTask启动模式一起出现。它会完毕SingleTask的作用。但事实上SingleTask启动模式默认具有此标记位的作用.
  • FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS: 具有此标记位的Activity不会出如今历史Activity的列表中,使用场景:当某些情况下我们不希望用户通过历史列表回到Activity时,此标记位便体现了它的效果。它等同于在xml中指定Activity的属性:android:excludeFromRecents="trure"

Android打包不可混淆哪些资源

1、反射中使用的元素;

2、GSON的序列化与反序列化(本质还是用到了反射)

3、枚举也不要混淆(用到反射)

4、四大组件不要混淆(会导致Manifest名称与混淆后名称不一致)

5、其他:

①jni调用的java方法

②java的native方法

③js调用的java方法

④第三方库不建议混淆

⑤其他和反射相关的一些情况

Tcp三次握手连接和四次挥手断开过程详解

baijiahao.baidu.com/s?id=159601…

TCP的连接建立是一个三次握手过程,目的是为了通信双方确认开始序号,以便后续通信的有序进行。主要步骤如下:
  1. 连接开始时,连接建立方(Client)发送SYN包,并包含了自己的初始序号a;
  2. 连接接受方(Server)收到SYN包以后会回复一个SYN包,其中包含了对上一个a包的回应信息ACK,回应的序号为下一个希望收到包的序号,即a+1,然后还包含了自己的初始序号b;
  3. 连接建立方(Client)收到回应的SYN包以后,回复一个ACK包做响应,其中包含了下一个希望收到包的序号即b+1。
TCP终止连接的四次握手过程如下:
  1. 首先进行关闭的一方(即发送第一个FIN)将执行主动关闭,而另一方(收到这个FIN)执行被动关闭。
  2. 当服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。
  3. 同时TCP服务器还向应用程序(即丢弃服务器)传送一个文件结束符。接着这个服务器程序就关闭它的连接,导致它的TCP端发送一个FIN。
  4. 客户必须发回一个确认,并将确认序号设置为收到序号加1。

热修复框架的区别(回答完热修复会问)

www.jianshu.com/p/566aa853c…

Android应用程序启动过程:

blog.csdn.net/shareus/art… 1.Launcher通过Binder进程间通信机制通知ActivityManagerService,它要启动一个Activity; 2.ActivityManagerService通过Binder进程间通信机制通知Launcher进入Paused状态; 3.Launcher通过Binder进程间通信机制通知ActivityManagerService,它已经准备就绪进入Paused状态,于是ActivityManagerService就创建一个新的进程,用来启动一个ActivityThread实例,即将要启动的Activity就是在这个ActivityThread实例中运行; 4.ActivityThread通过Binder进程间通信机制将一个ApplicationThread类型的Binder对象传递给ActivityManagerService,以便以后ActivityManagerService能够通过这个Binder对象和它进行通信; 5.ActivityManagerService通过Binder进程间通信机制通知ActivityThread,现在一切准备就绪,它可以真正执行Activity的启动操作了。

抽象类和接口的区别

抽象类 抽象方法必须用abstract关键字进行修饰。如果一个类含有抽象方法,则称这个类为抽象类,抽象类必须在类前用abstract关键字修饰。 抽象类就是为了继承而存在的 如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。

接口

称作interface 接口中的变量会被隐式地指定为public static final变量(并且只能是public static final变量,用private修饰会报编译错误),而方法会被隐式地指定为public abstract方法且只能是public abstract方法(用其他关键字,比如private、protected、static、 final等修饰会报编译错误),并且接口中所有的方法不能有具体的实现,也就是说,接口中的方法必须都是抽象方法。

区别

抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法; 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型; 接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法; 一个类只能继承一个抽象类,而一个类却可以实现多个接口。

webview的优化

www.jianshu.com/p/6179d5128… 为什么拥有Webview的H5页面打开这么慢,是因为它通常会经历以下几个阶段:

1)Webview初始化。 2)到达新的页面,网络连接,从服务器下载html,css,js,页面白屏。 3)页面基本框架出现,js请求页面数据,页面处于loading状态。 4)出现所需的数据,完成整个页面的渲染,用户可交互。

  • WebView在Application中提前初始化
  • WebView有一个setBlockNetworkImage(boolean)方法,该方法的作用是是否屏蔽图片的加载
  • 另开WebView进程
  • DNS解析优化(接口与网页主域名一致)
  • WebView创建与网络请求并行

http和https的区别

blog.csdn.net/cao12619710… www.jianshu.com/p/7a40e874f…

webview加载https注意什么

www.jianshu.com/p/fa290c28b…

tcp/ip协议

www.cnblogs.com/onepixel/p/…

强引用 软引用 弱引用 虚引用

blog.csdn.net/baidu_22254… Java中4种引用的级别和强度由高到低依次为:强引用 -> 软引用 -> 弱引用 -> 虚引用

  • 强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。
  • 如果一个对象只具有软引用,则内存空间充足时,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。
  • 软引用可以和一个引用队列(ReferenceQueue)联合使用。如果软引用所引用对象被垃圾回收,JAVA虚拟机就会把这个软引用加入到与之关联的引用队列中。
  • 弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
  • 同样,弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。可见WeakReference对象的生命周期基本由垃圾回收器决定,一旦垃圾回收线程发现了弱引用对象,在下一次GC过程中就会对其进行回收
  • 虚引用顾名思义,就是形同虚设。与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。

虚引用与软引用和弱引用的一个区别

虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。

安卓各个版本的不同

www.jianshu.com/p/21bd742e3…

集合 hashmap

www.cnblogs.com/chenglc/p/8…

DexClassLoader和PathClassLoader的区别

都是BaseDexClassLoader的子类 1、DexClassLoader可以加载jar/apk/dex,可以从SD卡中加载未安装的apk 2、PathClassLoader只能加载系统中已经安装过的apk