抽象类和接口的区别
1.接口是行为的抽象,是一种行为的规范,接口是like a 的关系;抽象是对类的抽象,是一种模板设计,抽象类是is a 的关系。
2.接口没有构造方法,而抽象类有构造方法
3.接口只有定义,不能有方法的实现,java 1.8中可以定义default方法体;而抽象类可以有定义与实现
3.抽象类体现了继承关系,继承只能单继承。接口提现了实现的关系,实现可以多实现。
4.接口成员变量默认为public static final,必须赋初值,不能被修改;其所有的成员方法都是public abstract的。抽象类中成员变量默认default,可在子类中被重新定义,也可被重新赋值;抽象方法被abstract修饰,不能被private、static、synchronized和native等修饰,必须以分号结尾,不带花括号。
ListView和RecyclerView的区别
1.ListView只能垂直滑动,RecyclerView可以垂直滑动也可以水平滑动,只要通过layoutManager指定就可以了。
2.都是基于适配器模式,不过RecyclerView需要自己实现,ListView除了自己实现外,提供了几个默认实现。
3.点击事件:RecyclerView需要自己实现,ListView提供了监听函数。
4.ViewHolder: RecyclerView强制要求使用ViewHolder, ListView中ViewHolder是一种优化手段,并不强制。
5.缓存:RecyclerView有四级缓存,缓存的是ViewHolder,ListView有两级缓存,缓存的是view。
6.刷新方式:ListView是整体刷新,局部刷新需要自己实现;RecyclerView既支持整体刷新,也支持局部刷新。
缓存:
ListView:
1.mActiveViews:用于屏幕内item快速重用
2.mScrapViews:用于缓存离开屏幕的itemview
RecyclerView:
1.mChangeScrap和mAttachedScrap用于屏幕内itemview快速重用
2.mCacheViews默认缓存两个屏幕外的itemview
3.mViewCacheExetention用户自己定义的缓存
4.RecyclerViewPool默认上限为5
为什么内部类(方法内部类或匿名内部类)引用的外部类的局部变量必须用final修饰(JDK1.8可以不用final修饰, 但外部变量也不可更改, 即相当于隐性的final修饰)
这一段代码在jdk1.7环境下,必须设置final,为什么?
public void print(final int age) {
new Runnable() {
@Override
public void run() {
System.out.println(age);
}
};
}
1.解决变量的生明周期问题:为什么必须局部变量加final关键字呢?因为局部变量直接存储在栈中,当方法执行结束,非final的局部变量就被销毁,而局部内部类对局部变量的引用依然存在,当局部内部类要调用局部变量时,就会出错,出现非法引用 。简单来说,就是非final的局部变量的生命周期比局部内部类的生命周期短,是不是直接可以拷贝变量到局部内部类?这样内部类中就可以使用而且不担心生命周期问题呢?也是不可以的,因为直接拷贝又会出现第二个问题,就是数据不同步。
2.数据不同步:内部类并不是直接使用传递进来的参数,而是将传递进来的参数通过自己的构造器备份到自己内部,表面看是同一个变量,实际调用的是自己的属性而不是外部类方法的参数,如果在内部类中,修改了这些参数,并不会对外部变量产生影响,仅仅改变局部内部类中备份的参数。但是在外部调用时发现值并没有被修改,这种问题就会很尴尬,造成数据不同步。所以使用final避免数据不同步的问题。
所以总结一下:
1.final修饰的变量会被编译器在内部类生成一个拷贝作为成员变量存在,所以生命周期问题解决了。
2.final修饰确保变量在内部类中不可以被修改,所以内部类变量和外部类变量的数据同步问题解决了。
局部内部类中的使用的age和外部类对象的引用this,都是通过构造函数传递进去的
class TestClass$1 implements Runnable {
TestClass$1(TestClass var1, int var2) {
this.this$0 = var1;
this.val$age = var2;
}
public void run() {
System.out.println(this.val$age);
}
}
1.为什么普通内部类会持有外部类的引用?
- 因为内部类的产生依赖于外部类,持有的引用是类名.this
- 编译器会将内部类和外部类分别编译成各自的class文件
- 编译器自动为内部类添加一个成员变量,这个成员变量就是指向外部类对象的引用
- 编译器自动为内部类的构造方法添加一个参数, 参数的类型是外部类的类型
- 调用内部类的构造函数初始化内部类对象时, 会默认传入外部类的引用。
因此,内部类就持有了外部类的引用,可以访问外部类的变量和方法
==和equals方法的区别
1.==对于基本类型,⽐较的是值,但对于引⽤类型,⽐较的是内存地址
2.equals只能用于引用类型的比较。equals() 表示内容相等,equals() 是 Object 的成员方法,Object#equals() 的默认实现时比较对象地址相等 (this == obj)。像String,Date,File,包装类等都重写了equals()方法,比较的是对象的“实体内容”是否相同。
这种情况下一般都是先通过==比较引用类型的地址,如果地址相同,直接返回true,如果不相同再比较字符序列。
为什么重写equals方法时必须重写hashCode方法
如果两个对象相等,则 hashcode 一定也是相同的。两个对象相等,对两个对象分别调用 equals 方法都返回 true。但是,两个对象有相同的 hashcode 值,它们也不一定是相等的 。因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖。
哈希表在判断是否有重复元素存在时,是先判断两个元素的hash值是否相同,如果不同,则不用再去比较元素内容,因为这两个元素一定是不同的,但是如果hash相同,则会再通过equals比较两个元素的内容,内容相同才能判断重复。如果不重写hashCode方法,那么就会出现equals相同的两个对象,被判断为是两个不同的对象的情况。
为什么说String是不可变的
看String源码会发现它是被定义为final类型,所以它是不可以被修改的,再往里看,String内部通过一个char类型的数组存储内容,这个数组也是final类型的。
ArrayList
1.底层实现是数组,存储空间连续
2.每次扩容是将现有容量扩容为1.5倍,扩容涉及到数组的新建和拷贝。
3.查找快,O(1),直接根据角标找。
4.增删慢,涉及到元素的移动。
5.从尾部添加相对较快,如果需要扩容,就慢了。
6.从尾部删除比较快,因为不涉及到元素的移动。
LinkedList
1.LinkedList可以实现栈或队列的功能,他底层是双向链表实现的,存储空间不连续(也有可能连续)
2.没有扩容的概念
3.查找慢,需要遍历链表
3.从头尾增删快,不需要遍历查找
4.从中间位置增删较慢,需要遍历找节点,但是也比数组快
HashMap
1.数据结构为数组+链表的形式,JDK8及其以后的版本中使用了数组+链表+红黑树,解决了链表太长导致的查询速度变慢的问题。
2.默认初始化大小是16扩容后大小都是2的n次幂,原因是什么?跟(n - 1) & hash有关,(n-1)的2进制也就是1111111***111这样形式的,这样与添加元素的hash值进行位运算时,能够(充分的散列),使得添加的元素均匀分布在HashMap的每个位置上,减少hash碰撞。
3.哈希冲突及解决方法,开放定址法,链地址法,再哈希法,建立公共溢出区。
4.红黑树是一种近似平衡的二叉搜索树,平衡对一个节点的左右子树的高度差提出了要求,也就不会出现二叉搜索树倒退为链表的情况;同时它又是个二叉搜索树,保证了左节点 < 跟节点 <右节点的特性。
......
LinkedHashMap
1.LinkedHashMap继承自HashMap,所以HashMap和双向链表合二为一组成了LinkedHashMap
2.它通过维护一个额外的双向链表保证了迭代顺序,该迭代顺序可以是插入顺序,也可以是访问顺序。如LruCache内部就是使用LinkedHashMap实现的,使用了它的访问顺序。
SparseArray
1.采用两个一维数组,一个是存储key(int类型),一个是存在value(object)。
1.以键值对的形式存储,基于二分查找,查找的事件复杂度是O(logn)
2.key是一个int数组,以基本数据类型int为key,避免了拆装箱,性能更高而且内存占用更优
3.采用延迟删除的机制,删除时先标记,将删除的操作延后,一定程度上可以减少数组元素的移动,同样提高了操作效率
4.扩容是一倍一倍的增加容量
ArrayMap
1.同样有两个数组mHashes(存储int)mArray(存储object),mHashes数组用于存储key的hash值,并且是升序排列,mArray存储key和value,key和value是相邻存放的。由key的hash值根据二分查找,可以找到它在mHashes数组中的位置,然后这个位置*2+1就是value在mArray中的位置。
2.总体上和SparseArray原理是一样的,都是用两个数组存储,不存在HashMap那种拉链的方式,都是通过二分查找确定元素在数组中的位置。
3.在内存上相比HashMap有优势是因为,HashMap在存储元素的时候,都要将元素包装成一个Node节点,而SparseArray和ArrayMap都是将元素的原有类型存进去。
CopyOnWritArrayList
1.底层是数组
2.增删是同步的,加了锁,但是增加和删除的时候都会涉及到数组的新建和拷贝,顾名思义,写时拷贝
3.读数据的过程是未加锁的,效率很高,O(n),但是有可能拿到的不是最新的数据
4.写操作涉及到大量的拷贝操作,所以适合使用在读操作远远大于写操作的场景里,比如缓存。
5.数据一致性问题。CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。
显示intent和隐式intent
显示intent会明确目标activity的类名,如下面三种
1.Intent intent = new Intent(getApplicationContext(), MainActivity.class);
2.intent.setClassName(getPackageName(),"com.rzm.MainActivity");
3.intent.setComponent(new ComponentName(getPackageName(),"com.rzm.MainActivity"));
隐式intent是通过在设置Action,Data,Category,让系统来筛选合适的activity,筛选是通过所有的intent-filter来筛选
Dalvik和Art的区别
1.Android 2.2 —— Dalvik虚拟机加入了 JIT 编译器,缺点是每次启动应用都需要重新编译,运行时比较耗电,造成电池额外的开销。
2.Andorid 4.4 ——ART 是和 Dalvik 共存,ART采用全新的编译策略 AOT(Ahead-of-time)
3.Android 5.0 —— ART 全面取代 Dalvik,AOT 也成为唯一的编译模式。AOT 和 JIT 的不同之处在于:JIT 是在运行时进行编译,是动态编译,并且每次运行程序的时候都需要对 odex 重新进行编译;而 AOT 是静态编译,应用在安装的时候会启动 dex2oat 过程把 dex 预编译成 ELF 文件,每次运行程序的时候不用重新编译,是真正意义上的本地应用。
4.Android 7.0 —— JIT 回归。形成 AOT/JIT 混合编译模式,这种混合编译模式的特点是:应用在安装的时候 dex 不会被编译。应用在运行时 dex 文件先通过解析器(Interpreter)解析后会被直接执行(这一步骤跟 Android 2.2 - Android 4.4之前的行为一致),与此同时,热点函数(Hot Code)会被识别并被 JIT 编译后存储在 jit code cache 中并生成 profile 文件以记录热点函数的信息。手机进入 IDLE(空闲) 或者 Charging(充电) 状态的时候,系统会扫描 App 目录下的 profile 文件并执行 AOT 过程进行编译。
AAPT是什么
Android Asset Packaging Tool ,即Android资源打包工具,是一种编译工具,供Android Studio和Android Gradle Plugin用于编译和打包应用资源。AAPT会解析资源、为资源编索引并将资源编译为针对Android平台进行过优化的二进制格式。
APK 的打包过程
1.aapt打包资源文件,生成R.java
2.aidl工具处理aidl文件,生成java
3.javac编译java文件,生成class
4.class文件转dex
5.apkbuilder打包生成未签名的apk文件
6.jarsigner对未签名的apk文件签名
7.zipalign对签名后的apk文件进行对齐处理
虚引用的独特性
当联合使用软引用、弱引用和引用队列时,系统在回收被引用的对象之后
,将把它所回收对象对应的引用添加到关联的引用队列中。而虚引用在对象被释放之前
,将把它对应的虚引用添加到它关联的引用队列中,这使得可以在对象被回收之前采取行动。
onTrimMemory
内存优化的一个切入点,一般在application中注册,通过一系列逻辑处理,设置一个中转站,将每一个页面都注册到中转站中,这样保证每个页面都能收到系统的这个消息,当内存不足时,根据风险等级清理不同的资源,释放内存。
registerComponentCallbacks(new ComponentCallbacks2() {
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
}
@Override
public void onLowMemory() {
}
@Override
public void onTrimMemory(int level) {
}
});
OnTrimMemory 回调是 Android 4.0 以后提供的一个API,这个 API 是提供给开发者的,它的主要做用是提示开发者在系统内存不足的时候,经过处理部分资源来释放内存,从而避免被 Android 系统杀死。这样应用在下一次启动的时候,速度就会比较快。
Android系统会根据不一样等级的内存使用状况,调用这个函数:
Glide在内存状态等于TRIM_MEMORY_RUNNING_CRITICA或者大于TRIM_MEMORY_UI_HIDDEN时,清理一半内存;大于TRIM_MEMORY_BACKGROUND会清空内存
- TRIM_MEMORY_UI_HIDDEN:Home键或者Back键致使应用的UI界面不可见,这时候可以考虑释放一些资源。
应用程序真正运行时的回调
- TRIM_MEMORY_RUNNING_MODERATE:应用程序正常运行,而且不会被杀掉。可是目前手机的内存已经有点低了,系统可能会开始根据LRU缓存规则来去杀死进程了
3.TRIM_MEMORY_RUNNING_LOW 表示应用程序正常运行,而且不会被杀掉。可是目前手机的内存已经很是低了,咱们应该去释放掉一些没必要要的资源以提高系统的性能,同时这也会直接影响到咱们应用程序的性能。
4.TRIM_MEMORY_RUNNING_CRITICAL 表示应用程序仍然正常运行,可是系统已经根据LRU缓存规则杀掉了大部分缓存的进程了。这个时候咱们应当尽量地去释听任何没必要要的资源,否则的话系统可能会继续杀掉全部缓存中的进程,而且开始杀掉一些原本应当保持运行的进程,好比说后台运行的服务
应用程序是缓存的(在后台)
5.TRIM_MEMORY_BACKGROUND 表示手机目前内存已经很低了,系统准备开始根据LRU缓存来清理进程。这个时候咱们的程序在LRU缓存列表的最近位置,是不太可能被清理掉的,但这时去释放掉一些比较容易恢复的资源可以让手机的内存变得比较充足,从而让咱们的程序更长时间地保留在缓存当中,这样当用户返回咱们的程序时会感受很是顺畅,而不是经历了一次从新启动的过程。
6.TRIM_MEMORY_MODERATE 表示手机目前内存已经很低了,而且咱们的程序处于LRU缓存列表的中间位置,若是手机内存还得不到进一步释放的话,那么咱们的程序就有被系统杀掉的风险了。
7.TRIM_MEMORY_COMPLETE 表示手机目前内存已经很低了,而且咱们的程序处于LRU缓存列表的最边缘位置,系统会最优先考虑杀掉咱们的应用程序,在这个时候应当尽量地把一切能够释放的东西都进行释放。
LayoutInflater.inflate函数意义
LayoutInflater会解析传入的布局文件,遍历所有的节点,反射创建view对象
app性能优化
1.绘制优化
2.内存优化
3.存储优化
4.稳定性优化
5.耗电优化
6.apk瘦身
谈谈Kotlin中的Coroutines,它与线程有什么区别?有哪些优点?
协程:协程就像非常轻量级的线程。线程是由系统调度的,线程切换或线程阻塞的开销都比较大。而协程依赖于线程,但是协程挂起时不需要阻塞线程,几乎是无代价的,协程是由开发者控制的。所以协程也像用户态的线程,非常轻量级,一个线程中可以创建任意个协程。
协程与线程有什么区别:
1.Kotlin 协程,不是操作系统级别的概念,无需操作系统支持,线程是操作系统级别的概念,我们开发者通过编程语言(Thread.java)创建的线程,本质还是操作系统内核线程的映射。
2.Kotlin 协程,是用户态的(userlevel),内核对协程「无感知」;一般情况下,我们说的线程,都是内核线程,线程之间的切换,调度,都由操作系统负责。
3.Kotlin 协程,是协作式的,由开发者管理,不需要操作系统进行调度和切换,也没有抢占式的消耗,因此它更加「高效」;线程,是抢占式的,它们之间能共享内存资源。
4.Kotlin 协程,它底层基于状态机实现,多协程之间共用一个实例,资源开销极小,因此它更加「轻量」;线程会消耗操作系统资源。
5.Kotlin 协程,本质还是运行于线程之上,它通过协程调度器,可以运行到不同的线程上
优点:
1.轻量和高效:协程可以在一个线程中开启1000个协程,也不会有什么影响。
2.简单好用:其实轻量和高效并不是协程的核心竞争力,最主要的还是简化异步并发任务,代码中可以已同步的方式替换异步,去除java中回调地狱问题。
进程,线程和协程
从单进程到多进程提高了 CPU 利用率;从进程到线程,降低了上下文切换的开销;从线程到协程,进一步降低了上下文切换的开销,使得高并发的服务可以使用简单的代码写出来,技术的每一步发展都是为了解决实际问题。
2.Java中try catch finally的执行顺序
最一般的情况下,先try 发生异常执行catch,最后一定执行finally
3.return语句对try catch finally返回值的影响
- 发如果try catch中都有return语句,finally中没有return,那么在finally中修改除包装类型和静态变量、全局变量以外的数据都不会对try、catch中返回的变量有任何的影响(包装类型、静态变量会改变、全局变量)。
- 尽量不要在finally中使用return语句,如果使用的话,会忽略try、catch中的返回语句,也会忽略try、catch中的异常,屏蔽了错误的发生
//修改基本数据类型
public static int testBasic(){
int i = 1;
try{
I++;
System.out.println("try block, i = "+i);
return I;
}catch(Exception e){
i ++;
System.out.println("catch block i = "+i);
return I;
}finally{
i = 10;
System.out.println("finally block i = "+i);
}
}
输出结果
try block, i = 2
finally block i = 10
main test i = 2
//修改引用数据类型
public static List<Object> testWrap(){
List<Object> list = new ArrayList<>();
try{
list.add("try");
System.out.println("try block");
return list;
}catch(Exception e){
list.add("catch");
System.out.println("catch block");
return list;
}finally{
list.add("finally");
System.out.println("finally block ");
}
}
输出结果
try block
finally block
main test i = [try, finally]
4.垃圾回收算法如何确定某个对象是垃圾
引用计数法:通过引用计数来判断一个对象是否可以被回收。如果一个对象没有任何引用与之关联,则说明该对象基本不太可能在其他地方被使用到,那么这个对象就成为可被回收的对象。(给对象增加一个计数器的功能,当存在引用使用功能对象是,计数器值加一,当引用失效时计数器值减1,任何时候计数器值为0时的对象是无法被使用的。)这种方式的特点是实现简单,而且效率较高,但是它无法解决循环引用(两个对象相互引用,导致两个对象都不能被回收)的问题,因此在Java中并没有采用这种方式(Python采用的是引用计数法)
可达性分析法:通过一系列的“GC Roots”对象作为起点进行搜索,如果在“GC Roots”和一个对象之间没有可达路径,则称该对象是不可达的,不过要注意的是被判定为不可达的对象不一定就会成为可回收对象。被判定为不可达的对象要成为可回收对象必须至少经历两次标记过程,如果在这两次标记过程中仍然没有逃脱成为可回收对象的可能性,则基本上就真的成为可回收对象了。
可作为GC Roots的对象包括下面几种:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象。
- 方法区中类静态属性引用的对象。
- 方法区中常量引用的对象。
- 本地方法栈中JNI(即一般说的Native方法)引用的对象。
4.介绍垃圾回收机制
标记-清除算法:还有一种说法是标记可到达的对象,清除不可到达的对象
缺点是标记和清除的效率不高,容易产生大量内存碎片,触发更多的垃圾回收
标记-压缩算法:在标记清除算法上增加一步,将所有存活的对象压缩到内存的一端,然后对另一端的对象进行清除,减少了内存碎片,提高了内存利用率,广泛应用于老年代
复制算法:把内存分成两部分,每次只使用其中一个区域,gc时,遍历当前区域,将存活的对象复制到另一个区域,清除当前区域对象
没有内存碎片的问题,但是使用的内存减少为一半,效率和存活对象的数量有关系,存活的数量越高,效率越低,所以复制算法多应用于新生代中,新生代对象生命周期短,可以保证在同一时间段中存活数量不会很高
分代收集算法:前面提到了新生代和老年代,所以就产生了分代收集算法,根据对象生命周期长短将其分成两个或者多个域,如年轻代和老年代,新生代生命周期短,会很快被回收,因此在新生代采用效率比较高的算法,当一个对象经过几次回收后依然存活,此对象就会被放入老年代的内存空间,采用标记-压缩算法清理
5.数据结构中用于存储数据的有哪些
数组:内存空间连续,空间复杂度大,但查找时间复杂度小为O(1),特点是查找效率高,增删效率低
链表:存储区间分散,占用内存宽松,空间复杂度小,但时间复杂度大,O(N),特点是,查找效率低,增删效率高
6 HashMap实现原理
数组+单链表
当我们往HashMap中put元素时,会根据key值的hashCode重新计算hash值,根据这个值得到该元素在数组中的位置,如果这个位置上已经有元素,那么在这个位置上的元素及那个以单链表的形式存在,新加入的放在头部,如果该位置上没有元素,直接将该元素放在该位置上
7 ArrayList,LinkedList的区别
ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。对于随机访问get和set,ArrayList优于LinkedList,因为LinkedList要移动指针。对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。
8 ArrayList和Vector的主要区别
ArrayList 和Vector底层是采用数组方式存储数据,Vector:线程同步当Vector中的元素超过它的初始大小时,Vector会将它的容量翻倍,ArrayList:线程不同步,但性能很好当ArrayList中的元素超过它的初始大小时,ArrayList只增加50%的大小
9 wait()和sleep()的区别
1.sleep来自Thread类,wait来自Object类
2.调用sleep()方法的过程中,线程不会释放对象锁。而 调用 wait 方法线程会释放对象锁
3.sleep睡眠后不出让系统资源,wait让出系统资源其他线程可以占用CPU
4.sleep(milliseconds)需要指定一个睡眠时间,时间一到会自动唤醒,wait需要被唤醒,也可以指定wait的时间,时间到了自动醒来。
10 Android 线程间通信有哪几种方式(重要)
共享内存(变量);
文件,数据库;
Handler;
Java 里的 wait(),notify(),notifyAll()
12 请介绍下 AsyncTask的内部实现,适用的场景是
AsyncTask内部是通过handler和线程池实现的,默认情况下所有任务串行执行,所以不适合高并发的场景
14.Fragment 如何实现类似 Activity 栈的压栈和出栈效果的?
Fragment 的事物管理器内部维持了一个双向链表结构,该结构可以记录我们每次 add 的Fragment 和 replace 的 Fragment,然后当我们点击 back 按钮的时候会自动帮我们实现退栈操作。
Activity和Fragment生命周期有哪些?
15 Activity和Fragment生命周期有哪些?
Activity(6个):onCreate onStart onResume onPause onStop onDestroy
Fragment(11个):onAttach onCreate onCreateView onActivityCreated onStart onResume onPause onStop onDestoryView onDestroy onDetach
16 启动service的两种方法?有什么区别?
一种是startService(),另一种是bindService()。这两者的区别是第一种方式调用者开启了服务,即会与服务失去联系,两者没有关联。即使访问者退出了,服务仍在运行。如需解除服务必须显式的调用stopService方法。主要用于调用者与服务没有交互的情况下,也就是调用者不需要获取服务里的业务方法。比如电话录音。而后者调用者与服务绑定在一起的。当调用者退出的时候,服务也随之退出。用于需要与服务交互。
19.Dalvik虚拟机与JVM有什么区别
Dalvik 基于寄存器,而 JVM 基于栈。基于寄存器的虚拟机对于更大的程序来说,在它们编译的时候,花费的时间更短。
Dalvik执行.dex格式的字节码,而JVM执行.class格式的字节码。
DVM的进程和linux的进程关系(曾经是一个选择题)
DVM指dalvik的虚拟机.每一个Android应用程序都在它自己的进程中运行,都拥有一个独立的 Dalvik虚拟机实例.而每一个DVM都是在Linux 中的一个进程,所以说可以认为是同一个概念.
21.据自己的理解描述下Android数字签名。
a: Android所有的应用程序必须要有数字证书签名,Android系统不会安装一个没有数字证书签名的程序。
b: Android系统中,系统app使用的是平台证书签名,而第三方app一般使用开发者的自签名证书。
c: Release版本的第三方app(例如淘宝、支付宝、微信),必须使用一个合适私钥生成的数字证书来给程序进行签名,并且保证每次的迭代新版本都是使用相同的证书进行数字签名。不然的话,新版本和旧版本的数字证书不一致,Android系统会认为这是两个不同的app,导致更新等操作失败。
d: 数字证书是存在有效期的,这也决定了app的预计生命周期,如果数字证书超期失效,则应用无法安装或者无法正常升级。
e: Android提供了基于签名的权限机制,那么一个应用程序就可以为另一个以相同证书签名的应用程序公开自己的功能。以同一个证书对多个应用程序进行签名,利用基于签名的权限检查,你就可以在应用程序间以安全的方式共享代码和数据了
22.Dalvik基于JVM的改进
a:几个class变为一个dex,constant pool,省内存
b:Zygote,copy-on-write shared,省内存,省cpu,省电
c:基于寄存器的bytecode,省指令,省cpu,省电
d:Trace-based JIT,省cpu,省电,省内存
23.ARGB_8888占用内存大小
是4byte,即ARGB各占用8个比特来描述。
24.apk安装卸载的原理
安装过程:
a.将apk复制到data/app目录下面,会放到data/app/包名/目录下面,同时apk中的so文件也会拷贝到此目录下的lib文件目录中。
b.解压apk,把其中的classes.dex 拷贝到data/dalvik-cache, 其命名规则是 apk路径+classes.dex
c.在data/data/目录下创建对应的包名目录,data/data/包名/,并在该目录下创建存储应用数据的相关目录,例如cache, database、lib、shared_perfs等
卸载过程:
删除安装过程中在上述三个目录下创建的文件及目录。
23.隐式启动Activity的匹配规则(IntentFilter的匹配规则)
IntentFilter中的过滤信息有action category data,假设一个IntentFilter中包含了这三个规则,那么需要这三个同时匹配才能正确的启动Activity,另外一个Activity可能设置多个IntentFilter,只要能完全匹配其中一个就可以了
a.action匹配规则
action的匹配要求Intent中的action必须存在且必须和过滤规中的其中一个action相同,也就是说可能存在多个action,只要和其中一个相同就可以了
b.category的匹配规则:
Intent中可以不定义category(系统默认定义一个Default),但是如果一旦定义了,不管定义几个,每一个都要和IntentFilter中的任何一个相同。这就说明一般IntentFilter中只会定义一个category
intent.addcategory("com.xxx");
c.data的匹配规则:
data由两部分组成,mineType和URI,如果IntentFilter中没有指定URI,那么默认值就是content和file,这就要求Intent中URI必须指定为content或file才行.注意不能先调用setData再调用setType,这两个方法会相互清空
//file是URI,image/png是mineType
intent.setDataAndType(Uri.parse("file://abc"),"image/png");
对应的IntentFilter中配置为
<intent-filter>
<data android:mimeType="image/*"/>
</intent-filter>
隐式启动Activity的时候,为了不发生异常,做一个判断采用PackageManager的resolveActivity方法或者Intent.resolveActivity,如果找不到匹配的Activity就会返回null
24.Serializable和Parcelable的区别
1.Serializable是Java中的序列化接口,使用简单但是开销很大,序列化和反序列化过程会设计大量IO操作
2.Parcelable是Android中的序列化方式,更适合在Android平台使用,缺点是使用麻烦但是效率很高,
3.Parcelable主要用在内存序列化上,通过它将对象序列化到存储设备中或者将对象序列化后通过网络传输也是可以的,但是这个过程会比较复杂,所以这两种情况推荐使用Serializable,
webview怎么优化加载速度
1.开启离线缓存
在缓存可获取并且没有过期的情况下加载缓存,否则通过网络获取资源。这样的话可以减少页面的网络请求次数。
离线的情况下,设置缓存策略为setCacheMode(WebSettings.LOAD_CACHE_ONLY) 不使用网络,只加载缓存。
针对资源无法及时更新的问题,WebSettings.LOAD_DEFAULT中的页面中的缓存版本好像不是很起作用,我们需要自己做一个缓存版本控制。这个缓存版本控制可以放在APP版本更新中,在版本更改时清空webview缓存
2.预加载
将页面加载的大量资源打包进APK里面,然后当页面加载这些资源的时候让它从本地获取,这样可以提升加载速度也能减少服务器压力
3.H5优化
Android的OnPageFinished事件会在Javascript脚本执行完成之后才会触发。iPhone是显示完页面才会触发脚本的执行。所以我们这边的解决方案延迟JS脚本的载入,这个方面的问题是需要Web前端工程师帮忙优化的,网上应该有比较多LazyLoad插件
4.加载时先加载文本,后加载图片调用方式如下:
public void onProgressChanged(WebView view, int newProgress) {
if (newProgress == 100) {
// 网页加载完成
settings.setBlockNetworkImage(false);
} else {
// 网页加载中
settings.setBlockNetworkImage(false);
}
webview内存泄漏的原因
webview引起的内存泄漏主要是因为org.chromium.android_webview.AwContents 类中注册了component callbacks,但是未正常反注册而导致的。
org.chromium.android_webview.AwContents 类中有这两个方法 onAttachedToWindow 和 onDetachedFromWindow;系统会在attach和detach处进行注册和反注册component callback;
在onDetachedFromWindow() 方法的第一行中:
if (isDestroyed()) return;,
如果 isDestroyed() 返回 true 的话,那么后续的逻辑就不能正常走到,所以就不会执行unregister的操作;我们的activity退出的时候,都会主动调用 WebView.destroy() 方法,这会导致 isDestroyed() 返回 true;destroy()的执行时间又在onDetachedFromWindow之前,所以就会导致不能正常进行unregister()。
然后解决方法就是:让onDetachedFromWindow先走,在主动调用destroy()之前,把webview从它的parent上面移除掉。
ViewParent parent = mWebView.getParent();
if (parent != null) {
((ViewGroup) parent).removeView(mWebView);
}
mWebView.destroy();
完整的activity的onDestroy()方法:
@Override
protected void onDestroy() {
if( mWebView!=null) {
// 如果先调用destroy()方法,则会命中if (isDestroyed()) return;这一行代码,需要先onDetachedFromWindow(),再
// destory()
ViewParent parent = mWebView.getParent();
if (parent != null) {
((ViewGroup) parent).removeView(mWebView);
}
mWebView.stopLoading();
// 退出时调用此方法,移除绑定的服务,否则某些特定系统会报错
mWebView.getSettings().setJavaScriptEnabled(false);
mWebView.clearHistory();
mWebView.clearView();
mWebView.removeAllViews();
mWebView.destroy();
}
super.on Destroy();
}
一个Activity中加载两个fragment,通过fragment的show和hide方法控制它的显示和隐藏,那么当我每次调用show的时候都会执行fragment的onResume方法吗?
不会,fragment的onResume方法和Activity的onResume方法是绑定在一起的,Activity的onResume执行的时候,fragment的onResume会执行,但是show方法调用的时候不会执行,如果调用的是replace方法,那么每次fragment的生命周期会再走一次,这种情况下onResume会执行