类加载机制
如下图所示,JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程。
加载
加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的入口。注意这里不一定非得要从一个Class文件获取,这里既可以从ZIP包中读取(比如从jar包和war包中读取),也可以在运行时计算生成(动态代理),也可以由其它文件生成(比如将JSP文件转换成对应的Class类)。
验证
这一阶段的主要目的是为了确保Class文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
准备
准备阶段是正式为类变量分配内存并设置类变量的初始值阶段,即在方法区中分配这些变量所使用的内存空间。注意这里所说的初始值概念,比如一个类变量定义为:
public static int v = 8080;
实际上变量v在准备阶段过后的初始值为0而不是8080,将v赋值为8080的putstatic指令是程序被编译后,存放于类构造器<client>方法之中,这里我们后面会解释。 但是注意如果声明为:
public static final int v = 8080;
在编译阶段会为v生成ConstantValue属性,在准备阶段虚拟机会根据ConstantValue属性将v赋值为8080。
解析
解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。符号引用就是class文件中的:
· CONSTANT_Class_info
· CONSTANT_Field_info
· CONSTANT_Method_info
等类型的常量。
下面我们解释一下符号引用和直接引用的概念:
· 符号引用与虚拟机实现的布局无关,引用的目标并不一定要已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。
· 直接引用可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用,那引用的目标必定已经在内存中存在。
初始化
初始化阶段是类加载最后一个阶段,前面的类加载阶段之后,除了在加载阶段可以自定义类加载器以外,其它操作都由JVM主导。到了初始阶段,才开始真正执行类中定义的Java程序代码。
初始化阶段是执行类构造器<client>方法的过程。<client>方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句合并而成的。虚拟机会保证<client>方法执行之前,父类的<client>方法已经执行完毕。p.s: 如果一个类中没有对静态变量赋值也没有静态语句块,那么编译器可以不为这个类生成<client>()方法。
注意以下几种情况不会执行类初始化:
· 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。
· 定义对象数组,不会触发该类的初始化。
· 常量在编译期间会存入调用类的常量池中,本质上并没有直接引用定义常量的类,不会触发定义常量所在的类。
· 通过类名获取Class对象,不会触发类的初始化。
· 通过Class.forName加载指定类时,如果指定参数initialize为false时,也不会触发类初始化,其实这个参数是告诉虚拟机,是否要对类进行初始化。
· 通过ClassLoader默认的loadClass方法,也不会触发初始化动作。
类加载器
虚拟机设计团队把加载动作放到JVM外部实现,以便让应用程序决定如何获取所需的类,JVM提供了3种类加载器:
· 启动类加载器(Bootstrap ClassLoader):负责加载 JAVA_HOME\lib 目录中的,或通过-Xbootclasspath参数指定路径中的,且被虚拟机认可(按文件名识别,如rt.jar)的类。
· 扩展类加载器(Extension ClassLoader):负责加载 JAVA_HOME\lib\ext 目录中的,或通过java.ext.dirs系统变量指定路径中的类库。
· 应用程序类加载器(Application ClassLoader):负责加载用户路径(classpath)上的类库。
JVM通过双亲委派模型进行类的加载,当然我们也可以通过继承java.lang.ClassLoader实现自定义的类加载器。
当一个类加载器收到类加载任务,会先交给其父类加载器去完成,因此最终加载任务都会传递到顶层的启动类加载器,只有当父类加载器无法完成加载任务时,才会尝试执行加载任务。
采用双亲委派的一个好处是比如加载位于rt.jar包中的类java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个Object对象。
在有些情境中可能会出现要我们自己来实现一个类加载器的需求,由于这里涉及的内容比较广泛,我想以后单独写一篇文章来讲述,不过这里我们还是稍微来看一下。我们直接看一下jdk中的ClassLoader的源码实现:
protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
|
· 首先通过Class c = findLoadedClass(name);判断一个类是否已经被加载过。
· 如果没有被加载过执行if (c == null)中的程序,遵循双亲委派的模型,首先会通过递归从父加载器开始找,直到父类加载器是Bootstrap ClassLoader为止。
· 最后根据resolve的值,判断这个class是否需要解析。
而上面的findClass()的实现如下,直接抛出一个异常,并且方法是protected,很明显这是留给我们开发者自己去实现的,这里我们以后我们单独写一篇文章来讲一下如何重写findClass方法来实现我们自己的类加载器。
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
|
Activity生命周期
一个Activity可以基本上存在三种状态:
恢复
这项Activity是在屏幕前的,并有用户取得其焦点。(此状态,有时也简称为“运行”。)
暂停
另一个Activity在屏幕前,取得焦点,但原来的Activity仍然可见。也就是说,另一个Activity是这一个顶部可见,或者是部分透明的或不覆盖整个屏幕。暂停的Activity完全是存在的( Activity 对象保留在内存中,它维护所有状态和成员信息,并保持窗口管理器的联系),但在极低的内存的情况下,可以被系统终止。
停止
Activity完全被另一个Activity遮住了(现在Activity是在“background”)。停止Activity也仍然存在( Activity 对象保留在内存中,它保持状态和成员信息,但不和窗口管理器有关联)。然而,它已不再是对用户可见,其他地方需要内存时,它可以被系统终止。
如果一项Activity被暂停或停止,该系统可以从内存中删除它,要求它结束(调用它的finish() 方法),或者干脆杀死它的进程。Activity再次打开时(在被结束或被杀死),它必须创造所有的一切。
实现生命周期回调函数
当Activity按照如上所述在不同状态转进,出的时,是通过各种回调方法进行通知的。所有的回调方法是钩子,当的Activity状态变化时您可以覆盖他们来做适当的工作时。以下的Activity,包括基本生命周期的每一个方法
:
1.启动onCreate()->onStart()->onResume()
2.按back键onPause()->onStop()->onDestroy()
再次启动:onCreate()->onStart()->onResume()
3.按home键onPause()->onStop() 再次启动时:onRestart()->onStart()->onResume()
4.切换到SecondActivity:FirstActivity.onPause()->FirstActivity.onStop()
再次启动时: FirstActivity.onRestart()->FirstActivity.onStart()->FirstActivity.onResume()
5.切换SecondActivity (FirstActivity.finish()):
FirstActivity.onPause()->FirstActivity.onStop()->FirstActivity.onDestroy()
再次启动时: FirstActivity.onCreate ()->FirstActivity.onStart()->FirstActivity.onResume()
6.切换到SecondActivity (SecondActivity主题为Dialog): FirstActivity.onPause()
再次启动时: FirstActivity.onRestart()->FirstActivity.onStart()->FirstActivity.onResume()
7.配置改变Activity生命周期
某些设备配置在运行时可以改变(如屏幕方向,键盘的可用性,和语言)。当这种变化发生时,Android重新运行Activity(系统调用的 onDestroy() ,然后立即调用的 onCreate()) 。这种行为旨在帮助您用您所提供的(如不同的屏幕方向和大小不同的布局)的替代资源进行应用程序自动重载,以适应新的配置。
AndroidManifest.xml
<activity android:name=”…” android:configChanges="keyboardHidden|orientation|screenSize"/>
回调方法的作用,就是通知我们Activity生命周期的改变,然后我们可以处理这种改变,以便程序不会崩溃或者数据丢失等等,也就是拥有更好的用户体检,那么这么多回调方法里到底应该怎么做呢?
1、onCreate
最重要是在里面调用setContentView,还可以在里面初始化各控件、设置监听、并初始化一些全局的变量。
因为在Activity的一次生命周期中,onCreate方法只会执行一次。在Paused和Stopped状态下恢复或重启的下,这些控件、监听和全局变量也不会丢失。即便是内存不足,被回收了,再次Recreate的话,又是一次新的生命周期的开始,又会执行onCreate方法。
还可以在onCreate执行数据操作,比如从Cursor中检索数据等等,但是如果你每次进入这个Activity都可能需要更新数据,那么最好放在onStart里面。(这个需要根据实际情况来确定)
2、onDestory
确定某些资源是否没有被释放,做一些最终的清理工作,比如在这个Activity的onCreate中开启的某个线程,那么就要在onDestory中确定它是否结束了,如果没有,就结束它。
3、onStart和onRestart、onStop
Activity进入到Stopped状态之后,它极有可能被系统所回收,在某些极端情况下,系统可能是直接杀死应用程序的进程,而不是调用onDestory方法,所以我们需要在onStop方法中尽可能的释放那些用户暂时不需要使用的资源,防止内存泄露。
尽管onPause在onStop之前执行,但是onPause只适合做一些轻量级的操作,更多的耗时耗资源的操作还是要放在onStop里面,比如说对数据保存,需要用到的数据库操作。
因为从Stopped状态重启之后, onStart和onRestart方法都会被执行,所以我们要判断哪些操作分别要放在哪个方法里面 。因为可能在onStop方法里面释放了一些资源,那么我们必须要重启他们,这个时候这些重启的操作放在onStart方法里面就比较好(因为onCreate之后也需要开启这些资源)。那些因为Stopped之后引发的需要单独操作的代码,就可以放在onRestart里面。
4、onResume和onPause
onPause和onResume中做的操作,其实意义上和onStart和inStop差不多,只不过是要更轻量级的,因为onPause不能阻塞转变到下一个Activity。
比如:停止动画、取消broadcast receivers。当然相应的需要在onResume中重启或初始化等等。
有时候也需要在onPause判断用户是调用finish结束这个Activity,还是暂时离开,以便区分处理。这时候可以调用isFinishing()方法来判断。如果是用户finish这个Activity,那么返回为true,如果只是暂时离开或者被系统回收的话,就返回false。
最后加上
Android****里面保存数据状态有哪几种方式?
onPause() 用来持久化数据状态
onsavedInstanceState() 保存数据状态
Handler机制
Message类
Message 实现了Parcelable 接口,也就是说实现了序列化,这就说明Message可以在不同进程之间传递。
包含一个名为target的Handler 对象
包含一个名为callback的Runnable 对象
使用obtain 方法可以从消息池中获取Message的实例,也是推荐大家使用的方法,而不是直接调用构造方法。
非UI线程真的不能更新UI吗?不一定,之所以子线程不能更新界面,是因为Android在线程的方法里面采用checkThread进行判断是否是主线程,而这个方法是在ViewRootImpl中的,这个类是在onResume里面才生成的,因此,如果这个时候子线程在onCreate方法里面生成更新UI,而且没有做阻塞,就是耗时多的操作,还是可以更新UI的。
MessageQueue的中文翻译是消息队列,顾名思义,它的内部存储了一组消息,以队列的形式对外提供插入和删除的工作。虽然叫消息队列,但是它的内部存储结构并不是真正的队列,而是采用单链表的数据结构来存储消息列表。
handler的原理是什么?
答:1、handler封装消息的发送(主要包括消息发送给谁)
2、Looper——消息封装的载体。(1)内部包含一个MessageQueue,所有的Handler发送的消息都走向这个消息队列;(2)Looper.Looper方法,就是一个死循环,不断地从MessageQueue取消息,如果有消息就处理消息,没有消息就阻塞。
3、MessageQueue,一个消息队列,添加消息,处理消息
4、handler内部与Looper关联,handler->Looper->MessageQueue
,handler发送消息就是向MessageQueue队列发送消息。
总结:handler负责发送消息,Looper负责接收handler发送的消息,并把消息回传给handler自己。
Android更新UI的方式?
答:1、runOnUIThread
2、handler post
3、handler sendMessage
4、view post
乐观锁与悲观锁
https://blog.csdn.net/truelove12358/article/details/54963791
乐观锁。更加宽松的加锁机制,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
悲观锁。正如其名,具有强烈的独占和排他特性。它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。
CAS操作是在乐观锁上面执行
JDK1.5中引入了底层的支持,在int、long和对象的引用等类型上都公开了CAS的操作,并且JVM把它们编译为底层硬件提供的最有效的方法,在运行CAS的平台上,运行时把它们编译为相应的机器指令。在java.util.concurrent.atomic包下面的所有的原子变量类型中,比如AtomicInteger,都使用了这些底层的JVM支持为数字类型的引用类型提供一种高效的CAS操作。
在CAS操作中,会出现ABA问题。就是如果V的值先由A变成B,再由B变成A,那么仍然认为是发生了变化,并需要重新执行算法中的步骤。有简单的解决方案:不是更新某个引用的值,而是更新两个值,包括一个引用和一个版本号,即使这个值由A变为B,然后为变为A,版本号也是不同的。
Android 7.0 8.0 p 兼容性问题
Android 7.0
低电耗模式
当设备处于充电状态且屏幕已关闭一定时间后,设备会进入低电耗模式并应用第一部分限制:关闭应用网络访问、推迟作业和同步。如果进入低电耗模式后设备处于静止状态达到一定时间,系统则会对 PowerManager.WakeLock、AlarmManager 闹铃、GPS 和 WLAN 扫描应用余下的低电耗模式限制。在此窗口期间,应用程序可以访问网络并执行任何被推迟的作业/同步。
后台优化
移除了三项隐式广播,以帮助优化内存使用和电量消耗
Android 框架提供多个解决方案来缓解对这些隐式广播的需求。例如,JobSchedulerAPI 提供了一个稳健可靠的机制来安排满足指定条件(例如连入无限流量网络)时所执行的网络操作。您甚至可以使用 JobScheduler 来适应内容提供程序变化。
权限更改
传递软件包网域外的 file:// URI 可能给接收器留下无法访问的路径,分享私有文件内容的推荐方法是使用 FileProvider。
在应用间共享文件,同上
Android 8.0
针对所有 API 级别的应用
为提高设备性能,系统会限制未在前台运行的应用的某些行为。具体而言:
现在,在后台运行的应用对后台服务的访问受到限制。
应用无法使用其清单注册大部分隐式广播(即,并非专门针对此应用的广播)。
后台位置限制
为节约电池电量、保持良好的用户体验和确保系统健康运行,在运行 Android 8.0 的设备上使用后台应用时,降低了后台应用接收位置更新的频率。此行为变更会影响包括 Google Play 服务在内的所有接收位置更新的应用。
此类变更会影响以下 API:
Fused Location Provider (FLP)
Geofencing
GNSS Measurements
Location Manager
Wi-Fi Manager
应用快捷键
com.android.launcher.action.INSTALL_SHORTCUT 广播不再会对您的应用有任何影响,因为它现在是私有的隐式广播。相反,您应使用 ShortcutManager 类中的 requestPinShortcut() 函数创建应用快捷方式。
现在,ACTION_CREATE_SHORTCUT Intent 可以创建可使用 ShortcutManager 类进行管理的应用快捷方式。此 Intent 还可以创建不与 ShortcutManager 交互的旧版启动器快捷方式。在以前,此 Intent 只能创建旧版启动器快捷方式。
现在,使用 requestPinShortcut() 创建的快捷方式和在处理 ACTION_CREATE_SHORTCUT Intent 的操作组件中创建的快捷方式均已转换为功能齐全的应用快捷方式。因此,应用现在可以使用 ShortcutManager 中的函数来更新这些快捷方式。
旧版快捷方式仍然保留了它们在旧版 Android 中的功能,但您必须在应用中手动将它们转换成应用快捷方式。
语言区域和国际化
Android 7.0(API 级别 24)引入能指定默认类别语言区域的概念,但是某些 API 在本应使用默认 DISPLAY 类别语言区域时,仍然使用不带参数的通用 Locale.getDefault() 函数。现在,在 Android 8.0 中,以下函数使用 Locale.getDefault(Category.DISPLAY) 来代替 Locale.getDefault():
提醒窗口
输入和导航
网页表单自动填充 Android 自动填充框架提供对自动填充功能的内置支持
针对 Android 8.0 的应用
提醒窗口
使用 SYSTEM_ALERT_WINDOW 权限的应用无法再使用以下窗口类型来在其他应用和系统窗口上方显示提醒窗口:
TYPE_PHONE
TYPE_PRIORITY_PHONE
TYPE_SYSTEM_ALERT
TYPE_SYSTEM_OVERLAY
TYPE_SYSTEM_ERROR
相反,应用必须使用名为 TYPE_APPLICATION_OVERLAY 的新窗口类型。
使用 TYPE_APPLICATION_OVERLAY 窗口类型显示应用的提醒窗口时,请记住新窗口类型的以下特性:
应用的提醒窗口始终显示在状态栏和输入法等关键系统窗口的下面。
系统可以移动使用 TYPE_APPLICATION_OVERLAY 窗口类型的窗口或调整其大小,以改善屏幕显示效果。
通过打开通知栏,用户可以访问设置来阻止应用显示使用 TYPE_APPLICATION_OVERLAY 窗口类型显示的提醒窗口。
媒体
框架会执行音频闪避。进行 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 时,应用不会失去焦点。新的 API 适用于需要暂停而不是闪避的应用。请注意,此行为无法在 Android 8.0 1 版本中实现。
当用户打电话时,活动的媒体流将在通话期间静音。
集合的处理
在 Android 8.0 中,Collections.sort() 是在 List.sort() 的基础上实现的。在 Android 7.x(API 级别 24 和 25)中,则恰恰相反。在过去,List.sort() 的默认实现会调用 Collections.sort()。
类加载行为
平台将检查类加载器返回的类描述符是否与预期的描述符一致。如果返回的描述符与预期不符,平台会引发 NoClassDefFoundError 错误,并在异常日志中存储一条注明不一致之处的详细错误消息。
平台还检查请求的类描述符是否有效。此检查捕获间接加载诸如 GetFieldID() 等类的 JNI 调用,向这些类传递无效的描述符。例如,找不到包含 java/lang/String 签名的字段,是因为此签名无效;它应为 Ljava/lang/String;。
这与 JNI 对 FindClass() 的调用不同,其中 java/lang/String 是一个有效的完全限定名称。
Android 8.0 不支持多个类加载器同时尝试使用相同的 DexFile 对象来定义类。尝试进行此操作,会导致 Android 运行时引发 InternalError 错误,同时显示消息“Attempt to register dex file <filename> with multiple class loaders”。
Android P
屏幕缺口支持
您可以按如下方法在任何运行 Android P 的设备或模拟器上模拟屏幕缺口:
启用开发者选项。
在 Developer options 屏幕中,向下滚动至 Drawing 部分并选择 Simulate a display with a cutout。
选择凹口屏幕的大小。
通知
短信 Android P 可在手机的“短信通知”中显示图像。SmartReply:Android P 支持您的短信应用中提供的建议回复。
多摄像头支持和摄像头更新
摄像头方面的其他改进还包括新的会话参数和 Surface 共享,前者有助于降低首次拍照期间的延迟,而后者则让摄像头客户端能够处理各种用例,而无需停止并启动摄像头视频流。 我们还针对基于显示屏的 flash 支持和 OIS 时间戳访问新增了一些 API,用以实现应用级的图像稳定化和特效。
适用于位图和可绘制对象的 ImageDecoder
您应使用 ImageDecoder 来解码图像,而不是使用 BitmapFactory 和 BitmapFactory.Options API。
ImageDecoder 让您可以从字节缓冲区、文件或 URI 来创建 Drawable 或 Bitmap。
内存泄漏
1、单例造成的内存泄漏
由于单例的静态特性使得其生命周期和应用的生命周期一样长
2、非静态内部类创建静态实例造成的内存泄漏
因为非静态内部类默认会持有外部类的引用,而该非静态内部类又创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,解决方法:将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,就使用Application的Context。
3、Handler造成的内存泄漏
1、从Android的角度
当Android应用程序启动时,该应用程序的主线程会自动创建一个Looper对象和与之关联的MessageQueue。当主线程中实例化一个Handler对象后,它就会自动与主线程Looper的MessageQueue关联起来。所有发送到MessageQueue的Messag都会持有Handler的引用,所以Looper会据此回调Handle的handleMessage()方法来处理消息。只要MessageQueue中有未处理的Message,Looper就会不断的从中取出并交给Handler处理。另外,主线程的Looper对象会伴随该应用程序的整个生命周期。
2、 Java角度
在Java中,非静态内部类和匿名类内部类都会潜在持有它们所属的外部类的引用,但是静态内部类却不会。
4、线程造成的内存泄漏
示例:AsyncTask和Runnable
AsyncTask和Runnable都使用了匿名内部类,那么它们将持有其所在Activity的隐式引用。如果任务在Activity销毁之前还未完成,那么将导致Activity的内存资源无法被回收,从而造成内存泄漏。
解决方法:将AsyncTask和Runnable类独立出来或者使用静态内部类,这样便可以避免内存泄漏。
5、资源未关闭造成的内存泄漏
1)比如在Activity中register了一个BraodcastReceiver,但在Activity结束后没有unregister该BraodcastReceiver。
2)资源性对象比如Cursor,Stream、File文件等往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它们的缓冲不仅存在于 java虚拟机内,还存在于java虚拟机外。如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄漏。
3)对于资源性对象在不使用的时候,应该调用它的close()函数将其关闭掉,然后再设置为null。在我们的程序退出时一定要确保我们的资源性对象已经关闭。
4)Bitmap对象不在使用时调用recycle()释放内存。2.3以后的bitmap应该是不需要手动recycle了,内存已经在java层了。
6、使用ListView时造成的内存泄漏
构造Adapter时,没有使用缓存的convertView。
解决方法:在构造Adapter时,使用缓存的convertView。
7、集合容器中的内存泄露
如果这个集合是static的话,那情况就更严重了。
解决方法:在退出程序之前,将集合里的东西clear,然后置为null,再退出程序。
8、WebView造成的泄露
当我们不要使用WebView对象时,应该调用它的destory()函数来销毁它,并释放其占用的内存,否则其长期占用的内存也不能被回收,从而造成内存泄露。
解决方法:为WebView另外开启一个进程,通过AIDL与主线程进行通信,WebView所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放。
大图加载的缓存
按比例压缩到跟控件大小,计算压缩值,压缩后,可以根据图片的色彩存储模式选择显示,减轻内存负担。
Bitmap优化
LruCache 和diskLruCache 优化缓存。 当向 ImageView 中加载一张图片时,首先会在 LruCache 的缓存中进行检查。如果找到了相应的键值,则会立刻更新ImageView ,否则开启一个后台线程来加载这张图片。
子线程能不能创建Handler
创建主线程的Looper、主线程的消息队列...就连我们使用的handler也是主线程的。
不一定,之所以子线程不能更新界面,是因为Android在线程的方法里面采用checkThread进行判断是否是主线程,而这个方法是在ViewRootImpl中的,这个类是在onResume里面才生成的,因此,如果这个时候子线程在onCreate方法里面生成更新UI,而且没有做阻塞,就是耗时多的操作,还是可以更新UI的。
所以以后要想创建非主线程的Handler时,我们用HandlerThread类提供的Looper对象即可。创建HandlerThread对象的时候,有个参数,是指定线程名字的。
线程间通信其他方式
一:Handler实现线程间的通信,消息是通过绑定在主线程的Handler来传递的。
二、解决线程间通信的问题:使用AsyncTask:
三、runOnUiThread(),子线程中持有当前Activity引用(假如为Activity mActivity;),即可以调用mActivity的runOnUiThread(Runnable r)方法。
四、post()和postDelay()
子线程如果持有某个View的引用,要对该View进行更新,则可调用该View对象的post(Runnable r)或postDelay(Runnable r)方法
volatile原理
通俗点讲就是说一个变量如果用volatile修饰了,则Java可以确保所有线程看到这个变量的值是一致的,如果某个线程对volatile修饰的共享变量进行更新,那么其他线程可以立马看到这个更新,这就是所谓的线程可见性。
volatile的使用
防重排序
并发环境下的单例实现方式,我们通常可以采用双重检查加锁(DCL)的方式来实现
实现可见性
可见性问题主要指一个线程修改了共享变量值,而另一个线程却看不到。引起可见性问题的主要原因是每个线程拥有自己的一个高速缓存区——线程工作内存。volatile关键字能有效的解决这个问题
保证原子性
volatile只能保证对单次读/写的原子性
因为long和double两种数据类型的操作可分为高32位和低32位两部分,因此普通的long或double类型读/写可能不是原子的。因此,鼓励大家将共享的long和double变量设置为volatile类型,这样能保证任何情况下对long和double的单次读/写操作都具有原子性。
volatile的原理
可见性实现:(1)修改volatile变量时会强制将修改后的值刷新的主内存中。
(2)修改volatile变量后会导致其他线程工作内存中对应的变量值失效。因此,再读取该变量值的时候就需要重新从读取主内存中的值。
有序性实现:通俗一点说就是如果a happen-before b,则a所做的任何操作对b是可见的。
同一个线程中的,前面的操作 happen-before 后续的操作。(即单线程内按代码顺序执行。但是,在不影响在单线程环境执行结果的前提下,编译器和处理器可以进行重排序,这是合法的。换句话说,这一是规则无法保证编译重排和指令重排)。
volatile的使用优化 用一种追加字节的方式来优化队列出队和入队的性能
那么是不是在使用volatile变量时都应该追加到64字节呢?不是的。在两种场景下不应该使用这种方式。
缓存行非64字节宽的处理器。如P6系列和奔腾处理器,它们的L1和L2高速缓存行是32个字节宽。
共享变量不会被频繁地写。因为使用追加字节的方式需要处理器读取更多的字节到高速缓冲区,这本身就会带来一定的性能消耗,如果共享变量不被频繁写的话,锁的几率也非常小,就没必要通过追加字节的方式来避免相互锁定。
读写锁的应用
比如说有两个线程,他们之间共享一块数据,在一个线程写数据和一个线程读数据的时候,怎么样保证数据和逻辑的正确性。是否需要同步和加锁呢?还是完全没有必要?实际上,在前面关于volatile的介绍里已经基本上解决了。对于有多个读取数据的线程和单个写数据线程的场景,需要读写锁。
RecyclerView与ListView的区别
RecyclerView,它主要的特点就是复用。我们知道,Listview中的Adapter中可以实现ViewHolder的复用。RecyclerView提供了一个耦合度更低的方式来复用ViewHolder,并且可以轻松的实现ListView、GridView以及瀑布流的效果。
LayoutManager
这个LayoutManager类决定视图被放在画面中哪个位置,但这只是它的众多职责之一。它可以管理滚动和循环利用。LayoutManager只有一个叫做LinearLayoutManager的实现类,我们可以设置它的横向和纵向。
ItemAnimator
ItemAnimator简单来说是会根据适配器上收到的相关通知去动画的显示组件的修改,添加和删除等。它会自动添加和移除item的动画。自带的默认效果也不错,已经非常好了。
优点:
RecyclerView本身它是不关心视图相关的问题的,由于ListView的紧耦合的问题,google的改进就是RecyclerView本身不参与任何视图相关的问题。它不关心如何将子View放在合适的位置,也不关心如何分割这些子View,更不关心每个子View各自的外观。更进一步来说就是RecyclerView它只负责回收和重用的工作,这也是它名字的由来。
所有关于布局、绘制和其他相关的问题,也就是跟数据展示相关的所有问题,都被委派给了一些”插件化”的类来处理。这使得RecyclerView的API变得非常灵活。你需要一个新的布局么?接入另一个LayoutManager就可以了!你想要不同的动画么?接入一个新的ItemAnimator就可以了,诸如此类等等。还能局部刷新。
缺点:
在RecyclerView中,没有一个onItemClickListener方法。所以目前在适配器中处理这样的事件比较好。如果想要从适配器上添加或移除条目,需要明确通知适配器。这与先前的notifyDataSetChanged()方法稍微有些不同。具体操作在适配器代码中就可以体现。
事件分发机制(看书)
动画(看书)
okhttp****支不支持优先级
本篇文章的网络层是OKHttp,既然选择了OkHttp,如果要在onDestroy中取消未开始执行以及已经开始执行的网络请求,就必须给每一个请求设置一个tag,然后通过该tag来需要网络请求。比较明智的做法是以该Activity的上下文的hash值作为tag。取消请求时将hash值传入,则该界面所有的请求都可以取消。
底层Request暴露优先级之后我们需要实现一个比较器,根据优先级由大到小进行排序。
ssl****握手谁实现的
首先tcp三次握手:Platform.get().connectSocket
其次获得I/O流:source = Okio.buffer(Okio.source(rawSocket));sink = Okio.buffer(Okio.sink(rawSocket));
然后判断是否需要ssl,如果需要则进行ssl:connectTls(readTimeout, writeTimeout, connectionSpecSelector);
在connectTls中,忽略其他的,ssl握手发生在
sslSocket.startHandshake();
底层实现握手。
okhttp websocket****应用
https://blog.csdn.net/tq08g2z/article/details/77311767
什么情况会产生ANR
在Android上,如果你的应用程序有一段时间响应不够灵敏,系统会向用户显示一个对话框,这个对话框称作应用程序无响应(ANR:Application Not Responding)对话框。默认情况下,在android中Activity的最长执行时间是5秒,BroadcastReceiver的最长执行时间则是10秒。
产生原因:由于主线程有很多重要事情要做,比如响应点击事件等。如果在主线程里做了太多耗时的操作,则有可能引发ANR。尽量将耗时的操作放在子线程里。
由于主线程导致的情况:
1.耗时网络访问
2.当有大量数据读写操作时再请求数据读写
3.数据库操作(比如其他大数据量应用访问数据库导致数据库负载过重时)
4.硬件操作(比如Camera)
非主线程导致的情况:
1.非主线程持有lock,导致主线程等待lock超时
2.非主线程终止或者崩溃导致主线程一直等待
广播****onReceive****方法调用线程
不建议开线程,建议开service,onreceive一下子就失活了
静态广播接收流程
广播的发送是通过sendBroadcast 执行的,
总结:
广播的注册过程 :最终在ActivityManagerService中将远程的InnerInnerReceiver以及Intent-filter对象存储起来。
广播的发送以及接受:内部会首先根据传入的Intent-filter 查找出匹配的广播接受者,并将改接受者放到BroadcastQueue中,紧接着系统会遍历ArrayList中的广播,并将其发送给它们对应的广播接受者,最后调用到广播接受者的onReceiver方法。
动态广播能不能重复注册
第三次被踢就会出现三次…..我在程序中使用的是动态注册,结果我换成静态注册就没问题了。想了想貌似问题就在这里,应该是重复注册了广播接收。找到问题以后将广播接收对象定义为静态对象,只初始化一次,问题迎刃而解。也就是说,使用动态注册,每注册一次就会生成一个广播接收的对象。
Butterknife****工作原理
ButterKnife是通过使用注解方式来自动生成模板代码,从而来将Activity中的字段和方法与View绑定在一起。
butterknife的原理主要分为三个部分来介绍,主要为:注解生成模板代码分析、butterknife.bind()方法分析、生成的模板类代码分析。
butterknife注册的注解器为ButterKnifeProcessor
https://www.jianshu.com/p/5cba8a5514cb
Android****仿微信朋友圈图片展示效果****,
1.透明Activity
2.计算gridView下imageView Item所在位置
3.一张图大小
4.图片展示动画
LeakCanary****原理
[图片上传失败...(image-36b1d8-1533601512661)]
Activity****启动模式及几个模式的应用场景
[图片上传失败...(image-ff924c-1533601512660)]
1. SingleTask模式的运用场景
最常见的应用场景就是保持我们应用开启后仅仅有一个Activity的实例。最典型的样例就是应用中展示的主页(Home页)。假设用户在主页跳转到其他页面,运行多次操作后想返回到主页,假设不使用SingleTask模式,在点击返回的过程中会多次看到主页,这明显就是设计不合理了。
还有:
|
LauchMode
|
Instance
|
| --- | --- |
|
standard
|
mainfest中没有配置就默认标准模式
|
|
singleTop
|
登录页面、WXPayEntryActivity、WXEntryActivity 、推送通知栏
|
|
singleTask
|
程序模块逻辑入口:主页面(Fragment的containerActivity)、WebView页面、扫一扫页面
|
|
singleInstance
|
系统Launcher、锁屏键、来电显示等系统应用
|
灭屏会不会触发onSavedInstance
会的。
调用时机 : Activity 容易被销毁的时候调用, 注意是容易被销毁, 也可能没有销毁就调用了;
按下Home键 : Activity 进入了后台, 此时会调用该方法;
按下电源键 : 屏幕关闭, Activity 进入后台;
启动其它 Activity : Activity 被压入了任务栈的栈底;
横竖屏切换 : 会销毁当前 Activity 并重新创建;
onRestoreInstanceState和onSavedInstanceState是否成对出现
onSavedInstanceState()和onRestoreInstanceState()并不是activity生命周期的方法。
onSaveInstanceState()会在onPause()或onStop()之前执行,onRestoreInstanceState()会在onStart()和onResume()之间执行。
当应用遇到意外情况(内存不足,用户直接按home键)由系统直接销毁一个Activity时,onSaveInstanceState()就会调用,但是当用户主动销毁activity,如按back键,或直接执行finish(),这种情况下onSaveInstanceState()就不会执行,因为这种情况下,用户的行为决定了不需要保存Activity的状态。
那么onRestoreInstanceState()会跟onSaveInstanceState()成对出现吗? 答案是不会成对出现,onSaveInstanceState()需要调用的时,activity可能销毁,也可能没有销毁,只有在activity销毁重建的时候onRestoreInstanceState()才会调用。
在onSaveInstanceState()中默认情况下具体干些什么?
默认情况下默认会自动保存Activity中的某些状态,比如activity中各种UI的状态,因此在activity被“系统”销毁和重建的时候,这些Ui的状态会默认保存,但是前提条件是Ui控件必须制定id,如果没有指定id的话,UI的状态是无法保存 的。
总结下Activity数据的保存和恢复:
activity中保存数据有两种方式onPause(),onSaveInstance(bundle), 恢复数据也有两种途径onCreate(Bundle), onRestoreInstanceState(budle),默认情况下onSaveInstanceSate()和onRestoreInstanceState()会对UI状态进行保存和恢复,如果需要保存其他数据可以在onSaveInstanceState(),onPause()保存,但是如果是持久化的数据得通过onPause()保存(google推荐)。
Service生命周期的理解
bindService整个代码怎么写
这个过程比较复杂,但总体来说,思路还是比较清晰的,整个调用过程为MainActivity.bindService->CounterService.onCreate->CounterService.onBind->MainActivity.ServiceConnection.onServiceConnection->CounterService.CounterBinder.getService。下面,我们就先用一个序列图来总体描述这个服务绑定的过程,然后就具体分析每一个步骤。
与service通信是否会阻塞当前线程
会的,因为service本来就是在主线程,为了避免阻塞,所以我们往往在service创建子线程,或者是启用intentservice,比较常见的Service传递数据到Activity有三种方式, 通过Binder, 通过Broadcast广播, 自定义接口回调, 都是借助于IBinder暴露Service中的相应操作。网上还有些文章中提到用观察者模式,观察者模式也是接口回调的一种方式,
如果是耗时方法,为什么会阻塞
是因为在主线程,ANR
如果不是耗时方法,为什么不会阻塞
如果远端是耗时操作,怎么不等待结果让主线程先运行
ANR
startService和bindSerivce对service生命周期的影响
一、正常情况(应该大家都很熟了,简单介绍):
(1)单独使用startService():
onCreate()->onStartCommand()->Service running->onDestroy()->Service shut down
(2)单独使用bindService():
onCreate()->onBind()->Clients are bound to service->onUnbind()->onDestroy()->Service shut down
例子一:按顺序1,2,3,4执行
(1)startServic:调用onCreate()->onStartCommand()
(2)bindService:调用onBind()
(3)stopService:没有调用onDestory() Service仍然在运行!
(4)unbindService:调用onUnbind()->onDestory() 此时Service关闭!
例子二:将例子一3,4调换
(1)startServic:调用onCreate()->onStartCommand()
(2)bindService:调用onBind()
(3)unbindService:调用onUnbind() Service仍然在运行!
(4)stopService:调用onDestory() 此时Service才关闭!
aidl传递Bitmap需要注意的事项
???线程问题?注意实体类实现Parcelable借口。
EventBus原理
在3.0的版本中,使用的注解类型为Runtime
三要素:
A,Event:事件,
B,Publisher:发布者,可以在任意线程发布事件
C,Subscrible:订阅者,
1,采用单利双重锁模式创建对象
2,构造方法
2.1,粘性事件,保存到ConCurrenHashMap集合,(在构造方法中实现),
HashMap效率高,但线程不安全,在多线程的情况下,尽量用ConcurrentHashMap,避免多线程并发异常
3,注册register()方法主要做了2件事:
3.1,找到订阅者的方法.找出传进来的订阅者的所有订阅方法,然后遍历订阅者的方法.
A,通过反射来获取订阅者中所有的方法,并根据方法的类型,参数和注解找到订阅方法.
3.2,订阅者的注册
注解处理器怎么工作,注解处理器有哪些API
注解处理器是(Annotation Processor)是javac的一个工具,用来在编译时扫描和编译和处理注解,就是把标记了注解的类,变量等作为输入内容,经过注解处理器处理,生成想要生成的java代码。
处理器的写法有固定的套路,继承AbstractProcessor。
init(ProcessingEnvironment processingEnv) 被注解处理工具调用,参数ProcessingEnvironment 提供了Element,Filer,Messager等工具
getSupportedAnnotationTypes() 指定注解处理器是注册给那一个注解的,它是一个字符串的集合,意味着可以支持多个类型的注解,并且字符串是合法全名。
getSupportedSourceVersion 指定Java版本
process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) 这个也是最主要的,在这里扫描和处理你的注解并生成Java代码,信息都在参数RoundEnvironment 里了,后面会介绍。
Glide原理
郭霖大佬写 https://blog.csdn.net/guolin_blog/article/details/53759439
Lrucache原理
LinkedHashMap原理
HashMap原理
o 解决Hash冲突的方法
o equals和hashcode作用
o hashcode如何实现
自己为知笔记
Object类下有什么方法
registerNatives() //私有方法
getClass() //返回此 Object 的运行类。
hashCode() //用于获取对象的哈希值。
equals(Object obj) //用于确认两个对象是否“相同”。
clone() //创建并返回此对象的一个副本。
toString() //返回该对象的字符串表示。
notify() //唤醒在此对象监视器上等待的单个线程。
notifyAll() //唤醒在此对象监视器上等待的所有线程。
wait(long timeout) //在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或 者超过指定的时间量前,导致当前线程等待。
wait(long timeout, int nanos) //在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量前,导致当前线程等待。
wait() //用于让当前线程失去操作权限,当前线程进入等待序列
finalize() //当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。
Android中的类加载器
类加载器之间的区别
Android中的ClassLoader类型也可分为系统ClassLoader和自定义ClassLoader。其中系统ClassLoader包括3种分别是:
BootClassLoader,Android系统启动时会使用BootClassLoader来预加载常用类,与Java中的Bootstrap ClassLoader不同的是,它并不是由C/C++代码实现,而是由Java实现的。BootClassLoader是ClassLoader的一个内部类。
PathClassLoader,全名是dalvik/system.PathClassLoader,可以加载已经安装的Apk,也就是/data/app/package 下的apk文件,也可以加载/vendor/lib, /system/lib下的nativeLibrary。
DexClassLoader,全名是dalvik/system.DexClassLoader,可以加载一个未安装的apk文件。
从打印的结果也可以证实:App系统类加载器是PathClassLoader,而BootClassLoader是其parent类加载器。
Dex融合用的哪种类加载器
DexClassLoader是一个可以从包含classes.dex实体的.jar或.apk文件中加载classes的类加载器。可以用于实现dex的动态加载、代码热更新等等。这个类加载器必须要一个app的私有、可写目录来缓存经过优化的classes(odex文件),使用Context.getDir(String, int)方法可以创建一个这样的目录。
父类是什么及三者之间的关系
是ClassLoader,也不同于Java的ClassLoader。
https://blog.csdn.net/mynameishuangshuai/article/details/52737581
双亲委派模型
如果使用委托机制,会递归的向父类查找,也就是首选用Bootstrap尝试加载,如果找不到再向下。这里的System就能在Bootstrap中找到然后加载,如果此时类B也要加载System,也从Bootstrap开始,此时Bootstrap发现已经加载过了System那么直接返回内存中的System即可而不需要重新加载,这样内存中就只有一份System的字节码了
Android中的动画及区别(看书,或者其他
Android中序列化方式
两者区别
永久的保存对象数据
通过序列化操作将对象数据在网络上进行传输
将对象数据在进程之间进行传递
Serializable 直接implement
Parcelable 不仅要implement 还要还需要实现内部的相应方法
为什么Parcelable性能更好
Parcelable与Serializable的性能比较
首先Parcelable的性能要强于Serializable的原因我需要简单的阐述一下
1). 在内存的使用中,前者在性能方面要强于后者
2). 后者在序列化操作的时候会产生大量的临时变量,(原因是使用了反射机制)从而导致GC的频繁调用,因此在性能上会稍微逊色
3). Parcelable是以Ibinder作为信息载体的.在内存上的开销比较小,因此在内存之间进行数据传递的时候,Android推荐使用Parcelable,既然是内存方面比价有优势,那么自然就要优先选择.
4). 在读写数据的时候,Parcelable是在内存中直接进行读写,而Serializable是通过使用IO流的形式将数据读写入在硬盘上.
但是:虽然Parcelable的性能要强于Serializable,但是仍然有特殊的情况需要使用Serializable,而不去使用Parcelable,因为****Parcelable****无法将数据进行持久化,因此在将数据保存在磁盘的时候,仍然需要使用后者,因为前者无法很好的将数据进行持久化.(原因是在不同的Android版本当中,Parcelable可能会不同,因此数据的持久化方面仍然是使用Serializable)
序列化UID作用
辅助完成序列化和反序列化,当一个类实现SerSerializable接口,没有添加serialVersionUID的作用字段时,IDE会发出警告,这个字段可以手动指定一个值,比如1L,也可指定为IED根据类的结构生成一个long值,它们的效果是一样的。在序列化时会将这个值写入存储介质,反序列化时就校验本地类的serialVersionUID和序列化介质中的是否一致,不一致将抛出异常 java.io.InvalidClassException
(1)若不指定:系统会根据类的结构计算出一个serialVersionUID,一旦类的结构发生改变这个值就会改变,将导致反序列化失败;
(2)指定一个值:当类的结构发生改变时,也可以不修改serialVersionUID的值,这种情况下能最大程度上通过反序列化回复数据,若类的结构发生毁灭性的改变,例如字段数据类型改变了,也会导致反序列失败。
VLayout实现原理
从官网的介绍VLayout扩展的主要是LayoutManager,具体布局逻辑在LayoutHelper中实现。Vlayout提供了各种各样的LayoutHelper实现,同时也可以非常容易地扩展实现自定义的LayoutHelper。
[图片上传失败...(image-216067-1533601512655)]
可以看的上图的列表中有各种各样的布局,这些都是LayoutHelper的功劳。DelegateAdapter负责组合多个adapter,展示在RecyclerView中。
这有一个比较明显的缺点是,每个子adapter只负责一个连续的区域,如果,有多个区域需要同样的adapter,就需要添加两次adapter。
VLayout为什么不用RecyclerView实现多Item
HTTPS连接过程
分为http链接和SSL链接过程
应用构建过程
[图片上传失败...(image-71cd6a-1533601512656)]
通常的构建过程就是如上图所示,下面是具体描述:
1.AAPT(Android Asset Packaging Tool)工具会打包应用中的资源文件,如AndroidManifest.xml、layout布局中的xml等,并将xml文件编译为二进制形式,当然assets文件夹中的文件不会被编译,图片及raw文件夹中的资源也会保持原来的形态,需要注意的是raw文件夹中的资源也会生成资源id。AAPT编译完成之后会生成R.java文件。
2.AIDL工具会将所有的aidl接口转化为java接口。
3.所有的java代码,包括R.java与aidl文件都会被Java编译器编译成.class文件。
4.Dex工具会将上述产生的.class文件及第三库及其他.class文件编译成.dex文件(dex文件是Dalvik虚拟机可以执行的格式),dex文件最终会被打包进APK文件。
5.ApkBuilder工具会将编译过的资源及未编译过的资源(如图片等)以及.dex文件打包成APK文件。
6.生成APK文件后,需要对其签名才可安装到设备,平时测试时会使用debug keystore,当正式发布应用时必须使用release版的keystore对应用进行签名。
7.如果对APK正式签名,还需要使用zipalign工具对APK进行对齐操作,这样做的好处是当应用运行时会提高速度,但是相应的会增加内存的开销。
签名机制流程:
1、对Apk中的每个文件做一次算法(数据摘要+Base64编码),保存到MANIFEST.MF文件中
2、对MANIFEST.MF整个文件做一次算法(数据摘要+Base64编码),存放到CERT.SF文件的头属性中,在对MANIFEST.MF文件中各个属性块做一次算法(数据摘要+Base64编码),存到到一个属性块中。
3、对CERT.SF文件做签名,内容存档到CERT.RSA中
应用签名校验过程
1、验证Apk中的每个文件的算法(数据摘要+Base64编码)和MANIFEST.MF文件中的对应属性块内容是否配对
2、验证CERT.SF文件的签名信息和CERT.RSA中的内容是否一致
3、MANIFEST.MF整个文件签名在CERT.SF文件中头属性中的值是否匹配以及验证MANIFEST.MF文件中的各个属性块的签名在CERT.SF文件中是否匹配
V1签名和V2签名区别
v1签名是对jar进行签名,V2签名是对整个apk签名:官方介绍就是:v2签名是在整个APK文件的二进制内容上计算和验证的,v1是在归档文件中解压缩文件内容。
二者签名所产生的结果:
v1:在v1中只对未压缩的文件内容进行了验证,所以在APK签名之后可以进行很多修改——文件可以移动,甚至可以重新压缩。即可以对签名后的文件在进行处理 。
v2:v2签名验证了归档中的所有字节,而不是单独的ZIP条目,如果您在构建过程中有任何定制任务,包括篡改或处理APK文件,请确保禁用它们,否则您可能会使v2签名失效,从而使您的APKs与Android 7.0和以上版本不兼容。
Dex加固原理
下面就来看一下Android中加壳的原理:
[图片上传失败...(image-2f742d-1533601512656)]
我们在加固的过程中需要三个对象:
1****、需要加密的****Apk(****源****Apk)
2****、壳程序****Apk(****负责解密****Apk****工作****)
3****、加密工具****(****将源****Apk****进行加密和壳****Dex****合并成新的****Dex)
主要步骤:
我们拿到需要加密的****Apk****和自己的壳程序****Apk****,然后用加密算法对源****Apk****进行加密在将壳****Apk****进行合并得到新的****Dex****文件,最后替换壳程序中的****dex****文件即可,得到新的****Apk,****那么这个新的****Apk****我们也叫作脱壳程序****Apk.****他已经不是一个完整意义上的****Apk****程序了,他的主要工作是:负责解密源****Apk.****然后加载****Apk,****让其正常运行起来。
APK瘦身
1、使用一套资源
对于绝大对数APP来说,只需要取一套设计图就足够了。鉴于现在分辨率的趋势,建议取720p的资源,放到xhdpi目录。
2、开启minifyEnabled混淆代码
在gradle使用minifyEnabled进行Proguard混淆的配置,可大大减小APP大小
3、开启shrinkResources去除无用资源
在gradle使用shrinkResources去除无用资源,效果非常好。
4、清理无用资源
Lint在检查完成后,会提供一份详细的资源文件清单,并且将没有用到的资源在 UnusedResources:Unused resources 区域。只要我们没有通过反射使用这些资源,就可以放心的删掉它们了。版本迭代过程中,不但有废弃代码冗余,肯定会有无用的图片存在。真正起效果只能通过Android Studio自带的 “Remove Unused Resources”小插件来实现了
5、删除无用的语言资源
大部分应用其实并不需要支持几十种语言的国际化支持。还好强大的gradle支持语言的配置,比如国内应用只支持中文:
6、使用jpg格式,使用webp格式,缩小大图
7、删除armable-v7包下的so ,删除x86包下的so
8、使用微信资源压缩打包工具