Android和java知识点总结

Android和java知识点总结(均为网络转载,方便复习和学习)

1 java基础部分

1.1 JVM部分

1.2 JVM内存区域(运行时数据区域):

Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都各有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁。
在这里插入图片描述

1.3 JVM内存区域(虚拟机栈):

见文章:https://www.jianshu.com/p/ecfcc9fb1de7

1.4 JVM内存区域(程序计数器):

见文章:https://www.jianshu.com/p/77c882f47b29

1.5 JVM内存区域(本地方法栈):

见文章:https://www.jianshu.com/p/8a775d747c47

1.6 JVM内存区域(Java 堆):

见文章:https://www.jianshu.com/p/702eddcac053

1.7 JVM内存区域(方法区):

见文章:https://www.jianshu.com/p/59f98076b382

1.8 GC算法

见文章:https://blog.csdn.net/yanghw117/article/details/80903927

1.9 java类加载机制

见文章:https://www.cnblogs.com/ityouknow/p/5603287.html

1.10 反射

见文章:https://blog.csdn.net/sinat_38259539/article/details/71799078

1.11 注解

见文章:https://www.cnblogs.com/yangming1996/p/9295168.html

1.12 泛型

使用泛型的意义在于
1,适用于多种数据类型执行相同的代码(代码复用)
2, 泛型中的类型在使用时指定,不需要强制类型转换(类型安全,编译器会检查类型)
见文章:https://www.jianshu.com/p/986f732ed2f1

1.13 枚举

见文章:https://blog.csdn.net/qq_34988624/article/details/86592229

1.12 Java面向对象的三种特性

(转自:https://www.jianshu.com/p/3a01dee87561
(1)首先,要引申出类:类是面向对象中的一个很重要的概念,因为类是很多个具有相同属性和行为特征的对象所抽象出来的,对象是类的一个实例。
(2)面向对象共有三个特征:封装,继承,多态。
封装: 把一些属性和方法封装起来,形成一个类;可以隐藏实现细节。提高了代码的复用性。也提高了安全性。

封装的意义在于保护或者防止代码(数据)被我们无意中破坏。
封装的作用:
1、提高了代码的复用性。
2、隐藏了实现细节,还要对外提供可以访问的方式。便于调用者的使用。这是核心之一,也可以理解为就是封装的概念。
3、提高了安全性。

继承: 是指在一个现有类的基础上去构建一个新的类,构建出来的新类被称作子类,现有类被称作父类,子类能调用父类的非private修饰的成员,同时还可以自己添加一些新的成员,扩充父类,甚至重写父类已有的方法,使其表现符合子类的特征。

继承的好处:
1.提高了代码的可维护性
2.提高了代码的复用性
3.让类与类之间产生了继承关系

继承的弊端:
1.类与类之间的耦合度过高

继承特点:
1.java中类只能够单继承,不能多继承,可以多层继承

注:封装和继承其实有一个共同的目的就是代码重用。

多态: 多态是同一个行为具有多个不同表现形式的能力。多态性是对象多种表现形式的体现
Java作为面向对象的语言,同样可以描述一个事物的多种形态。

方法的重载和重写都体现了多态性。
类中多个方法的重载叫多态,父子类中方法的覆盖也叫多态。
多态有方法的多态和对象的多态(一个对象多种形态)。

1.13 Java面向对象面试题总结

见文章:https://blog.csdn.net/qq_31622345/article/details/78686559

1.14 多线程

线程池:https://www.jianshu.com/p/0f7f3e353dcf
面试题见文章:https://www.jianshu.com/p/00fb4d37d9cf

1.15 多线程三大特性

原子性:…
可见性:…
有序性:…
见文章:https://www.cnblogs.com/javatalk/p/9917348.html

1.16 HashMap

见文章:hashmap面试题原理、hashmapj基本实例 :

import java.util.HashMap;
/**
 * Created by landyChris on 2017/10/29.
 */
public class FHashMap<K,V> implements Map<K,V> {
    
    

    public static void main(String[] args) {
    
    
//        FHashMap FHashMap =new FHashMap();
//        FHashMap.put("速度和咯技术的好的",99999);
//        FHashMap.put("速度和咯技术的好的",3333);
//        FHashMap.put("快速度和咯技术的好的",555555);
//        FHashMap.put("加快速度和咯技术的好",99444999);
//        System.out.println(FHashMap.get("和咯技术的好的").toString()+";size="+ FHashMap.size);

        HashMap hashMap=new HashMap();
        hashMap.put("1","2222");
        hashMap.put("1","2222w");
        hashMap.put("1","2222d");
        hashMap.put("1","2222d2");
        hashMap.put("2","2222d2");
        System.out.println(hashMap.size());
    }
    //默认容量
    private static int defaultLength = 16;
    //默认加载因子
    private static double defualtLoaderFactor = 0.75;

    private Entry<K,V> table[] = null;
    private int size = 0;

    public FHashMap() {
    
    
        this(defaultLength,defualtLoaderFactor);
    }

    public FHashMap(int length, double loaderFactor) {
    
    
        defaultLength = length;
        defualtLoaderFactor = loaderFactor;
        table = new Entry[defaultLength];
    }

    private int hash(K k) {
    
    
        int m = defaultLength;
        int i = k.hashCode() % m;
        System.out.println("i:"+i);
        return i > 0 ? i : -i;
    }

    @Override
    public V put(K key, V value) {
    
    
        int index = hash(key);

        Entry<K,V> entry = table[index];
        if(entry == null) {
    
    
            table[index] = new Entry(key,value,null);
            size ++;
        }else {
    
    
            table[index] = new Entry(key,value,entry);
            System.out.println("oldVlaue=" + table[index].next.getValue()+"newValue="+table[index].getValue());
        }
        return table[index].getValue();
    }

    @Override
    public V get(K key) {
    
    
        int index = hash(key);
        if(table[index] == null) {
    
    
            return  null;
        }
        return find(table[index],key);
    }

    private V find(Entry<K, V> entry, K key) {
    
    

        if(key == entry.getKey()||key.equals(entry.getKey())) {
    
    
            if(entry.next != null) {
    
    
                System.out.println("oldValue1=" + entry.next.getValue());
            }
            return entry.getValue();
        }else {
    
    
            //不相等的时候,就直接递归去取下一个值
            if(entry.next != null) {
    
    
                System.out.println("oldValue2=" + entry.next.getValue());
                return find(entry.next,key);
            }
        }
        return null;
    }

    @Override
    public int size() {
    
    
        return size;
    }

    class Entry<K,V> implements Map.Entry<K,V> {
    
    
        K k;
        V v;

        Entry<K,V> next;

        public Entry(K k,V v,Entry<K,V> next) {
    
    
            this.k = k;
            this.v = v;
            this.next = next;
        }

        @Override
        public K getKey() {
    
    
            return k;
        }

        @Override
        public V getValue() {
    
    
            return v;
        }
    }

}

1.17 Arraylist

ArrayList小结
ArrayList是List接口的一个可变大小的数组的实现
ArrayList的内部是使用一个Object对象数组来存储元素的
初始化ArrayList的时候,可以指定初始化容量的大小,如果不指定,就会使用默认大小,为10
当添加一个新元素的时候,首先会检查容量是否足够添加这个元素,如果够就直接添加,如果不够就进行扩容,扩容为原数组容量的1.5倍
当在index处放置一个元素的时候,会将数组index处右边的元素全部右移
当在index处删除一个元素的时候,会将数组index处右边的元素全部左移

见文章:https://www.cnblogs.com/lierabbit/p/8383683.html
手写arraylist:

import java.util.Arrays;

public class ExtArrayList<E> {
    
    
    
    //ArrayList底层采用数组存放
    private Object[] elementData;
    //默认数组容量
    private static final int DEFAULT_CAPACITY = 10;
    //记录实际ArrayList大小
    private int size;
    
    
    //默认初始化容量为10
    public ExtArrayList() {
    
    
        this(DEFAULT_CAPACITY);
    }
    
    //ArryList指定数组初始的容量
    public ExtArrayList(int initialCapacity) {
    
    
        if (initialCapacity < 0) {
    
    
            throw new IllegalArgumentException("初始化容量不能小于0");
        }
        elementData = new Object[initialCapacity];
    }
    
    public void add(E e) {
    
    
        //1.判断实际存放的数据容量是否大于elementData
        ensureCapacityInternal(size + 1);
        //2.使用下标进行赋值
        elementData[size++] = e; 
    }
    
    public void add(int index, E e) {
    
    
        rangeCheck(index);
        ensureCapacityInternal(size + 1);
        System.arraycopy(elementData, index, elementData, index + 1, size - index);
        elementData[index] = e;
        size ++; 
    }
    
    // int minCapacity 当前 size + 1
    private void ensureCapacityInternal(int minCapacity) {
    
    
        if (size == elementData.length) {
    
    
//          //新数组容量大小
//          int newCapacity = 2 * size;
//          Object[] newObjects = new Object[newCapacity];
            //新旧数组复制
//          for (int i = 0; i < elementData.length; i++) {
    
    
//              newObjects[i] = elementData[i];
//          }
//          elementData = newObjects;
            //新旧数组复制
            int oldCapacity = elementData.length;
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            //最小扩容容量;防止初始容量大小为1
            if (newCapacity - minCapacity < 0) {
    
    
                newCapacity = minCapacity;
            }
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    }
    
    //使用下标获取数组元素
    public Object get(int index) {
    
    
        return elementData[index];
    }
    
    @SuppressWarnings("unchecked")
    E elementData(int index) {
    
    
        return (E) elementData[index];
    }
    
    public Object remove(int index) {
    
    
        rangeCheck(index);
        //1.使用下标查询该值是否存在
        Object object = get(index);
        //计算删除元素后面的长度
        int numMoved = size - index - 1;
        if (numMoved > 0) {
    
    
            //2.删除原理分析,将删除元素后面的所有元素往前移动一位
            System.arraycopy(elementData, index + 1, elementData, index, numMoved);
        }
        //将最后一个元素变为空
        elementData[--size] = null;
        return object;
    }
    
    // 删除相同元素 删除第一个
    public boolean remove(E e) {
    
    
        for (int i = 0; i < elementData.length; i++) {
    
    
            Object value = elementData[i];
            if (value.equals(e)) {
    
    
                remove(i);
                return true;
            }
        }
        return false;
    }
    
    //判断下标是否越界
    private void rangeCheck(int index) {
    
    
        if (index >= size)
            throw new IndexOutOfBoundsException("越界啦!");
    }
    
    public int getSize() {
    
    
        return size;
    }

}

1.18 List、Map、Set的理解(LinkedList和ArrayList、Vector和ArrayList、HashMap和HashTable和HashSet区别与使用)

转自:https://www.jianshu.com/p/64734a935588
List特点:元素有放入顺序,元素可重复
Map特点:元素按键值对存储,无放入顺序
Set特点:元素无放入顺序,元素不可重复(注意:元素虽然无放入顺序,但是元素在set中的位置是有该元素的HashCode决定的,其位置其实是固定的)
List接口有三个实现类:LinkedList,ArrayList,Vector
LinkedList:底层基于链表实现,链表内存是散乱的,每一个元素存储本身内存地址的同时还存储下一个元素的地址。链表增删快,查找慢
ArrayList和Vector的区别:ArrayList是非线程安全的,效率高;Vector是基于线程安全的,效率低
Set接口有两个实现类:HashSet(底层由HashMap实现),LinkedHashSet
SortedSet接口有一个实现类:TreeSet(底层由平衡二叉树实现)
Query接口有一个实现类:LinkList
Map接口有三个实现类:HashMap,HashTable,LinkeHashMap
HashMap非线程安全,高效,支持null;HashTable线程安全,低效,不支持null
SortedMap有一个实现类:TreeMap
其实最主要的是,list是用来处理序列的,而set是用来处理集的。Map是知道的,存储的是键值对
set 一般无序不重复.map kv 结构 list 有序

HashSet,存储object的集合,既然是集合,就不允许有重复元素。判断两个元素是否相同,是由hashCode与equals方法共同完成的。

1.19 为什么重写了equals()也要重写hashCode()

见文章:链接

1.20 Java字符串池(String Pool)深度解析

见文章:https://www.cnblogs.com/cold-windy/p/11514977.html

1.21 计算机网络知识

见文章:https://blog.csdn.net/vip97yigang/article/details/78306837
TCP/IP协议:https://blog.csdn.net/wuzhiwei549/article/details/105965493?utm_medium=distribute.pc_feed.none-task-blog-alirecmd-1.nonecase&depth_1-utm_source=distribute.pc_feed.none-task-blog-alirecmd-1.nonecase&request_id=

2 安卓部分

2.1 安卓系统架构

见文章:https://www.jianshu.com/p/cf260703f8ff

2.2 安卓性能优化

见文章:https://blog.csdn.net/github_37130188/article/details/89788571

2.3 APP启动优化

1、冷启动
App没有启动过或App进程被killed, 系统中不存在该App进程。需要创建App进程, 加载相关资源, 启动Main Thread, 初始化首屏Activity等。在这个过程中, 屏幕会显示一个空白的窗口(颜色基于主题), 直至首屏Activity完全启动。
2、热启动
热启动意味着你的App进程只是处于后台, 系统只是将其从后台带到前台, 展示给用户。在这个过程中, 屏幕会显示一个空白的窗口(颜色基于主题), 直至activity渲染完毕。
3、温启动
介于冷启动和热启动之间, 一般来说在以下两种情况下发生:
用户back退出了App, 然后又启动. App进程可能还在运行, 但是activity需要重建
用户退出App后, 系统可能由于内存原因将App杀死, 进程和activity都需要重启
启动优化主要是针对冷启动. 热启动和温启动都相对较快。

冷启动优化

2.4 安卓界面绘制原理

在这里插入图片描述
文章:https://blog.csdn.net/dream_caoyun/article/details/83412984?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.nonecase

视频:https://www.bilibili.com/video/BV1f7411p72Q?from=search&seid=8899746014938436225

2.5 安卓事件机制

在这里插入图片描述
见文章:https://segmentfault.com/a/1190000012227736

2.6 安卓app启动流程

在这里插入图片描述
见文章:https://www.cnblogs.com/net19880504/p/10931222.html

2.7 recycleview原理

见文章:https://blog.csdn.net/qq_33275597/article/details/93849695
手写实现简单的recycleview:https://github.com/buder-cp/CustomView/tree/master/buder_DN_view/buderdn1920

multiType分析

见文章:https://blog.csdn.net/hyb1234hi/article/details/78049460

2.8 设计模式

设计模式的七大原则:https://www.cnblogs.com/neverth/p/11760931.html
设计模式23种模式代码实现:https://quanke.gitbooks.io/design-pattern-java/content/

2.9 handler机制

在这里插入图片描述
见文章:https://www.jianshu.com/p/592fb6bb69fa
处理消息的线程,其实是发送handler所持有的Looper所在的线程。
消息是如何插入到MessageQueue中:
我们之前说,所有handler.post和handler.sendMessage都会调用到Handler的sendMessageDelayed方法,方法如下:

public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

这里逻辑就很简单了,直接调用了sendMessageAtTime方法,第一个参数为Message,第二个参数为SystemClock.uptimeMillis() + delayMillis,其中delayMillis为延时的时间,单位为毫秒,SystemClock.uptimeMillis() 为开机到现在的时间(不包括休眠时间),单位为毫秒。第二个参数主要是决定该Message在MessageQueue的顺序,比如现在开机时间为100s,发送一个延时20s的消息,则两者之和为120s; 过了5秒,又发了一个延时5s的消息,则两者只喝为105+5 = 110s。

题目:
Android中,有哪些是基于Handler来实现通信的?
答:App的运行、更新UI、AsyncTask、Glide、RxJava等
处理Handler消息,是在哪个线程?一定是创建Handler的线程么?
答:创建Handler所使用的Looper所在的线程
消息是如何插入到MessageQueue中的?
答: 是根据when在MessageQueue中升序排序的,when=开机到现在的毫秒数+延时毫秒数
当MessageQueue没有消息时,它的next方法是阻塞的,会导致App ANR么?
答:不会导致App的ANR,是Linux的pipe机制保证的,阻塞时,线程挂起;需要时,唤醒线程,应用卡死与Looper没有关系,应用在没有消息需要处理的时候,它是在睡眠,释放线程;卡死是ANR,而Looper是睡眠。
见文章:https://blog.csdn.net/qingtiantianqing/article/details/99557277
子线程中可以使用Toast么?
答:可以使用,但是Toast的显示是基于Handler实现的,所以需要先创建Looper,然后调用Looper.loop。
Looper.loop()是死循环,可以停止么?
答:可以停止,Looper提供了quit和quitSafely方法
Handler内存泄露怎么解决?
答: 静态内部类+弱引用 、Handler的removeCallbacksAndMessages等方法移除MessageQueue中的消息
handler.postDelayed函数延时执行计时是否准确?
答:当上一个消息存在耗时任务的时候,会占用延时任务执行的时机,此时是不准确的。那么如何准确执行延时任务呢,可以开启一个HandlerThread为一个专门的唯一的延时消息服务。
解析:根据源码说原因
由于Loop.loop里面消息是串行取出并发给handler.dispatchMessage的,而postDelayed函数调用的时候执行时间就已经算好了,这里假设调用postDelayed的当前时间SystemClock.uptimeMillis为1s,那么算上delayMillis就是1s+1s=2s,也就是系统开机后第2s,它应该在系统时间的第2s就应该被执行了,但因为消息执行是串行执行的,上一个runnable 里面调用了Thread.sleep(3000),也就是说上一个任务执行需要3s,系统时间一下就到了第3S,那么轮到处理第二个延时runnable的时候,MessageQueue类的next方法再执行到`
138行if(now < msg.when)的时候,now是系统开机后第3秒,而此时msg.whe 是开机后第2秒,那么就会走到下面的else分支,就立刻return了该msg,然后由handler.dispatchMessage处理,执行到该runnable的run方法,此时Log打印的时间就是3150毫秒了.

2.10 binder机制

见文章:https://blog.csdn.net/augfun/article/details/82343249

2.11 Java 运算符

见文章:https://www.runoob.com/java/java-operators.html

2.12 进制转换

见文章:https://jingyan.baidu.com/article/495ba84109665338b30ede98.html

2.13 java基本类型的范围

基本类型:byte 二进制位数:8
包装类:java.lang.Byte
最小值:Byte.MIN_VALUE=-128
最大值:Byte.MAX_VALUE=127

基本类型:short 二进制位数:16
包装类:java.lang.Short
最小值:Short.MIN_VALUE=-32768
最大值:Short.MAX_VALUE=32767

基本类型:int 二进制位数:32
包装类:java.lang.Integer
最小值:Integer.MIN_VALUE=-2147483648
最大值:Integer.MAX_VALUE=2147483647

基本类型:long 二进制位数:64
包装类:java.lang.Long
最小值:Long.MIN_VALUE=-9223372036854775808
最大值:Long.MAX_VALUE=9223372036854775807

基本类型:float 二进制位数:32
包装类:java.lang.Float
最小值:Float.MIN_VALUE=1.4E-45
最大值:Float.MAX_VALUE=3.4028235E38

基本类型:double 二进制位数:64
包装类:java.lang.Double
最小值:Double.MIN_VALUE=4.9E-324
最大值:Double.MAX_VALUE=1.7976931348623157E308

基本类型:char 二进制位数:16
包装类:java.lang.Character
最小值:Character.MIN_VALUE=0
最大值:Character.MAX_VALUE=65535

2.14 Arouter 原理

视频:https://www.bilibili.com/video/BV1wz4y197XD?from=search&seid=1188900803975595839

问题:
组件化开发有什么优势?
在这里插入图片描述
组件化路由是什么,有什么用,说说实现方式?
使用**注解处理器**扫描所有模块的activity并生成对应的代码,通过单例执行生成类的方法,把所有的activity添加到单例的路由表中。
自己写的组件化

2.15 热修复和插件化原理

见文章:https://www.jianshu.com/p/704cac3eb13d

2.16 横竖屏切换时 Activity 的生命周期

此时的生命周期跟清单文件里的配置有关系。
1.不设置 Activity 的 android:configChanges 时,切屏会重新调用各个生 命周期默认首先销毁当前 activity,然后重新加载。
2.设置 Activity
android:configChanges=“orientation|keyboardHidden|screenSize” 时,切
屏不会重新调用各个生命周期,只会执行 onConfigurationChanged 方法。

2.17 如 何 退 出 Activity ? 如 何 安 全 退 出 已 调 用 多 个 Activity 的 Application?

1、通常情况用户退出一个 Activity 只需按返回键,我们写代码想退出 activity 直接调用 finish()方法就行。
2、记录打开的 Activity:
每打开一个 Activity,就记录下来。在需要退出时,关闭每一个 Activity 即可。
3、发送特定广播:
在需要结束应用时,发送一个特定的广播,每个 Activity 收到广播后,关闭 即可。
//给某个 activity 注册接受接受广播的意图
registerReceiver(receiver, filter)
// 如 果 过 接 受 到 的 是 关 闭 activity 的 广 播 activity finish()掉
4、递归退出
就 调 用 finish() 方 法 把 当 前 的
在打开新的 Activity 时使用 startActivityForResult,然后自己加标志,在 onActivityResult 中处理,递归关闭。
5、其实 也可以通过 intent 的 flag 来实现
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) 激活一个新的 activity。
此时如果该任务栈中已经有该 Activity,那么系统会把这个 Activity 上面的所有 Activity 干掉。其实相当于给 Activity 配置的启动模式为 SingleTop。

2.18 请描述一下 Service 的生命周期

Service 有绑定模式和非绑定模式,以及这两种模式的混合使用方式。不同 的使用方法生命周期方法也不同。
" 绑定模式: 当第一次调用 startService 的时候执行的方法依次为
onCreate()、onStartCommand(),当 Service 关闭的时候调用 onDestory 方 法。
绑定模式:第一次 bindService()的时候,执行的方法为 onCreate()、 onBind()解除绑定的时候会执行 onUnbind()、onDestory()。
上面的两种生命周期是在相对单纯的模式下的情形。我们在开发的过程中还
必须注意 Service 实例只会有一个,也就是说如果当前要启动的 Service 已经存
在了那么就不会再次创建该 Service 当然也不会调用 onCreate()方法。
一个 Service 可以被多个客户进行绑定,只有所有的绑定对象都执行了
onBind()方法后该 Service 才会销毁,不过如果有一个客户执行了 onStart() 方法,那么这个时候如果所有的 bind 客户都执行了 unBind()该 Service 也不会 销毁。
在这里插入图片描述

2.19 什么是IntentService?有何优点?

一、IntentService 简介
IntentService 是 Service 的子类,比普通的 Service 增加了额外的功能。 先看 Service 本身存在两个问题:
Service 不会专门启动一条单独的进程,Service 与它所在应用位于同一个进 程中;
Service 也不是专门一条新线程,因此不应该在 Service 中直接处理耗时的 任务;
二、IntentService 特征
会创建独立的 worker 线程来处理所有的 Intent 请求;
会创建独立的 worker 线程来处理 onHandleIntent() 方法实现的代码,无需 处理多线程问题;
所有请求处理完成后,IntentService 会自动停止,无需调用 stopSelf()方法 停止 Service;
为 Service 的 onBind()提供默认实现,返回 null;
为 Service 的 onStartCommand 提供默认实现,将请求 Intent 添加到队列 中;

2.20 请介绍下 ContentProvider 是如何实现数据共享的

在 Android 中如果想将自己应用的数据(一般多为数据库中的数据)提供 给第三发应用,那么我们只能通过 ContentProvider 来实现了。
ContentProvider 是应用程序之间共享数据的接口。使用的时候首先自定义 一个类继承 ContentProvider,然后覆写 query 、 insert、 update、 delete 等
方法。因为其是四大组件之一因此必须在 AndroidManifest 文件中进行注册。
把自己的数据通过 uri 的形式共享出去,android 系 统 下 不 同 程 序 数 据 默 认 是 不 能 共 享 访 问 , 需 要 去 实 现 一 个 类 去 继 承 ContentProvider
public class PersonContentProvider extends ContentProvider{
public boolean onCreate(){
}
query(Uri, String[], String, String[], String)
insert(Uri, ContentValues)
update(Uri, ContentValues, String, String[])
delete(Uri, String, String[])
}
第三方可以通过 ContentResolver 来访问该 Provider。

2.21 为什么要用 ContentProvider?它和 sql 的实现上有什么差 别?

ContentProvider 屏蔽了数据存储的细节 ,内部实现对用户完全 透明,用户只需要关心操作数据的 uri 就可以了,ContentProvider 可以实现不同 app之间共享。Sql 也有增删改查的方法, 但是 sql 只能查询本应用下的数据库。而 ContentProvider 还可以去增删改查本地文件. xml 文件的读取等。

2.22 说说 ContentProvider、ContentResolver、ContentObserver 之间的关系

a. ContentProvider 内容提供者,用于对外提供数据
b. ContentResolver.notifyChange(uri)发出消息
c. ContentResolver 内容解析者,用于获取内容提供者提供的数据
d. ContentObserver 内容监听器,可以监听数据的改变状态
e. ContentResolver.registerContentObserver()监听消息。

2.23 使用 contentProvider 获取本地所有的音频文件

Android 中,系统为多媒体类型的文件(比如图片、音频、视频等)建立了
数据库(sqlite 数据库),将文件的名称、大小等信息存储起来,从而完成多媒体 数据的维护工作;所以我们需要实现建立一个实体类。
可以根据 ContentResover 获取到一个 Cursor,然后根据这个游标,遍历所
有的歌曲的信息,设置给实 体 类 ,得到你想要的音频文件。因为是从本地读取数 据,所以需要添加权限

<uses-permissionandroid:name="android.permission.READ_EXTERNAL_STORAGE"/> 

2.24 ListView 如何实现分页加载

①设 置 ListView 的 滚 动 监 听 器 : setOnScrollListener(newOnScrollListener{….})在监听器中有两个方法: 滚动状态发生变化的方法(onScrollStateChanged)和 listView 被滚动时调用的方法(onScroll)
② 在滚动状态发生改变的方法中,有三种状态:
手指按下移动的状态:SCROLL_STATE_TOUCH_SCROLL: // 触摸滑动 惯性滚动(滑翔(flgin)状态):SCROLL_STATE_FLING: // 滑翔
静止状态: SCROLL_STATE_IDLE: // 静止
对不同的状态进行处理:
分批加载数据,只关心静止状态:关心最后一个可见的条目,如果最后一个 可见条目就是数据适配器( 集 合 )里的最后一个,此时可加载更多的数据。在每次加载的时候,计算出滚动的数量,当滚动的数量大于等于总数量的时候,可以提 示用户无更多数据了。

2.25 如何在ScrollView 中如何嵌入 ListView

通常情况下我们不会在 ScrollView 中嵌套 ListView,但是如果面试官非 让我嵌套的话也是可以的。
在 ScrollView 添加一个 ListView 会导致 listview 控件显示不全,通常只会显示一条,这是因为两个控件的滚动事件冲突导致。所以需要通过 listview 中的item 数量去计算 listview 的显示高度,从而使其完整展示,现阶段最好的处理的方式是: 自定义 ListView,重载 onMeasure()方法,设置 全部显示。

2.26 Serializable 和 Parcelable 的 区别

在使用内存的时候, Parcelable 类比 Serializable 性能高, 所以推荐使用 Parcelable 类。
1.Serializable 在序列化的时候会产生大量的临时变量,从而引起频繁的 GC。
2.Parcelable 不能使用在要将数据存储在磁盘上的情况。尽管 Serializable 效率低点,但在这种情况下,还是建议你用 Serializable 。
实现:
1.Serializable 的实现,只需要继承 Serializable 即可。这只是给对象打
了一个标记,系统会自动将其序列化。
2.Parcelabel 的实现,需要在类中添加一个静态成员变量 CREATOR,这 个变量需要继承 Parcelable.Creator 接口。

2.27 如何避免 OOM 异常

OOM 内存溢出,想要避免 OOM 异常首先我们要知道什么情况下会导致 OOM 异常。
(1)图片过大导致OOM
Android 中 用 bitmap 时 很 容 易 内 存 溢 出 , 比 如 报 如 下 错 误 : Java.lang.OutOfMemoryError : bitmap size exceeds VM budget。解决方法:方法 1: 等比例缩小图片,方法 2:对图片采用软引用,及时地进行 recyle()操作,方法 3:使用加载图片框架处理图片,如专业处理加载图片的 ImageLoader 图 片加载框架。
(2)界面切换导致OOM
一般情况下,开发中都会禁止横屏的。因为如果是来回切换话,activity 的 生命周期会重新销毁然后创建。
有时候我们会发现这样的问题,横竖屏切换 N 次后 OOM 了。
这种问题没有固定的解决方法,但是我们可以从以下几个方面下手分析。
1、看看页面布局当中有没有大的图片,比如背景图之类的。
去除 xml 中相关设置,改在程序中设置背景图(放在 onCreate()方法中):
在 Activity destory 时注意,drawable.setCallback(null); 防止 Activity 得 不到及时的释放。
2、跟上面方法相似,直接把 xml 配置文件加载成 view 再 放 到 一 个 容 器 里, 然 后 直 接 调 用 this.setContentView(View view);方法,避免 xml 的重复加载。
3、 在页面切换时尽可能少地重复使用一些代码
比如:重复调用数据库,反复使用某些对象等等…
(3)查询数据库没有关闭游标
程序中经常会进行查询数据库的操作,但是经常会有使用完毕 Cursor 后 没 有关闭的情况。如果我们的查询结果集比较小,对内存的消耗不容易被发现,只有在常时间大量操作的情况下才会出现内存问题,这样就会给以后的测试和问题排 查带来困难和风险。
(4)构造Adapter 时,没有使用缓存的 convertView
在使用 ListView 的时候通常会使用 Adapter,那么我们应该尽可能的使用 ConvertView 。
为什么要使用convertView?
当 convertView 为空时,用 setTag()方法为 每个 View 绑定一个存放控件的ViewHolder 对象。当convertView 不为空,重复利用已经创建的 view 的时候,使用 getTag()方法获取绑定的 ViewHolder 对象,这样就避免了 findViewById 对控件的层层查询,而是快速定位到控件。
(5)Bitmap 对象不再使用时调用recycle()释放内存
有时我们会手工的操作 Bitmap 对象,如果一个 Bitmap 对象比较占内存,当它不再被使用的时候,可以调用 Bitmap.recycle()方 法回收此 对象的像素 所占 用的内存,但这不是必须的,视情况而定。
(6)其他
Android 应用程序中最典型的需要注意释放资源的情况是在Activity 的生命周期中,在 onPause()、onStop() 、 onDestroy()方法 中需 要适当的释 放资 源的情况。使用广播没有注销也会产生 OOM。

2.28 ANR 是什么?怎样避免和解决 ANR

在 Android 上,如果你的应用程序有一段时间响应不够灵敏,系统会向用 户显示一个对话框,这个对话框称作应用程序无响应(ANR:Application NotResponding ) 对话框。原因是主线程在接受到其他消息的时候没有时间去响应,它的时间都在处理那一个耗时的操作,造成点击事件没有办法响应,点击事件没有办法响应就容易出现ANR。
ANR 一 般 有 三 种 类 型 :
1:KeyDispatchTimeout(5 seconds) --主要类型按键或触摸事件在特定时间内无响应
2:BroadcastTimeout(10 seconds)BroadcastReceiver 在特定时间内无法处理完成
3:ServiceTimeout(20 seconds) --小概率类型Service 在特定的时间内无法处理完成
超时的原因一般有两种:
(1)当前的事件没有机会得到处理(UI 线程正在处理前一个事件没有及时完成或 者 looper 被某种原因阻塞住)
(2)当前的事件正在处理,但没有及时完成
UI 线程尽量只做跟 UI 相关的工作,耗时的工作(数据库操作,I/O,连接网络或者其他可能阻碍 UI 线程的操作)放入单独的线程处理,尽量用 Handler 来处 理 UI thread 和 thread 之间的交互。
查找 ANR 的方式:

  1. 导出/data/data/anr/traces.txt,找出函数和 调用过程, 分析代码
  2. 通过性能 LOG 人肉查找

2.29 Android 线程间通信有哪几种方式

 共享内存(变量);
 文件,数据库;
 Handler;
 Java 里的 wait(),notify(),notifyAll()

2.30 android 应用对内存是如何限制的?我们应该如何合 理使用内 存?

Android 应用的开发语言为 Java,每个应用最大可使用的堆内存受到 Android 系统的限制
•Android 每一个应用的堆内存大小有限
•通常的情况为 16M-48M
•通过 ActivityManager 的 getMemoryClass()来查询可用堆内存限制
•3.0(HoneyComb)以上的版本可以通过 largeHeap=“true”来申请更多 的堆内存
•NexueHeap 512
•如果试图申请的内存大于当前余下的堆内存就会引发 OutOfMemoryError()
•应用程序由于各方面的限制,需要注意减少内存占用,避免出现内存泄漏。

2.31 如何合理使用内存?

1、注意资源回收,像数据库,输入输出流,定位操作这样的对象,要在使 用完及时关闭流。
2、少 使用 静态 变量 ,因为 系统 将静 态变 量的优 先级 设定 的很 高, 会最后 回
收。所以可能因为静态变量导致该回收的没有回收。而回收了不该回收的内 存。
3、注意大图片的缩放,如果载入的图片很大,要先经过自己程序的处理,
降低分辨率等。最好设置多种分辨率格式的图片,以减少内存消耗。
4、动态注册监听,把一些只有显示的时候才使用到的监听放进程序内部, 而不是放在 manifesat 中去。
5、减少使用动画,或者适当减少动画的帧数。
6 、 注意 自己 的 程 序 逻辑, 在该 关闭 自己 程序的 控件 的时 候, 主动 关闭, 不 要交给系统去决定。(这个要自己把握好,也不是说都自己搞定,只有那些 自己确定需要关闭的对象,自己将其关闭。)

2.32 简述android 应用程序结构是哪些?

src 目录是源代码目录,所有允许用户修改的 java 文件和用户自己添加的 java 文件都保存在这个目录中
gen 目录是 1.5 版本新增的目录,用来保存 ADT 自动生成的 java 文件,例 如 R.java 或 AIDL 文件
注 意 :R.java 文件(非常重要)
a) R.java 文件是 ADT 自动生成的文件,包含对 drawable、layout 和 values目录内的资源的引用指针,Android 程序能够直接通过 R 类引用目录中的资源
b) R.java 文件不能手工修改,如果向资源目录中增加或删除了资源文件,则需 要在工程名称上右击,选择 Refresh 来更新 R.java 文件中的代码
c) R 类包含的几个内部类,分别与资源类型相对应,资源 ID 便保存在这些内部类中,例如子类 drawable 表示图像资源,内部的静态变量 icon 表示资源 名称,其资源 ID 为 0x7f020000 。一般情况下,资源名称与资源文件名相同android.jar 文件是 Android 程序所能引用的函数库文件,Android 通过平 台所支持 API 都包含在这个文件中assets 目录用来存放原始格式的文件,例如音频文件、视频文件等二进制格式文件。此目录中的资源不能被 R.java 文件索引。,所以只能以资截流的形式 读取。一般情况下为空layout 目录用来存放我们为每个界面写的布局文件Strings.xml 文件是程序中的一些字符串的引用
AndroidManifest.xml 是 XML 格式的 Android 程序声明文件,包含了Android 系统运行 Android 程序前所必须掌握的重要信息,这些信息包含应用程序名称、图标、包名称、模块组成、授权和 SDK 最低版本等,而且每个 Android 程序必须在根目录下包含一个 AndroidManifest.xml 文件
注:AndroidMainfest.xml 文件:

  1. AndroidManifest.xml 文件的根元素是 manifest,包含了
    xmlns:android、package、android:versionCode 和 android:versionName 共 4 个属性
  2. xmlns:android 定义了 Android 的命名空间,值为 http://schemas.android.com/apk/res/android
  3. package 定义了应用程序的包名称
  4. android:versionCode 定义了应用程序的版本号,是一个整数值,数值
    越大说明版本越新,但仅在程序内部使用,并不提供给应用程序的使用者
  5. android:versionName 定义了应用程序的版本名称,是一个字符串,仅 限于为用户提供一个版本标识
  6. manifest 元素仅能包含一个 application 元素,application 元素中能够
    声明 Android 程序中最重要的四个组成部分,包括 Activity、Service、
    BroadcastReceiver 和 ContentProvider,所定义的属性将影响所有组成部分
  7. android:icon 定义了 Android 应用程序的图标,其中@drawable/icon 是 一种资源 引用方式 ,表示资源类 型是图像 ,资源名称为 icon ,对应 的资 源文 件为 res/drawable 目录下的 icon.png
  8. android:label 则定义了 Android 应用程序的标签名称
    default.properties 文件记录 Android 工程的相关设置,该文件不能手动修 改,需右键单击工程名称,选择“Properties”进行修改

2.33 多线程间通信和多进程之间通信有什么不同,分别怎么实现?

一、进程间的通信方式

)管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只
能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
) 有名管道 (namedpipe) : 有名管道也是半双工的通信方式,但是它允许 无亲缘关系进程间的通信。
) 信 号 量 ( semophore ) : 信号量是一个计数器,可以用来控制多个进程对 共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其
他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的 同步手段。
) 消 息 队 列 ( messagequeue ) : 消息队列是由消息的链表,存放在内核中
并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无 格式字节流以及缓冲区大小受限等缺点。
) 信号 (sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个 事件已经发生。
)共 享 内 存 ( shared memory ) :共享内存就是映射一段能被其他进程所访问 的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最
快 的 IPC 方 式, 它 是 针 对其他 进程 间 通信 方 式 运行 效 率低 而 专门 设 计 的。它
往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
)套接字(socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同 的是,它可用于不同及其间的进程通信。

二、线程间的通信方式
)锁机制:包括互斥锁、条件变量、读写锁
*互斥锁提供了以排他方式防止数据结构被并发修改的方法。

*读写锁允许多个线程同时读共享数据,而对写操作是互斥的。

*条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。 # 信号量机制(Semaphore):包括无名线程信号量和命名线程信号量
信号机制(Signal):类似进程间的信号处理
线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于 数据交换的通信机制。

2.34 什么是 AIDL 以及如何使用

① aidl 是 Android interface definition Language 的英文缩写, 意思Android 接口定义语言。
②使用 aidl 可以帮助我们发布以及调用远程服务,实现跨进程通信。 ③将服务的 aidl 放到对应的 src 目录,工程的 gen 目录会生成相应的接口类
我们通过 bindService(Intent,ServiceConnect,int)方法绑定远程服务, 在 bindService 中有一个 ServiceConnec 接口, 我们需要覆写该类的
onServiceConnected(ComponentName,IBinder) 方法,这个方 法的第二个参
数 IBinder 对象其实就是已经在 aidl 中定义的接口,因此我们可以将 IBinder 对象强制转换为 aidl 中的接口类。
我们通过 IBinder 获取到的对象(也就是 aidl 文件生成的接口)其实是系统 产生的代理对象,该代理对象既可以跟我们的进程通信,又可以跟远程进程通信, 作 为一个中间的角色实现了进程间通信。

2.35 安卓屏幕适配方案

见文章:https://www.cnblogs.com/whycxb/p/9755012.html
适配框架:https://github.com/JessYanCoding/AndroidAutoSize/blob/master/README-zh.md

2.36 Git

见文章:https://gitee.com/all-about-git

2.37 Linux常用命令

见文章:https://blog.csdn.net/qq_24946689/article/details/105365912

2.38 Glide原理解析

见文章:https://www.jianshu.com/p/bce65d04d831
手写高并发图片加载框架:https://blog.csdn.net/sw5131899/article/details/66970195

2.39 webview内存泄露分析

见文章:https://www.jianshu.com/p/3e8f7dbb0dc7

2.40 BigDecimal一定不会失去精度吗?

BigDecimal一定不会失去精度吗?

2.4.1 service生命周期

在这里插入图片描述

服务生命周期从创建到销毁可以遵循两条不同的路径:

启动服务
该服务在其他组件调用 startService() 时创建,然后无限期运行,必须通过调用 stopSelf() 来自行停止运行或通过其他组件调用 stopService() 来停止服务。服务停止后,系统会将其销毁

绑定服务
服务在另一个组件(客户端)调用 bindService() 时创建。然后,客户端通过 IBinder 接口与服务进行通信。客户端可以通过调用 unbindService() 关闭连接。多个客户端可以绑定到相同服务,而且当所有绑定全部取消后,系统即会销毁该服务(服务不必自行停止运行)

这两条路径并非完全独立。也就是说,可以绑定到已经使用 startService() 启动的服务。例如,可以通过使用 Intent(标识要播放的音乐)调用 startService() 来启动后台音乐服务。随后,可能在用户需要稍加控制播放器或获取有关当前播放歌曲的信息时,Activity 可以通过调用 bindService() 绑定到服务。在这种情况下,除非所有客户端均取消绑定,否则 stopService() 或 stopSelf() 不会实际停止服务

需要注意的点:
1.通过startService启动的Service,只有调用了stopService(外部组件调用)或stopSelf(Service内部自己调用),才会停止。
2.通过startService启动的Service,在Service运行中无法与Service进行交互,即外部组件只能控制其开关,无法进行交互。
3.通过startService启动的Service,与外部组件之间没有关系,外部组件的生死跟它没有联系。
3.通过startService启动的Service,启动之后重复启动的话不会触发onCreate方法,但是会重复触发onStartCommand,其实貌似也算是数据交流了吧,不过是单向的。
4.bindService启动的Service表示将一个Service绑定到一个组件上,其生命周期与该组件的生命周期绑定在一起,比如绑到一个Activity,Activity在Destroy后Service跟着就Destroy了。
4.注意是不能绑定广播的,因为广播发完了其生命就到头了,常用的是绑Activity,还可以是Service。
5.bindService启动的Service在使用完之后可以解除绑定,当一个Service上的所有绑定的组件都解绑之后,它就会被销毁。
6.可以同时使用两种启动方式,此时的生命周期就变的有些复杂了,两种关联到一起,总结来说的话,先startService与先bindService两种方式达到的效果是一样的,即此时unbindService的话,Service并不会结束,而是要等到stopService才会结束(onDestroy);若是此时stopService,也不会结束,而是要等到unbindService时才会结束(由于已经调用过stopService,此时会直接onDestroy)。
7.onRebind调用时机:
当旧client与service之间的关联在onUnbind中都结束之后,新client绑定时,
必须是onUnbind返回true,且服务在解绑之后没有销毁

2.4.2 启动模式

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_24946689/article/details/106685369