写点什么

2021 年尾 Android 面试之必问高级知识点(包含答案),kotlin 语法大全

用户头像
Android架构
关注
发布于: 刚刚

以下知识点是节选自本人整理的 Android 知识点汇总,如需要 PDF 版可点击这里:PS(均无收费内容,点击直达) ------------------------------------------------------- [Github![icon-default.png?t=L9C2](https://static001.geekbang.org/infoq/52/52ef3549afec661c0a03661ed724e7bf.png)https://github.com/hunanmaniu/AndroidNotes](https://github.com/hunanmaniu/AndroidNotes) 1.2 AOT 优点 --------- 下面是 AOT 编译方式的一些优点: ### 1.2.1 预先编译 ART 引入了预先编译机制,可提高应用的性能。ART 还具有比 Dalvik 更严格的安装时验证。在安装时,ART 使用设备自带的 dex2oat 工具来编译应用。该实用工具接受 DEX 文件作为输入,并为目标设备生成经过编译的应用可执行文件,该工具能够顺利编译所有有效的 DEX 文件。 ### 1.2.2 垃圾回收优化 垃圾回收 (GC) 可能有损于应用性能,从而导致显示不稳定、界面响应速度缓慢以及其他问题。ART 模式从以下几个方面优化了垃圾回收的策略: * 只有一次(而非两次)GC 暂停 * 在 GC 保持暂停状态期间并行处理 * 在清理最近分配的短时对象这种特殊情况中,回收器的总 GC 时间更短 * 优化了垃圾回收的工效,能够更加及时地进行并行垃圾回收,这使得 GC\_FOR\_ALLOC 事件在典型用例中极为罕见 * 压缩 GC 以减少后台内存使用和碎片 ### 1.2.3 开发和调试方面的优化 **支持采样分析器**?一直以来,开发者都使用 Traceview 工具(用于跟踪应用执行情况)作为分析器。虽然 Traceview 可提供有用的信息,但每次方法调用产生的开销会导致 Dalvik 分析结果出现偏差,而且使用该工具明显会影响运行时性能 ART 添加了对没有这些限制的专用采样分析器的支持,因而可更准确地了解应用执行情况,而不会明显减慢速度。支持的版本从 KitKat (4.4)版本开始,为 Dalvik 的 Traceview 添加了采样支持。 **支持更多调试功能**?ART 支持许多新的调试选项,特别是与监控和垃圾回收相关的功能。例如,查看堆栈跟踪中保留了哪些锁,然后跳转到持有锁的线程;询问指定类的当前活动的实例数、请求查看实例,以及查看使对象保持有效状态的参考;过滤特定实例的事件(如断点)等。 **优化了异常和崩溃报告中的诊断详细信息**?当发生运行时异常时,ART 会为您提供尽可能多的上下文和详细信息。 ART 会提供 java.lang.ClassCastException、java.lang.ClassNotFoundException 和 java.lang.NullPointerException 的更多异常详细信息(较高版本的 Dalvik 会提供 java.lang.ArrayIndexOutOfBoundsException 和 java.lang.ArrayStoreException 的更多异常详细信息,这些信息现在包括数组大小和越界偏移量;ART 也提供这类信息)。 1.3 垃圾回收 -------- ART 提供了多个不同的 GC 方案,这些方案运行着不同垃圾回收器,默认的 GC 方案是 CMS(并发标记清除),主要使用粘性 CMS 和部分 CMS。粘性 CMS 是 ART 的不移动分代垃圾回收器。它仅扫描堆中自上次 GC 后修改的部分,并且只能回收自上次 GC 后分配的对象。除 CMS 方案外,当应用将进程状态更改为察觉不到卡顿的进程状态(例如,后台或缓存)时,ART 将执行堆压缩。 除了新的垃圾回收器之外,ART 还引入了一种基于位图的新内存分配程序,称为 RosAlloc(插槽运行分配器)。此新分配器具有分片锁,当分配规模较小时可添加线程的本地缓冲区,因而性能优于 DlMalloc(内存分配器)。 同时,与 Dalvik 相比,ART 的 CMS 垃圾回收也带来了其他方面的改善,如下: * 与 Dalvik 相比,暂停次数从 2 次减少到 1 次。Dalvik 的第一次暂停主要是为了进行根标记,即在 ART 中进行并发标记,让线程标记自己的根,然后马上恢复运行。 * 与 Dalvik 类似,ART GC 在清除过程开始之前也会暂停 1 次。两者在这方面的主要差异在于:在此暂停期间,某些 Dalvik 环节在 ART 中并发进行。这些环节包括 java.lang.ref.Reference 处理、系统弱清除(例如,jni 弱全局等)、重新标记非线程根和卡片预清理。在 ART 暂停期间仍进行的阶段包括扫描脏卡片以及重新标记线程根,这些操作有助于缩短暂停时间。 * 相对于 Dalvik,ART GC 改进的最后一个方面是粘性 CMS 回收器增加了 GC 吞吐量。不同于普通的分代 GC,粘性 CMS 不移动。系统会将年轻对象保存在一个分配堆栈(基本上是 java.lang.Object 数组)中,而非为其设置一个专属区域。这样可以避免移动所需的对象以维持低暂停次数,但缺点是容易在堆栈中加入大量复杂对象图像而使堆栈变长。 ART GC 与 Dalvik 的另一个主要区别在于 ART GC 引入了移动垃圾回收器。使用移动 GC 的目的在于通过堆压缩来减少后台应用使用的内存。目前,触发堆压缩的事件是 ActivityManager 进程状态的改变。当应用转到后台运行时,它会通知 ART 已进入不再“感知”卡顿的进程状态。此时 ART 会进行一些操作(例如,压缩和监视器压缩),从而导致应用线程长时间暂停。 目前,Android 的 ART 正在使用的两个移动 GC 是同构空间压缩和半空间压缩,它们的区别如下: * **半空间压缩**:将对象在两个紧密排列的碰撞指针空间之间进行移动。这种移动 GC 适用于小内存设备,因为它可以比同构空间压缩稍微多节省一点内存,额外节省出的空间主要来自紧密排列的对象,这样可以避免 RosAlloc/DlMalloc 分配器占用开销。 * 同构空间压缩通过将对象从一个 RosAlloc 空间复制到另一个 RosAlloc 空间来实现。这有助于通过减少堆碎片来减少内存使用量。这是目前非低内存设备的默认压缩模式。相比半空间压缩,同构空间压缩的主要优势在于应用从后台切换到前台时无需进行堆转换。 2,类加载器 ------ 2.1 类加载器分类 ---------- 目前,Android 的类加载器从下到上主要分为 BootstrapClassLoader(根类加载器)、 ExtensionClassLoader (扩展类加载器)和 AppClassLoader(应用类加载器)三种。 * **根类加载器**:该加载器没有父加载器。它负责加载虚拟机的核心类库,如 java.lang.\*等。例如 java.lang.Object 就是由根类加载器加载的。根类加载器从系统属性 sun.boot.class.path 所指定的目录中加载类库。根类加载器的实现依赖于底层操作系统,属于虚拟机的实现的一部分,它并没有继承 java.lang.ClassLoader 类。 * **扩展类加载器**:它的父加载器为根类加载器。它从 java.ext.dirs 系统属性所指定的目录中加载类库,或者从 JDK 的安装目录的 jre/lib/ext 子目录(扩展目录)下加载类库,如果把用户创建的 JAR 文件放在这个目录下,也会自动由扩展类加载器加载。扩展类加载器是纯 Java 类,是 java.lang.ClassLoader 类的子类。 * **系统类加载器**:也称为应用类加载器,它的父加载器为扩展类加载器。它从环境变量 classpath 或者系统属性 java.class.path 所指定的目录中加载类,它是用户自定义的类加载器的默认父加载器。系统类加载器是纯 Java 类,是 java.lang.ClassLoader 类的子类。 父子加载器并非继承关系,也就是说子加载器不一定是继承了父加载器。 2.2 双亲委托模式 ---------- 所谓双亲委托模式,指的是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才 ``` 《Android 学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》 浏览器打开:qq.cn.hn/FTe 免费领取 ``` 自己去加载。 因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子 ClassLoader 再加载一次。如果不使用这种委托模式,那我们就可以随时使用自定义的类来动态替代一些核心的类,存在非常大的安全隐患。 举个例子,事实上,java.lang.String 这个类并不会被我们自定义的 classloader 加载,而是由 bootstrap classloader 进行加载,为什么会这样?实际上这就是双亲委托模式的原因,因为在任何一个自定义 ClassLoader 加载一个类之前,它都会先 委托它的父亲 ClassLoader 进行加载,只有当父亲 ClassLoader 无法加载成功后,才会由自己加载。 2.3 Android 的类加载器 ---------------- 下面是 Android 类加载器的模型图: ![](https://static001.geekbang.org/infoq/a8/a8a3b08021d3d74299cb777543bde6f8.jpeg) 下面看一下 DexClassLoader,DexClassLoader 重载了 findClass 方法,在加载类时会调用其内部的 DexPathList 去加载。DexPathList 是在构造 DexClassLoader 时生成的,其内部包含了 DexFile,涉及的源码如下。 ··· public Class findClass(String name) { for (Element element : dexElements) { DexFile dex = element.dexFile; if (dex != null) { Class clazz = dex.loadClassBinaryName(name, definingContext); if (clazz != null) { return clazz; } } } return null; } ··· 3,Android Hook -------------- 所谓 Hook,就是在程序执行的过程中去截取其中的某段信息,示意图如下。 ![](https://static001.geekbang.org/infoq/d4/d4de2d677efb05ee4fa55589a3c87bd9.jpeg) Android 的 Hook 大体的流程可以分为如下几步: 1、根据需求确定需要 hook 的对象? 2、寻找要 hook 的对象的持有者,拿到需要 hook 的对象? 3、定义“要 hook 的对象”的代理类,并且创建该类的对象? 4、使用上一步创建出来的对象,替换掉要 hook 的对象 下面是一段简单的 Hook 的示例代码,用到了 Java 的反射机制。 @SuppressLint({"DiscouragedPrivateApi", "PrivateApi"}) public static void hook(Context context, final View view) {// try { // 反射执行 View 类的 getListenerInfo()方法,拿到 v 的 mListenerInfo 对象,这个对象就是点击事件的持有者 Method method = View.class.getDeclaredMethod("getListenerInfo"); method.setAccessible(true);//由于 getListenerInfo()方法并不是 public 的,所以要加这个代码来保证访问权限 Object mListenerInfo = method.invoke(view);//这里拿到的就是 mListenerInfo 对象,也就是点击事件的持有者 // 要从这里面拿到当前的点击事件对象 Class<?> listenerInfoClz = Class.forName("android.view.View$ListenerInfo");// 这是内部类的表示方法 Field field = listenerInfoClz.getDeclaredField("mOnClickListener"); final View.OnClickListener onClickListenerInstance = (View.OnClickListener) field.get(mListenerInfo);//取得真实的 mOnClickListener 对象 // 2\. 创建我们自己的点击事件代理类 // 方式 1:自己创建代理类 // ProxyOnClickListener proxyOnClickListener = new ProxyOnClickListener(onClickListenerInstance); // 方式 2:由于 View.OnClickListener 是一个接口,所以可以直接用动态代理模式 // Proxy.newProxyInstance 的 3 个参数依次分别是: // 本地的类加载器; // 代理类的对象所继承的接口(用 Class 数组表示,支持多个接口) // 代理类的实际逻辑,封装在 new 出来的 InvocationHandler 内 Object proxyOnClickListener = Proxy.newProxyInstance(context.getClass().getClassLoader(), new Class[]{View.OnClickListener.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Log.d("HookSetOnClickListener", "点击事件被 hook 到了");//加入自己的逻辑 return method.invoke(onClickListenerInstance, args);//执行被代理的对象的逻辑 } }); // 3\. 用我们自己的点击事件代理类,设置到"持有者"中 field.set(mListenerInfo, proxyOnClickListener); } catch (Exception e) { e.printStackTrace(); } } // 自定义代理类 static class ProxyOnClickListener implements View.OnClickListener { View.OnClickListener oriLis; public ProxyOnClickListener(View.OnClickListener oriLis) { this.oriLis = oriLis; } @Override public void onClick(View v) { Log.d("HookSetOnClickListener", "点击事件被 hook 到了"); if (oriLis != null) { oriLis.onClick(v); } } } 而在 Android 开发中,想要实现 Hook,肯定是没有这么简单的,我们需要借助一些 Hook 框架,比如 Xposed、Cydia Substrate、Legend 等。 4,代码混淆 ------ 4.1 Proguard ------------ 众所周知,Java 代码是非常容易反编译的,为了更好的保护 Java 源代码,我们往往会对编译好的 Class 类文件进行混淆处理。而 ProGuard 就是一个混淆代码的开源项目。它的主要作用就是混淆,当然它还能对字节码进行缩减体积、优化等,但那些对于我们来说都算是次要的功能。 具体来说,ProGuard 具有如下功能: * 压缩(Shrink): 检测和删除没有使用的类,字段,方法和特性。 * 优化(Optimize) : 分析和优化 Java 字节码。 * 混淆(Obfuscate): 使用简短的无意义的名称,对类,字段和方法进行重命名。 在 Android 开发中,开启混淆需要将 app/build.gradle 文件下的 minifyEnabled 属性设置为 true,如下所示。 minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguard-android.txt 是 Android 提供的默认混淆配置文件,我们需要的混淆的规则都放在这个文件中。 4.2 混淆规则 -------- **混淆命令** * keep:保留类和类中的成员,防止被混淆或移除 * keepnames:保留类和类中的成员,防止被混淆,成员没有被引用会被移除 * keepclassmembers:只保留类中的成员,防止被混淆或移除 * keepclassmembernames:只保留类中的成员,防止被混淆,成员没有引用会被移除 * keepclasseswithmembers:保留类和类中的成员,防止被混淆或移除,保留指明的成员 * keepclasseswithmembernames:保留类和类中的成员,防止被混淆,保留指明的成员,成员没有引用会被移除 **混淆通配符** * `<field>`:匹配类中的所有字段 * `<method>`:匹配类中所有的方法 * `<init>`:匹配类中所有的构造函数 * `*`: 匹配任意长度字符,不包含包名分隔符(.) * `**`: 匹配任意长度字符,包含包名分隔符(.) * `***`: 匹配任意参数类型 keep 的规则的格式如下: [keep 命令] [类] { [成员] } 我收录了更多 Android 开发面试题,可在这里获得,开源利好: ------------------------------- Android 学习文档以及教学视频 4.3 混淆模版 -------- ProGuard 中有些公共的模版是可以复用的,比如压缩比、大小写混合和一些系统提供的 Activity、Service 不能混淆等。 # 代码混淆压缩比,在 0~7 之间,默认为 5,一般不做修改 -optimizationpasses 5 # 混合时不使用大小写混合,混合后的类名为小写 -dontusemixedcaseclassnames # 指定不去忽略非公共库的类 -dontskipnonpubliclibraryclasses # 这句话能够使我们的项目混淆后产生映射文件 # 包含有类名->混淆后类名的映射关系 -verbose # 指定不去忽略非公共库的类成员 -dontskipnonpubliclibraryclassmembers # 不做预校验,preverify 是 proguard 的四个步骤之一,Android 不需要 preverify,去掉这一步能够加快混淆速度。 -dontpreverify # 保留 Annotation 不混淆 -keepattributes *Annotation*,InnerClasses # 避免混淆泛型 -keepattributes Signature # 抛出异常时保留代码行号 -keepattributes SourceFile,LineNumberTable # 指定混淆是采用的算法,后面的参数是一个过滤器 # 这个过滤器是谷歌推荐的算法,一般不做更改 -optimizations !code/simplification/cast,!field/*,!class/merging/* ############################################# # # Android 开发中一些需要保留的公共部分 # ############################################# # 保留我们使用的四大组件,自定义的 Application 等等这些类不被混淆 # 因为这些子类都有可能被外部调用 -keep public class * extends android.app.Activity -keep public class * extends android.app.Appliction -keep public class * extends android.app.Service -keep public class * extends android.content.BroadcastReceiver -keep public class * extends android.content.ContentProvider -keep public class * extends android.app.backup.BackupAgentHelper -keep public class * extends android.preference.Preference -keep public class * extends android.view.View -keep public class com.android.vending.licensing.ILicensingService # 保留 support 下的所有类及其内部类 -keep class android.support.** { *; } # 保留继承的 -keep public class * extends android.support.v4.** -keep public class * extends android.support.v7.** -keep public class * extends android.support.annotation.** # 保留 R 下面的资源 -keep class **.R$* { *; } # 保留本地 native 方法不被混淆 -keepclasseswithmembernames class * { native <methods>; } # 保留在 Activity 中的方法参数是 view 的方法, # 这样以来我们在 layout 中写的 onClick 就不会被影响 -keepclassmembers class * extends android.app.Activity { public void *(android.view.View); } # 保留枚举类不被混淆 -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); } # 保留我们自定义控件(继承自 View)不被混淆 -keep public class * extends android.view.View { *** get*(); void set*(***); public <init>(android.content.Context); public <init>(android.content.Context, android.util.AttributeSet); public <init>(android.content.Context, android.util.AttributeSet, int); } # 保留 Parcelable 序列化类不被混淆 -keep class * implements android.os.Parcelable { public static final android.os.Parcelable$Creator *; } # 保留 Serializable 序列化的类不被混淆 -keepnames class * implements java.io.Serializable -keepclassmembers class * implements java.io.Serializable { static final long serialVersionUID; private static final java.io.ObjectStreamField[] serialPersistentFields; !static !transient <fields>; !private <fields>; !private <methods>; private void writeObject(java.io.ObjectOutputStream); private void readObject(java.io.ObjectInputStream); java.lang.Object writeReplace(); java.lang.Object readResolve(); } # 对于带有回调函数的 onXXEvent、**On*Listener 的,不能被混淆 -keepclassmembers class * { void *(**On*Event); void *(**On*Listener); } # webView 处理,项目中没有使用到 webView 忽略即可 -keepclassmembers class fqcn.of.javascript.interface.for.webview { public *; } -keepclassmembers class * extends android.webkit.webViewClient { public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap); public boolean *(android.webkit.WebView, java.lang.String); } -keepclassmembers class * extends android.webkit.webViewClient { public void *(android.webkit.webView, java.lang.String); } # js -keepattributes JavascriptInterface -keep class android.webkit.JavascriptInterface { *; } -keepclassmembers class * { @android.webkit.JavascriptInterface <methods>; } # @Keep -keep,allowobfuscation @interface android.support.annotation.Keep -keep @android.support.annotation.Keep class * -keepclassmembers class * { @android.support.annotation.Keep *; } 如果是 aar 这种插件,可以在 aar 的 build.gralde 中添加如下混淆配置。 android { ··· defaultConfig { ··· consumerProguardFile 'proguard-rules.pro' } ··· } 5,NDK ----- 如果要问 Android 的高级开发知识,那么 NDK 肯定是必问的。那么什么的 NDK,NDK 全称是 Native Development Kit,是一组可以让开发者在 Android 应用中使用 C/C++ 的工具。通常,NDK 可以用在如下的场景中: * 从设备获取更好的性能以用于计算密集型应用,例如游戏或物理模拟。 * 重复使用自己或其他开发者的 C/C++ 库,便利于跨平台。 * NDK 集成了譬如 OpenSL、Vulkan 等 API 规范的特定实现,以实现在 Java 层无法做到的功能,如音视频开发、渲染。 * 增加反编译难度。 5.1, JNI 基础 ---------- JNI 即 java native interface,是 Java 和 Native 代码进行交互的接口。 ### 5.1.1 JNI 访问 Java 对象方法 假如,有如下一个 Java 类,代码如下。 package com.xzh.jni; public class MyJob { public static String JOB_STRING = "my_job"; private int jobId; public MyJob(int jobId) { this.jobId = jobId; } public int getJobId() { return jobId; } } 然后,在 cpp 目录下,新建 native\_lib.cpp,添加对应的 native 实现。 #include <jni.h> extern "C" JNIEXPORT jint JNICALL Java_com_xzh_jni_MainActivity_getJobId(JNIEnv *env, jobject thiz, jobject job) { // 根据实例获取 class 对象 jclass jobClz = env->GetObjectClass(job); // 根据类名获取 class 对象 jclass jobClz = env->FindClass("com/xzh/jni/MyJob"); // 获取属性 id jfieldID fieldId = env->GetFieldID(jobClz, "jobId", "I"); // 获取静态属性 id jfieldID sFieldId = env->GetStaticFieldID(jobClz, "JOB_STRING", "Ljava/lang/String;"); // 获取方法 id jmethodID methodId = env->GetMethodID(jobClz, "getJobId", "()I"); // 获取构造方法 id jmethodID initMethodId = env->GetMethodID(jobClz, "<init>", "(I)V"); // 根据对象属性 id 获取该属性值 jint id = env->GetIntField(job, fieldId); // 根据对象方法 id 调用该方法 jint id = env->CallIntMethod(job, methodId); // 创建新的对象 jobject newJob = env->NewObject(jobClz, initMethodId, 10); return id; } 5.2 NDK 开发 --------- ### 5.2.1 基本流程 首先,在 Java 代码中声明 Native 方法,如下所示。 public class MainActivity extends AppCompatActivity { // Used to load the 'native-lib' library on application startup. static { System.loadLibrary("native-lib"); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d("MainActivity", stringFromJNI()); } private native String stringFromJNI(); } 然后,新建一个 cpp 目录,并且新建一个名为 native-lib.cpp 的 cpp 文件,实现相关方法。 #include <jni.h>

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
2021年尾 Android 面试之必问高级知识点(包含答案),kotlin语法大全