onSaveInstanceState,就是用bundle存储。
1、当用户按下手机home键的时候。
2、长按手机home键或者按下菜单键时。
3、手机息屏时。
4、FirstActivity启动SecondActivity,FirstActivity就会调用,也就是说打开新Activity时,原Activity就会调用。
5、默认情况下横竖屏切换时。
当竖屏切换到横屏时,系统会销毁竖屏Activity然后创建横屏的Activity,所以竖屏被销毁时,该方法就会被调用。
Bundle存取数据具体实现
内部维护了一个ArrayMap,具体定义是在其父类BaseBundle中;
HashMap:大小16位数组,空间不足时,空间将会以2倍的,采用链表结合结构进行数据存储。
ArrayMap:是一个<key,value>映射的数据结构,它设计上更多的是考虑内存的优化,内部是使用两个数组进行数据存储,一个数组记录key的hash值,另外一个数组记录Value值,它和SparseArray一样,也会对key使用二分法进行从小到大排序,在添加、删除、查找数据的时候都是先使用二分查找法得到相应的index,效率稿。
java程序在内存中的存储分配情况:
一、堆区: 1.存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令) 2.jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身
栈区: 1.每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象),对象都存放在堆区中 2.每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。 3.栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。
方法区: 1.又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。 2.方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。
二、内存分区而内存分为四个区:stack segment,heap segment,data segment,code segment;stack 区存放函数参数和局部变量;heap 区存放对象;data 区存放static 的变量或者字符串常量; code 区存放类中的方法;因此,静态变量是存放在data区的 !
内存泄漏:
静态对象内存泄漏
static Object cc=null
在Dalvik虚拟机中,static变量所指向的内存引用,如果不把它设置为null,那么JVM虚拟机就会在内存中一直保留这个对象,这个对象不会被垃圾回收器清理,GC是永远不会回收这个对象的,直到应用退出。
非静态内部类内存泄漏
1.编译器自动为内部类添加一个成员变量, 这个成员变量的类型和外部类的类型相同, 这个成员变量就是指向外部类对象的引用
2.编译器自动为内部类的 构造方法添加一个参数, 参数的类型是外部类的类型, 在构造方法内部使用这个参数为1中添加的成员变量赋值;
3.在调用内部类的构造函数初始化内部类对象时, 会默认传入外部类的引用。
越来越多的内存得不到回收的时候,最终就有可能导致内存溢出,下面说一下使用staitc属性所导致的内存泄漏的问题。
常用的Utils中的static修饰
public class ToastUtil {
private static Toast toast;
public static void show(Context context, String message) {
if (toast == null) {
toast = Toast.makeText(context, message, Toast.LENGTH_SHORT);
} else {
toast.setText(message);
}
toast.show();
}
}
分析:
static修饰的toast对象,在show方法中这个对象同时对context持有了引用。toast是static修饰的,意味着toast将不会从内存中消失,那么其持有的引用对象context将一直保持强引用,也不会在内存中消失。如果传个占用大内存的Activity的context进来,那么将会导致大量的内存泄漏。
解决方案:
将context改为context.getApplicationContext(),由于ApplicationContext是一个全局的context,会在app运行期间一直存在,就不存在内存泄露的问题了
Handler造成的内存泄漏
public class MainActivity extends AppCompatActivity
{
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//...更新UI操作
}
};
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
....
....
initDatas();
}
private void initDatas()
{
//...子线程获取数据,在主线程中更新UI
Message message = Message.obtain();
mHandler.sendMessage(message);
}
}
分析:
这种创建Handler的方式会造成内存泄漏,由于mHandler是Handler的非静态匿名内部类的实例,持有外部类Activity的引用。消息队列是在一个Looper线程中不断轮询处理消息,如果当前Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,而mHandler又持有Activity的引用,导致Activity的内存资源无法回收,引发内存泄漏。而别的匿名内部类比如new OnClickListener()因为得到及时回收并非一直被触发,所以会被回收。
解决方案:(新建内部类handler)最后:
mHandler.removeCallbacksAndMessages(null);
public class MainActivity extends AppCompatActivity
{
private MyHandler mHandler = new MyHandler(this);
private static class MyHandler extends Handler {
private WeakReference<Context> reference;
public MyHandler(Context context) {
reference = new WeakReference<>(context);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = (MainActivity) reference.get();
if(activity != null)
{
//...更新UI操作
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
...
...
initDatas();
}
private void initDatas()
{
//...子线程获取数据,在主线程中更新UI
Message message = Message.obtain();
mHandler.sendMessage(message);
}
@Override
protected void onDestroy() {
super.onDestroy();
//移除消息队列中所有消息和所有的Runnable
mHandler.removeCallbacksAndMessages(null);
}
}
静态内部类:
1.静态内部类是不需要依赖于外部类的
2.它不能使用外部类的非static成员变量或者方法(在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象)
非静态内部类:
1.成员内部类可以无条件访问外部类的所有成员属性和成员方法,包括private成员和静态成员。
2.当成员内部类拥有和外部类同名的成员变量或者方法时,外部类中的属性和方法会发生隐藏现象,即默认情况下访问的是成员内部类的成员,可以通过下面这种形式对外部类的属性和方法进行访问,外部类.this.成员变量,外部类.this.成员方法。
3.外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问。
4.如果要创建成员内部类的对象,前提是必须存在一个外部类的对象
局部内部类:
1.局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
内部类的应用场景:
场景一:当某个类除了它的外部类,不再被其他的类使用时。我们说这个内部类依附于它的外部类而存在,可能的原因有:1、不可能为其他的类使用;2、出于某种原因,不能被其他类引用,可能会引起错误。等等。这个场景是我们使用内部类比较多的一个场景。(内部类可以看成代码隐藏机制)
场景二:当我们希望一个类必须继承多个抽象或者具体的类时,就只能用内部类来实现多重继承
内部类对象创建(成员内部类、局部内部类、匿名内部类和静态内部类):
Outter outter = new Outter(); Outter.Inner inner = outter.new Inner(); //必须通过Outter对象来创建
匿名内部类:
new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
}
就是匿名内部类的使用。代码中需要给按钮设置监听器对象,使用匿名内部类能够在实现父类或者接口中的方法情况下同时产生一个相应的对象,但是前提是这个父类或者接口必须先存在才能这样使用。当然像下面这种写法也是可以的,跟上面使用匿名内部类达到效果相同。
匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。
补充:
1. 构造方法的特征
它具有与类相同的名称;
它不含返回值;
它不能在方法中用 return 语句返回一个值;
当自定义了构造方法后,编译器将不再自动创建不带参数的构造方法 。
在构造方法里不含返回值的概念是不同于 “void” 的,在定义构造方法时加了 “void” ,结果这个方法就不再被自动调用了。
JVM是Java Virtual Machine(Java虚拟机)的缩写
synchronized 关键字,代表这个方法加锁,相当于不管哪一个线程(例如线程A),运行到这个方法时,都要检查有没有其它线程B(或者C、 D等)正在用这个方法(或者该类的其他同步方法),
1、线程安全:
指多个线程在执行同一段代码的时候采用加锁机制,使每次的执行结果和单线程执行的结果都是一样的,不存在执行程序时出现意外结果。
2、线程不安全:
是指不提供加锁机制保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。
Android开发过程中的版本适配问题?
Android4.4适配:
uri转path需要适配
Android5.0适配:
分包适配 -〉在5.0及以上在app的gradle文件中配置multiDexEnabled true即可,但是5.0以下需要倒入jar,然后在Application的attch方法中进行初始化
Android6.0:
权限适配 -〉敏感权限动态申请;
Android7.0:
Uri.fromFile()适配 -〉使用FileProvider进行适配;
Android出于安全考虑关闭了网络/拍照/录像系统广播;
Android8.0:
Service启动方式适配 -〉需要使用startForegroundService()启动服务;
Notification适配 -〉添加了渠道和组的概念;
软件安装适配 -〉Android8.0去掉了“允许未知来源”选项,需要用户手动确定,所以安装程序需要在AndroidManifest.xml文件中添加REQUEST_INSTALL_PACKAGES权限;
广播适配 -〉AndroidManifest.xml中注册的广播不能使用隐式,需要明确指定。
权限适配-〉读写权限分离
关于协程的概念
简单的介绍:协程又称微线程,是一个线程执行。协程看上去也是子程序,但是不同的是可以在子程序内部中断转而去执行其他子程序,然后在合适的时候再返回中断位置继续执行。
协程特点:
执行效率高:没有多线程的线程间切换的开销;
不需要多线程的锁机制:因为只有一个线程,所以不需要
面试题3.
synchronized和lock的区别?
synchronized会主动释放锁,而lock需要手动调用unlock释放锁;
synchronized是java内置的关键字,而lock是个java类;
面试题4.
Handler机制如何保证消息不错乱?消息延迟是如何实现的?Handler、Looper、MessageQueue三者对应关系?内存泄漏如何避免?Looper中的死循环为什么不会引器主线程ANR?
1.handler机制中多个handler共有一个looper不会错乱是因为在handler 发送消息的时候,会将当前的handler对象绑定到message的target属性上,然后在Looper取到消息后通过msg.target拿到之前的handler对象,然后调用handler的handleMessage方法。
2.消息延迟的原理:handler发送延迟消息,会将当前的延迟时间绑定到msg的when属性上,然后在循环MessageQUeue获取msg时判断如果当前有延迟就进行阻塞,通过计时器计算时间,时间通过系统启动计算时间,然后等待阻塞时间结束之后将其唤醒,在阻塞过程中会将之后的消息放在消息队列的头部去处理。
3.同一个线程中可以有多个Handler,只有一个Looper,而MessageQueue在looper中初始化的,所以也只有一个MessageQueue。因此对应关系是:Handler:Looper = 多对一,Looper:MeesageQueue = 一对一,Handler:MessageQueue = 多对一。
4.Handler的内存泄漏是由于Handler持有外部类的引用,使其无法释放。
解决办法:(1)定义成静态内部类,使其不持有外部类的引用;(2)可以使用弱引用;
还需要在外部类销毁的时候,移除所有的消息。
5.可以说整个应用的生命周期都是在looper.loop()控制之下的(在应用启动的入口main函数中初始化ActivityThread,Handler,Looper,然后通过handler和looper去控制初始化应用)。而looper.loop采用的是Linux的管道机制
在没有消息的时候会进入阻塞状态,释放CPU执行权,等待被唤醒。
真正会卡死主线程的操作是在回调方法onCreate/onStart/onResume等操作时间过长,会导致掉帧,甚至发生ANR,
looper.loop本身不会导致应用卡死。
面试题5.
开发过程中如果想替换第三方jar中的某个class文件,或者在开发时你的class文件与jar中的重名,但是你想使用自己的应该如何解决?如果你替换掉某个方法又该怎么解决?
可以获取到jar的源码或者将jar反编译获取到java项目,然后替换掉自己想要的.java文件或者方法;
方式二:可以通过类加载器将目标class替换成自己的class;
面试题6.
IO与NIO的区别?
第一点:IO是面向流的,NIO是面向缓冲区的。
IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。
NIO是面向缓存的。数据读取到一个缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且要确保当更多的数据读入缓冲区时,不要覆盖缓冲区中未处理的数据。
第二点:IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情。NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,在数据可读之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。
面试题7.
单例模式有几种写法以及各自的优劣?
1.饿汉式:
public class SingleInstance {
private static SingleInstance mInstance = new SingleInstance();
private SingleInstance(){}
public static SingleInstance getInstance(){
return mInstance;
}
}
缺点:存在内存损耗问题,如果当前类没有用到也会被实例化
2.懒汉式:
public class SingleInstance {
private static SingleInstance mInstance = null;
private SingleInstance(){}
public static SingleInstance getInstance(){
if(mInstance==null){
synchronized (SingleInstance.class){
if(mInstance==null){
mInstance = new SingleInstance();
}
}
}
return mInstance;
}
}
缺点:加了synchronized锁会影响性能
有次被问到为什么要有两次空判断?
第一次空判断和好理解,可以很大程度上减少锁机制的次数;
第二次判空是因为,如果a,b两个线程都到了synchronized处,而假设a拿到了锁,进入到代码块中创建了对象,然后释放了锁,由于b线程在等待锁,所以a释放后,会被b拿到,因此此时判空就保证了实例的唯一性。
3.静态内部类:
public class SingleInstance {
private SingleInstance(){}
public static SingleInstance getInstance(){
return Builder.mInstance;
}
private static class Builder{
private static SingleInstance mInstance = new SingleInstance();
}
}
优点:解决了内存浪费问题,同时也避免了加锁性能问题
为什么这种写法是线程安全的?
因为类加载过程是安全的,而静态变量是随着类的加载进行初始化的。
4.枚举形式:
public enum SingleInstance {
INSTANCE;
}
优点:不存在反射和反序列化的问题。
缺点:通过查看枚举类生成的class文件发现,有多少变量,就会在静态代码块中创建多少对象,所以不建议使用。
定义一些有意义的常量,如果不用枚举,怎么解决?
可以使用注解的形式,例如:
@IntDef({DataType.INT, DataType.STRING, DataType.FLOAT, DataType.DOUBLE, DataType.OBJECT})
public @interface DataType {
int INT = 0;
int STRING = 1;
int FLOAT = 2;
int DOUBLE = 3;
int OBJECT = 4;
}
面试题8.
ArrayList 和LinketList区别?hashmap的实现原理?hashmap与hashtable的区别?
ArrayList与LinketList差别:
ArrayList基于数组实现,所以get,set操作效率较高;
LinketList基于链表实现(双向链表),所以add,remove操作效率较高;
如何实现高效率的查询和插入结构?
二叉树或者散列表
HashMap实现原理:
hashmap是由数组+链表结构现实的。获取到key的hashcode,然后对数组长度取余,找到对应的数组位置index,然后在对应的链表中判断是否有当前key,从而进行查询/添加/替换等操作。
HashMap与HashTable区别:
面试题9.
gson序列化数据时如何排除某个字段?
方式一:给字段加上 transient 修饰符
方式二:排除Modifier指定类型的字段。这个方法需要用GsonBuilder定制一个GSON实例。
方式三:使用@expose(揭露,阐述,暴露)注解。没有被 @expose 标注的字段会被排除
面试题10.
ButterKnife与Xutils注解的区别?以及Retrofit中的注解是如何处理的?
ButterKnife采用的是编译时注解,在编译时生成辅助类,在运行时通过辅助类完成操作。编译时注解运行效率较高,不需要反射操作。
XUtils采用的是运行时注解,在运行时通过反射进行操作。运行时注解相对效率较低。
Retrofit与EventBus采用的都是运行时注解,也就是通过反射技术处理的。
面试题11.
jvm的类加载机制?
类加载分类:
BootstrapClassLoader(负责加载java_home中的jre/lib/rt.jar中的class,不是ClassLoader的子类)
ExtensionClassLoader(负责加载java平台中扩展的一些jar中的class)
AppClassLoader(负责加载classpath中指定的jar或class文件)
CustomClassLoader(自定义的classloader)
JVM的类加载机制采用的是双亲委派模型。
类加载过程:
由底层类加载器开始查找是否已经加载,如果底层已经加载,则视为已经加载
上层就无需再加载,避免重复加载。
如果没有加载,则向上层类加载器查找,以此类推,直到顶层类加载器。如果最后发现顶层类加载器也没有加载,则先交由顶层类加载器尝试加载,如果无法加载,则交由下层类加器加载,直至底层类加载器
如果还是无法加载,则JVM会抛出相应的类加载异常。
面试题12.
列举一些git版本控制的常用操作符?
面试题13.
AsyncTask内部也是通过线程池+Handler的方式实现的
AsyncTask的原理以及弊端?AsyncTask为什么要求在主线程加载,对象为什么要在主线程创建?
面试题14.
Android开发中的屏幕适配方案?
sw(smallestWidth最小宽度)适配;
通过修改系统的density值进行适配;
面试题15.
多线程中sleep和wait的区别?
sleep是Thread的静态方法;wait是Object中的方法;
sleep过程中不会释放锁,不会让出系统资源;wait会释放锁资源,将其放入等待池中,让出系统资源,让cpu可以执行其他线程;
sleep之后可以主动释放锁;wait需要手动去notify;
面试题16.
输出字符串中的第一个不重复的字符,例如:
“hello”输出 ‘h’
“abbac”输出 ‘c’
“abdabe”输出‘d’
利用LinketHashMap数组的有序性和键的唯一性来处理:
private void printChar(String source) {
if (source == null) {
return;
}
String soureTrim = source.replaceAll(" ", "");//去掉字符串中的所有空格
char[] chars = soureTrim.toCharArray();//拿到字符串对应的char[]
int length = chars.length;
//用map键的唯一性去处理记录重复数据,而选择LinkedHashMap是为了保证有序
LinkedHashMap<Character, Integer> map = new LinkedHashMap<>();
//循环检测或插入数据,然后通过value的值记录当前字符出现次数
for (int i = 0; i < length; i++) {
char key = chars[i];
Integer value = map.get(key);
if (value == null) {
map.put(key, 1);
} else {
map.put(key, value+1);
}
}
//value=1,说明只出现一次
Set<Character> keys = map.keySet();
for (Character key : keys) {
Integer integer = map.get(key);
if (integer == 1) {
System.out.println("current frist only char is = " + key);
break;
}
}
}
面试题17.
对有序int数组去重,并输出去重后的长度,并打印出来,要求时间复杂度为O(n),空间复杂度为O(1)。
例如:int[] array = {-1,0,0,2,4,4,4,6};
长度为:5,打印结果为:-1,0,2,4,6
面试题18.
假设有A,B,C三个线程,在A线程的执行过程中去执行B线程,并且等待B线程的执行结果,然后去执行C线程,然后当C线程执行完成后,返回结果给A线程。不阻塞线程,如何实现?(相关描述我也记不太清了,可能有些不准确,考点就是Future)
面试题19.
ThreadLocal作用?
ThreadLocal是一个线程内的数据存储类,可以通过它在指定线程中存储数据,并且只有在当前线程可以获取到存储的数据。通常当某些数据以线程为作用域并且不同线程具有不同的数据副本时使用。
通过查看源码可以知道,set方法会通过values()方法拿到当前线程的ThreadLocal数据(Thread类中有个成员变量专门存储ThreadLocal数据:ThreadLocal.Values localValues),在localValues内部有个数组Object[] table,用于存储ThreadLocal的值,而位置存储在ThreadLocal的reference的下一个位置。
而get方法就是通过当前线程的reference拿到localValues中table的位置,然后index+1获取数据。