2020年Android高级面试题与相关知识点总结

这些题都是我最近参加各大IT公司笔试后靠记忆记下来的,经过整理献给与我一样参加各大IT校园招聘的同学们,纯考基础功底,花了不少时间整理,在整理过程中也学到了很多东西,请大家认真对待每一题~~~

另外本人整理收藏了20年多家公司面试知识点整理 ,以及各种Java核心知识点免费分享给大家,想要资料的话请点击这里来加入我们的交流分享群 暗号 qf 。
在这里插入图片描述

说下你所知道的设计模式与使用场景

在这里插入图片描述

a.建造者模式:

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
使用场景比如最常见的AlertDialog,拿我们开发过程中举例,比如Camera开发过程中,可能需要设置一个初始化的相机配置,设置摄像头方向,闪光灯开闭,成像质量等等,这种场景下就可以使用建造者模式

装饰者模式:动态的给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。装饰者模式可以在不改变原有类结构的情况下曾强类的功能,比如Java中的BufferedInputStream 包装FileInputStream,举个开发中的例子,比如在我们现有网络框架上需要增加新的功能,那么再包装一层即可,装饰者模式解决了继承存在的一些问题,比如多层继承代码的臃肿,使代码逻辑更清晰

  • 观察者模式
  • 代理模式
  • 门面模式
  • 单例模式
  • 生产者消费者模式

java语言的特点与OOP思想

这个通过对比来描述,比如面向对象和面向过程的对比,针对这两种思想的对比,还可以举个开发中的例子,比如播放器的实现,面向过程的实现方式就是将播放视频的这个功能分解成多个过程,比如,加载视频地址,获取视频信息,初始化解码器,选择合适的解码器进行解码,读取解码后的帧进行视频格式转换和音频重采样,然后读取帧进行播放,这是一个完整的过程,这个过程中不涉及类的概念,而面向对象最大的特点就是类,封装继承和多态是核心,同样的以播放器为例,一面向对象的方式来实现,将会针对每一个功能封装出一个对象,吧如说Muxer,获取视频信息,Decoder,解码,格式转换器,视频播放器,音频播放器等,每一个功能对应一个对象,由这个对象来完成对应的功能,并且遵循单一职责原则,一个对象只做它相关的事情

说下handler原理

Handler,Message,looper和MessageQueue构成了安卓的消息机制,handler创建后可以通过sendMessage将消息加入消息队列,然后looper不断的将消息从MessageQueue中取出来,回调到Hander的handleMessage方法,从而实现线程的通信。

从两种情况来说,第一在UI线程创建Handler,此时我们不需要手动开启looper,因为在应用启动时,在ActivityThread的main方法中就创建了一个当前主线程的looper,并开启了消息队列,消息队列是一个无限循环,为什么无限循环不会ANR?因为可以说,应用的整个生命周期就是运行在这个消息循环中的,安卓是由事件驱动的,Looper.loop不断的接收处理事件,每一个点击触摸或者Activity每一个生命周期都是在Looper.loop的控制之下的,looper.loop一旦结束,应用程序的生命周期也就结束了。我们可以想想什么情况下会发生ANR,第一,事件没有得到处理,第二,事件正在处理,但是没有及时完成,而对事件进行处理的就是looper,所以只能说事件的处理如果阻塞会导致ANR,而不能说looper的无限循环会ANR

另一种情况就是在子线程创建Handler,此时由于这个线程中没有默认开启的消息队列,所以我们需要手动调用looper.prepare(),并通过looper.loop开启消息

主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗。

WebView java js的通信

通过Android原生的方式进行通信

1.Java层调用js方法:

通过WebView的loadUrl():没返回值,得通过js改变iframe.src把结果返回,这样执行效率较低
通过WebView的evaluateJavascript():sdk19(4.4)以上,在回调方法里有返回值,效率优于前一种,因为该方法的执行不会使页面刷新,而方法(loadUrl )的执行则会使页面刷新。
建议两种方法混合使用,即Android 4.4以下使用方法1,Android 4.4以上方法2

//假如js中定义了这个方法,要在java中调用它

//这样调用,callJS就是方法名,javascript:是固定写法
webView.loadUrl(“javascript:callJS()”);
// 只需要将第一种方法的loadUrl()换成下面该方法即可
webView.evaluateJavascript(“javascript:callJS()”, new ValueCallback() {
@Override public void onReceiveValue(String value) {
//此处为 js 返回的结果
}
});
}
2.Js调用Java代码的方法有3种:

  • 通过WebView的addJavascriptInterface()进行对象映射

这种方法在安卓4.2以下存在远程代码调用漏洞,漏洞产生原因是:当JS拿到Android这个对象后,就可以调用这个Android对象中所有的方法,包括系统类(java.lang.Runtime 类),从而进行任意代码执行。

具体获取系统类的描述:(结合 Java 反射机制)

  • Android中的对象有一公共的方法:getClass() ;
  • 该方法可以获取到当前类 类型Class
  • 该类有一关键的方法: Class.forName;
  • 该方法可以加载一个类(可加载 java.lang.Runtime 类)
  • 而该类是可以执行本地命令的

//以下是攻击的Js核心代码:

function execute(cmdArgs)  
{
    
      
    // 步骤1:遍历 window 对象
    // 目的是为了找到包含 getClass ()的对象
    // 因为Android映射的JS对象也在window中,所以肯定会遍历到
    for (var obj in window) {
    
      
        if ("getClass" in window[obj]) {
    
      
        // 步骤2:利用反射调用forName()得到Runtime类对象
        return  window[obj].getClass().forName("java.lang.Runtime") 
                   .getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs);  
        // 步骤3:以后,就可以调用静态方法来执行一些命令,比如访问文件的命令
        // 从执行命令后返回的输入流中得到字符串,有很严重暴露隐私的危险。
        // 如执行完访问文件的命令之后,就可以得到文件名的信息了。
        }  
    }  
}   
a.定义一个与JS对象映射关系的Android类:AndroidtoJs

// 继承自Object类
 public class AndroidtoJs extends Object {
    
     
    // 定义JS需要调用的方法
    // 被JS调用的方法必须加入@JavascriptInterface注解 
    @JavascriptInterface
    public void hello(String msg) {
    
     
        System.out.println("JS调用了Android的hello方法");
     }
 }
b.在Android里通过WebView设置Android类与JS代码的映射
 
WebSettings webSettings = mWebView.getSettings(); 
// 设置与Js交互的权限 
webSettings.setJavaScriptEnabled(true); 
// 通过addJavascriptInterface()将Java对象映射到JS对象
//参数1:Javascript对象名 //参数2:Java对象名,在js中通过test调用hello方法
mWebView.addJavascriptInterface(new AndroidtoJs(), "test");
//AndroidtoJS类对象映射到js的test对象
c.对应的js代码为
 


<!DOCTYPE html> 
<html> 
<head> 
<meta charset="utf-8"> 
<title>Carson</title> 
<script> 
function callAndroid(){
    
     
    // 由于对象映射,所以调用test对象等于调用Android映射的对象 
    test.hello("js调用了android中的hello方法"); 
} 
</script> 
</head> 
<body> //点击按钮则调用callAndroid函数 
<button type="button" id="button1" "callAndroid()"></button>
</body> 
</html>
 -通过 WebViewClient 的shouldOverrideUrlLoading ()方法回调拦截 url,Android通过 WebViewClient 的回调方法shouldOverrideUrlLoading ()拦截 url解析该 url 的协议,如果检测到是预先约定好的协议,就调用相应方法


function callAndroid(){
    
     
    /*约定的url协议为:js://webview?arg1=111&arg2=222*/
    document.location = "js://webview?arg1=111&arg2=222"; 
}
//点击之后执行了callAndroid()方法
<button type="button" id="button1" "callAndroid()">点击调用Android代码</button>
回调到shouldOverrideUrlLocading方法


public boolean shouldOverrideUrlLoading(WebView view, String url) {
    
     
    // 步骤2:根据协议的参数,判断是否是所需要的url
    // 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数) 
    //假定传入进来的 url = "js://webview?arg1=111&arg2=222"(同时也是约定好的需要拦截的)            Uri uri = Uri.parse(url); 
    // 如果url的协议 = 预先约定的 js 协议
     // 就解析往下解析参数 
    if ( uri.getScheme().equals("js")) {
    
     
        // 如果 authority = 预先约定协议里的 webview,即代表都符合约定的协议 
        // 所以拦截url,下面JS开始调用Android需要的方法 
        if (uri.getAuthority().equals("webview")) {
    
     
           // 步骤3: 
            // 执行JS所需要调用的逻辑 
            System.out.println("js调用了Android的方法"); 
        } 
        return true;
    } 
    return super.shouldOverrideUrlLoading(view, url); 
}
如果JS想要得到Android方法的返回值,只能通过 WebView 的 loadUrl ()去执行 JS 方法把返回值传递回去,相关的代码如下


// Android:
MainActivity.java mWebView.loadUrl("javascript:returnResult(" + result + ")"); 
// JS:
javascript.html function returnResult(result){
    
     alert("result is" + result); }
  • 通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS对话框alert()、confirm()、prompt()消息

1.常用的拦截是:拦截 JS的输入框(即prompt()方法)
2.因为只有prompt()可以返回任意类型的值,操作最全面方便、更加灵活;而alert()对话框没有返回值;confirm()对话框只能返回两种状态(确定 / 取消)两个值
3.如果是拦截警告框(即alert()),则触发回调onJsAlert();
4.如果是拦截确认框(即confirm()),则触发回调onJsConfirm();

//点击按钮调用clickprompt方法
<button type="button" id="button1" "clickprompt()">点击调用Android代码</button>

function clickprompt(){
    
    
    // 调用prompt()
    var result=prompt("js://demo?arg1=111&arg2=222");
    alert("demo " + result);
}
mWebView.setWebChromeClient(new WebChromeClient() {
    
     
    // 拦截输入框(原理同方式2) 
    // 参数message:代表promt()的内容(不是url)
    // 参数result:代表输入框的返回值 
    @Override 
    public boolean onJsPrompt(WebView view, String url, String message, String     defaultValue, JsPromptResult result) {
    
     
        // 根据协议的参数,判断是否是所需要的url(原理同方式2) 
        // 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数) 
        //假定传入进来的 url = "js://webview?arg1=111&arg2=222"(同时也是约定好的需要拦截的) 
        Uri uri = Uri.parse(message); 
        // 如果url的协议 = 预先约定的 js 协议 
        // 就解析往下解析参数 
        if ( uri.getScheme().equals("js")) {
    
     
            // 如果 authority = 预先约定协议里的 webview,即代表都符合约定的协议 
            // 所以拦截url,下面JS开始调用Android需要的方法
            if (uri.getAuthority().equals("webview")) {
    
     
                // // 执行JS所需要调用的逻辑 System.out.println("js调用了Android的方法"); 
                // 可以在协议上带有参数并传递到Android上 HashMap<String, String> params = new HashMap<>(); 
                Set<String> collection = uri.getQueryParameterNames(); 
                //参数result:代表消息框的返回值(输入值) 
                result.confirm("js调用了Android的方法成功啦"); 
            } 
            return true;
        } 
        return super.onJsPrompt(view, url, message, defaultValue, result); 
}

说下Activity的启动模式,生命周期,两个Activity跳转的生命周期,如果一个Activity跳转另一个Activity再按下Home键在回到Activity的生命周期是什么样的
在这里插入图片描述

启动模式

Standard模式:Activity可以有多个实例,每次启动Activity,无论任务栈中是否已经有这个Activity的实例,系统都会创建一个新的Activity实例
SingleTop模式:当一个singleTop模式的Activity已经位于任务栈的栈顶,再去启动它时,不会再创建新的实例,如果不位于栈顶,就会创建新的实例
SingleTask模式:如果Activity已经位于栈顶,系统不会创建新的Activity实例,和singleTop模式一样。但Activity已经存在但不位于栈顶时,系统就会把该Activity移到栈顶,并把它上面的activity出栈
SingleInstance模式:singleInstance模式也是单例的,但和singleTask不同,singleTask只是任务栈内单例,系统里是可以有多个singleTask Activity实例的,而singleInstance Activity在整个系统里只有一个实例,启动一singleInstanceActivity时,系统会创建一个新的任务栈,并且这个任务栈只有他一个Activity

生命周期

onCreate onStart onResume onPause onStop onDestroy

两个Activity跳转的生命周期

1.启动A

onCreate - onStart - onResume

2.在A中启动B

ActivityA onPause
ActivityB onCreate
ActivityB onStart
ActivityB onResume
ActivityA onStop

3.从B中返回A(按物理硬件返回键)

ActivityB onPause
ActivityA onRestart
ActivityA onStart
ActivityA onResume
ActivityB onStop
ActivityB onDestroy

4.继续返回

ActivityA onPause
ActivityA onStop
ActivityA onDestroy

onRestart的调用场景

(1)按下home键之后,然后切换回来,会调用onRestart()。
(2)从本Activity跳转到另一个Activity之后,按back键返回原来Activity,会调用onRestart();
(3)从本Activity切换到其他的应用,然后再从其他应用切换回来,会调用onRestart();

说下Activity的横竖屏的切换的生命周期,用那个方法来保存数据,两者的区别。触发在什么时候在那个方法里可以获取数据等。

如何实现进程保活

a: Service设置成START_STICKY kill 后会被重启(等待5秒左右),重传Intent,保持与重启前一样
b: 通过 startForeground将进程设置为前台进程, 做前台服务,优先级和前台应用一个级别,除非在系统内存非常缺,否则此进程不会被 kill
c: 双进程Service: 让2个进程互相保护对方,其中一个Service被清理后,另外没被清理的进程可以立即重启进程
d: 用C编写守护进程(即子进程) : Android系统中当前进程(Process)fork出来的子进程,被系统认为是两个不同的进程。当父进程被杀死的时候,子进程仍然可以存活,并不受影响(Android5.0以上的版本不可行)联系厂商,加入白名单
e.锁屏状态下,开启一个一像素Activity

说下冷启动与热启动是什么,区别,如何优化,使用场景等。

app冷启动: 当应用启动时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用, 这个启动方式就叫做冷启动(后台不存在该应用进程)。冷启动因为系统会重新创建一个新的进程分配给它,所以会先创建和初始化Application类,再创建和初始化MainActivity类(包括一系列的测量、布局、绘制),最后显示在界面上。

  • app热启动: 当应用已经被打开, 但是被按下返回键、Home键等按键时回到桌面或者是其他程序的时候,再重新打开该app时, 这个方式叫做热启动(后台已经存在该应用进程)。热启动因为会从已有的进程中来启动,所以热启动就不会走Application这步了,而是直接走MainActivity(包括一系列的测量、布局、绘制),所以热启动的过程只需要创建和初始化一个MainActivity就行了,而不必创建和初始化Application

冷启动的流程

当点击app的启动图标时,安卓系统会从Zygote进程中fork创建出一个新的进程分配给该应用,之后会依次创建和初始化Application类、创建MainActivity类、加载主题样式Theme中的windowBackground等属性设置给MainActivity以及配置Activity层级上的一些属性、再inflate布局、当onCreate/onStart/onResume方法都走完了后最后才进行contentView的measure/layout/draw显示在界面上

冷启动的生命周期简要流程:

Application构造方法 –> attachBaseContext()–>onCreate –>Activity构造方法 –> onCreate() –> 配置主体中的背景等操作 –>onStart() –> onResume() –> 测量、布局、绘制显示

冷启动的优化主要是视觉上的优化,解决白屏问题,提高用户体验,所以通过上面冷启动的过程。能做的优化如下:

  • 减少onCreate()方法的工作量

  • 不要让Application参与业务的操作

  • 不要在Application进行耗时操作

  • 不要以静态变量的方式在Application保存数据

  • 减少布局的复杂度和层级

  • 减少主线程耗时
    为什么冷启动会有白屏黑屏问题?
    在这里插入图片描述

原因在于加载主题样式Theme中的windowBackground等属性设置给MainActivity发生在inflate布局当onCreate/onStart/onResume方法之前,而windowBackground背景被设置成了白色或者黑色,所以我们进入app的第一个界面的时候会造成先白屏或黑屏一下再进入界面。解决思路如下

1.给他设置windowBackground背景跟启动页的背景相同,如果你的启动页是张图片那么可以直接给windowBackground这个属性设置该图片那么就不会有一闪的效果了

<style name=``"Splash_Theme"` `parent=``"@android:style/Theme.NoTitleBar"``>`
    <item name=``"android:windowBackground"``>@drawable/splash_bg</item>`
    <item name=``"android:windowNoTitle"``>``true``</item>`
</style>`

2.采用世面的处理方法,设置背景是透明的,给人一种延迟启动的感觉。,将背景颜色设置为透明色,这样当用户点击桌面APP图片的时候,并不会"立即"进入APP,而且在桌面上停留一会,其实这时候APP已经是启动的了,只是我们心机的把Theme里的windowBackground的颜色设置成透明的,强行把锅甩给了手机应用厂商(手机反应太慢了啦)

<style name=``"Splash_Theme"` `parent=``"@android:style/Theme.NoTitleBar"``>`
    <item name=``"android:windowIsTranslucent"``>``true``</item>`
    <item name=``"android:windowNoTitle"``>``true``</item>`
</style>`

3.以上两种方法是在视觉上显得更快,但其实只是一种表象,让应用启动的更快,有一种思路,将Application中的不必要的初始化动作实现懒加载,比如,在SpashActivity显示后再发送消息到Application,去初始化,这样可以将初始化的动作放在后边,缩短应用启动到用户看到界面的时

ANR的原因

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

三级缓存原理

当Android端需要获得数据时比如获取网络中的图片,首先从内存中查找(按键查找),内存中没有的再从磁盘文件或sqlite中去查找,若磁盘中也没有才通过网络获取

jvm,jre以及jdk三者之间的关系?JDK(Java Development Kit)是针对Java开发员的产品,是整个Java的核心,包括了Java运行环境JRE、Java工具和Java基础类库。
Java Runtime Environment(JRE)是运行JAVA程序所必须的环境的集合,包含JVM标准实现及Java核心类库。
JVM是Java Virtual Machine(Java虚拟机)的缩写,是整个java实现跨平台的最核心的部分,能够运行以Java语言写作的软件程序。

谈谈你对 JNIEnv 和 JavaVM 理解?

JavaVm
JavaVM 是虚拟机在 JNI 层的代表,一个进程只有一个 JavaVM,所有的线程共用一个 JavaVM。

JNIEnv
JNIEnv 表示 Java 调用 native 语言的环境,是一个封装了几乎全部 JNI 方法的指针。
JNIEnv 只在创建它的线程生效,不能跨线程传递,不同线程的 JNIEnv 彼此独立。
native 环境中创建的线程,如果需要访问 JNI,必须要调用 AttachCurrentThread 关联,并使用 DetachCurrentThread 解除链接。

Serializable与Parcable的区别?

  • Serializable (java 自带)

方法:对象继承 Serializable类即可实现序列化,就是这么简单,也是它最吸引我们的地方

  • Parcelable(Android专用):Parcelable方式的实现原理是将一个完整的对象进行分解,用起来比较麻烦

1)在使用内存的时候,Parcelable比Serializable性能高,所以推荐使用Parcelable。

2)Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC。

3)Parcelable不能使用在要将数据存储在磁盘上的情况,因为Parcelable不能很好的保证数据的持续性,在外界有变化的情况下。尽管Serializable效率低点,但此时还是建议使用Serializable 。

4)android上应该尽量采用Parcelable,效率至上,效率远高于Serializable

宝马面试题

  • Handler消息机制。
  • 自定义view步骤,如何自定义属性。
  • 布局的xml文件中 merge、include、viewstub关键字的使用。
  • MVP和MVC的区别。
  • DataBinding的使用。
  • 谷歌最新技术的关注度?
  • 自定义view中的onDraw()方法中,canvas和paint的api的功能。以及刷新功能的使用。
  • Synchronized关键字的各种使用。
  • 如何绘制一个带圆角的箭头?你的实现思路是什么?

Java中Exception和Error的区别

  • 1.Error类一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢等
  • 2.Exception类表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。Exception又分为可检查异常和不可检查异常,可检查异常在源代码里必须显示的进行捕获处理,这是编译期检查的一部分。不可检查异常就是所谓的运行时异常,类似NullPointerException、ArrayIndexOutOfBoundsException之类,通常是可以编码避免的逻辑错误,具体可以根据需要来判断是否需要捕获,并不会在编译期强制要求
  • 3…exception和error都是继承了throwable类,在java中只有throwable类型的实例才可以被抛出(throw)或者捕获(catch),它是异常处理机制的基本组成类型
  • 4.exception和error体现了java平台设计者对不同异常情况的分类。exception是程序正常运行中,可以预料的意外情况,并且应该被捕获,进行相应的处理

Android单线程模型

  • Android单线程模型的核心原则就是:只能在UI线程(Main Thread)中对UI进行处理。当一个程序第一次启动时,Android会同时启动一个对应的 主线程(Main Thread),主线程主要负责处理与UI相关的事件,如:用户的按键事件,用户接触屏幕的事件以及屏幕绘图事 件,并把相关的事件分发到对应的组件进行处理。所以主线程通常又被叫做UI线 程。在开发Android应用时必须遵守单线程模型的原则: Android UI操作并不是线程安全的并且这些操作必须在UI线程中执行。

Android的单线程模型有两条原则:

  • 不要阻塞UI线程。

  • 不要在UI线程之外访问Android UI toolkit(主要是这两个包中的组件:android.widget and android.view

RecyclerView在很多方面能取代ListView,Google为什么没把ListView划上一条过时的横线?
ListView采用的是RecyclerBin的回收机制在一些轻量级的List显示时效率更高。

App启动流程

App启动时,AMS会检查这个应用程序所需要的进程是否存在,不存在就会请求Zygote进程启动需要的应用程序进程,Zygote进程接收到AMS请求并通过fock自身创建应用程序进程,这样应用程序进程就会获取虚拟机的实例,还会创建Binder线程池(ProcessState.startThreadPool())和消息循环(ActivityThread looper.loop),然后App进程,通过Binder IPC向sytem_server进程发起attachApplication请求;system_server进程在收到请求后,进行一系列准备工作后,再通过Binder IPC向App进程发送scheduleLaunchActivity请求;App进程的binder线程(ApplicationThread)在收到请求后,通过handler向主线程发送LAUNCH_ACTIVITY消息;主线程在收到Message后,通过反射机制创建目标Activity,并回调Activity.onCreate()等方法。到此,App便正式启动,开始进入Activity生命周期,执行完onCreate/onStart/onResume方法,UI渲染结束后便可以看到App的主界面。

双亲委托模式

类加载器查找class所采用的是双亲委托模式,所谓双亲委托模式就是判断该类是否已经加载,如果没有则不是自身去查找而是委托给父加载器进行查找,这样依次进行递归,直到委托到最顶层的Bootstrap ClassLoader,如果Bootstrap ClassLoader找到了该Class,就会直接返回,如果没找到,则继续依次向下查找,如果还没找到则最后交给自身去查找

双亲委托模式的好处:

  • 避免重复加载,如果已经加载过一次Class,则不需要再次加载,而是直接读取已经加载的Class
  • 更加安全,确保,java核心api中定义类型不会被随意替换,比如,采用双亲委托模式可以使得系统在Java虚拟机启动时旧加载了String类,也就无法用自定义的String类来替换系统的String类,这样便可以防止核心API库被随意篡改。

什么情况下会触发类的初始化

  • 遇到new,getstatic,putstatic,invokestatic这4条指令;
  • 使用java.lang.reflect包的方法对类进行反射调用;
  • 初始化一个类的时候,如果发现其父类没有进行过初始化,则先初始化其父类(注意!如果其父类是接口的话,则不要求初始化父类);
  • 当虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的那个类),虚拟机会先初始化这个主类;
  • 当使用jdk1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getstatic REF_putstatic,REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则先触发其类初始化;

类的加载过程

类加载过程主要包含加载、验证、准备、解析、初始化、使用、卸载七个方面,下面一一阐述。

  • 加载:获取定义此类的二进制字节流,生成这个类的java.lang.Class对象

  • 验证:保证Class文件的字节流包含的信息符合JVM规范,不会给JVM造成危害

  • 准备:准备阶段为变量分配内存并设置类变量的初始化

  • 解析:解析过程是将常量池内的符号引用替换成直接引用

  • 初始化:不同于准备阶段,本次初始化,是根据程序员通过程序制定的计划去初始化类的变量和其他资源。这些资源有static{}块,构造函数,父类的初始化等

  • 使用:使用过程就是根据程序定义的行为执行

  • 卸载:卸载由GC完成。

  • 三次握手时最后一次客户端收到服务端SYN+ACK包时,发送确认信息给服务端,此时服务端没有收到,那么两端都处于什么状态
    当第三次握手失败时的处理操作,可以看出当失败时服务器并不会重传ack报文,而是直接发送RTS报文段,进入CLOSED状态。这样做的目的是为了防止SYN洪泛攻击。

  • BLocked和Waiting状态的区别

  • wait的实现机制

  • C++重载的原理

消息队列中的消息处理完之后,Looper进入组赛状态,那么此时我想进行一次垃圾回收,能不能做到?

可以。通过IdleHandler实现,IdleHandler即在looper里面的message处理完了的时候去调用
IdleHandler源码

/**
 * Callback interface for discovering when a thread is going to block
 * waiting for more messages.消息队列进入组赛状态后会执行
 */
 public static interface IdleHandler {
    
    
 /**
 * Called when the message queue has run out of messages and will now
 * wait for more. Return true to keep your idle handler active, false
 * to have it removed. This may be called if there are still messages
 * pending in the queue, but they are all scheduled to be dispatched
 * after the current time.
 *返回值为true,则保持此Idle一直在Handler中,一直运行,否则,执行一次后就从Handler线程中remove掉,只执行一次。
 */
 boolean queueIdle();
 }

系统源码中IdleHandler的使用

void scheduleGcIdler() {
    
    
        if (!mGcIdlerScheduled) {
    
    
            mGcIdlerScheduled = true;
            Looper.myQueue().addIdleHandler(mGcIdler);
        }
        mH.removeMessages(H.GC_WHEN_IDLE);
    }

这个方法的调用发生在ActivityThread的Handler handleMessage中,可以看到,gc是在主线程消息队列阻塞状态(等待新的消息或者消息执行完成)时进行的,并且每次执行gc有一个最小时间间隔5x1000

public void handleMessage(Message msg) {
    
    
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
    
    
                ......
                case GC_WHEN_IDLE:
                    scheduleGcIdler();
                    break;

void scheduleGcIdler() {
    
    
        if (!mGcIdlerScheduled) {
    
    
            mGcIdlerScheduled = true;
            Looper.myQueue().addIdleHandler(mGcIdler);
        }
        mH.removeMessages(H.GC_WHEN_IDLE);
    }
final class GcIdler implements MessageQueue.IdleHandler {
    
    
        @Override
        public final boolean queueIdle() {
    
    
            doGcIfNeeded();
            return false;
        }
    }
void doGcIfNeeded() {
    
    
        mGcIdlerScheduled = false;
        final long now = SystemClock.uptimeMillis();
        //Slog.i(TAG, "**** WE MIGHT WANT TO GC: then=" + Binder.getLastGcTime()
        //        + "m now=" + now);
        //private static final long MIN_TIME_BETWEEN_GCS = 5*1000;
        if ((BinderInternal.getLastGcTime()+MIN_TIME_BETWEEN_GCS) < now) {
    
    
            //Slog.i(TAG, "**** WE DO, WE DO WANT TO GC!");
            BinderInternal.forceGc("bg");
        }
    }

Handler消息的优先级-同步屏障机制(sync barrier)

同步屏障可以通过MessageQueue.postSyncBarrier函数来设置。该方法发送了一个没有target的Message到Queue中,在next方法中获取消息时,如果发现没有target的Message,则在一定的时间内跳过同步消息,优先执行异步消息。再换句话说,同步屏障为Handler消息机制增加了一种简单的优先级机制,异步消息的优先级要高于同步消息。在创建Handler时有一个async参数,传true表示此handler发送的时异步消息。ViewRootImpl.scheduleTraversals方法就使用了同步屏障,保证UI绘制优先执行。

Linux管道机制

阻塞后的Looper如何再次被激活

Java中的泛型是什么 ? 使用泛型的好处是什么?

在集合中存储对象并在使用前进行类型转换不方便。泛型防止了那种情况的发生。它提供了编译期的类型安全,确保你只能把正确类型的对象放入 集合中,避免了在运行时出现ClassCastException。

Java的泛型是如何工作的 ? 什么是类型擦除 ?

Java源代码里面类型提供实现泛型功能,而编译后Class文件类型就变成原生类型(即类型被擦除掉),而在引用处插入强制类型转换以实现JVM对泛型的支持。本质是Java泛型只是Java提供的一个语法糖,底层
的JVM并不提供支持,Java中的泛型属于伪泛型。
但是编译后的字节码通过反射后还是可以获取到泛型的真实类型信息,因为泛型擦除并没有把保存泛型元数据擦除掉。
泛型是通过类型擦除来实现的,编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。例如 List在运行时仅用一个List来表示。这样做的目的,是确保能和Java 5之前的版本开发二进制类库进行兼容。你无法在运行时访问到类型参数,因为编译器已经把泛型类型转换成了原始类型。根据你对这个泛型问题的回答情况,你会 得到一些后续提问,比如为什么泛型是由类型擦除来实现的或者给你展示一些会导致编译器出错的错误泛型代码。

什么是泛型中的限定通配符和非限定通配符 ?

这是另一个非常流行的Java泛型面试题。限定通配符对类型进行了限制。有两种限定通配符,一种是<? extends T>它通过确保类型必须是T的子类来设定类型的上界,另一种是<? super T>它通过确保类型必须是T的父类来设定类型的下界。泛型类型必须用限定内的类型来进行初始化,否则会导致编译错误。另一方面<?>表 示了非限定通配符,因为<?>可以用任意类型来替代。

List<? extends T>和List <? super T>之间有什么区别 ?

这和上一个面试题有联系,有时面试官会用这个问题来评估你对泛型的理解,而不是直接问你什么是限定通配符和非限定通配符。这两个List的声明都是 限定通配符的例子,List<? extends T>可以接受任何继承自T的类型的List,而List<? super T>可以接受任何T的父类构成的List。例如List<? extends Number>可以接受List或List。在本段出现的连接中可以找到更多信息。

为什么Java泛型是伪泛型

Java 的泛型是伪泛型, 也就是骗骗编译器的。运行期的泛型类型,被擦除了,因此,在运行期,ArrayList 和 ArrayList 是相同的类型。Java源代码里面类型提供实现泛型功能,而编译后Class文件类型就变成原生类型(即类型被擦除掉),而在引用处插入强制类型转换以实现JVM对泛型的支持。本质是Java泛型只是Java提供的一个语法糖,底层
的JVM并不提供支持,Java中的泛型属于伪泛型。

文末

Android一词的本义指“机器人”,同时也是Google于2007年11月5日宣布的基于Linux平台的开源手机操作系统的名称,该平台由操作系统、中间件、用户界面和应用软件组成。
另外本人整理收藏了20年多家公司面试知识点整理 ,以及各种Java核心知识点免费分享给大家,想要资料的话请点击这里暗号 qf 。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/SpringBoot_/article/details/108870572