阿里百度腾讯常见java和安卓面试题汇总

安卓部分

一,view的事件分发机制

dispatchTouchEvent 分发事件 onInterceptTouchEvent 拦截事件只有viewgroup才有,view和activity没 onTouchEvent 处理点击事件

  • 1,图解ACTION_DOWN 事件分发,如果面试的时候能把下面的图画出来。能增分不少 dispatchTouchEvent和 onTouchEvent的框里有个【true---->消费】的字,表示的意思是如果方法返回true,那么代表事件就此消费,不会继续往别的地方传了,事件终止

  • 2,红色的箭头代表ACTION_DOWN 事件的流向 蓝色的箭头代表ACTION_MOVE 和 ACTION_UP 事件的流向

二,Handler原理机制

Handler主要负责发送和接受消息,Looper负责不断轮询MessageQueue,有新的消息就交给Handler处理,如果轮询不到新的消息,那就自身就处于阻塞状态。 Handler简单图解

Handler铁三角
  • Handler android的消息机制就是指Handler机制,Handler机制的运行需要MeeageQueue和Looper的辅助。 λ MessageQueue:消息队列,用于将所有收到的消息以队列的形式进行排列, 并提供入队和出队的方法。在looper的构造函数中创建,因此一个Looper也就对应了一个MessageQueue. 通过enqueueMessage实现消息的入队,通过next方法实现出队
  • Looper 轮询器 作用:与当前线程绑定,保证一个线程只会有一个Looper实例,同时一个Looper实例也只有一个- MessageQueue。Looper 通过loop()方法调用messagequeue的next方法,不断从 MessageQueue中去取消息

详解单个handler原理

图解多个handler原理

Handler的创建流程
  • 1、首先Looper.prepare()在本线程中保存一个Looper实例,然后该实例中保存一个MessageQueue对象;因为Looper.prepare()在一个线程中只能调用一次,所以MessageQueue在一个线程中只会存在一个。
  • 2、Looper.loop()会让当前线程进入一个无限循环,不端从MessageQueue的实例中读取消息,然后回调msg.target.dispatchMessage(msg)方法。
  • 3、Handler的构造方法,会首先得到当前线程中保存的Looper实例,进而与Looper实例中的MessageQueue想关联。
  • 4、Handler的sendMessage方法,会给msg的target赋值为handler自身,然后加入MessageQueue中。
  • 5、在构造Handler实例时,我们会重写handleMessage方法,也就是msg.target.dispatchMessage(msg)最终调用的方法。 好了,总结完成,大家可能还会问,那么在Activity中,我们并没有显示的调用Looper.prepare()和Looper.loop()方法,为啥Handler可以成功创建呢,这是因为在Activity的启动代码中,已经在当前UI线程调用了Looper.prepare()和Looper.loop()方法。

四,mvc,mvp,mvvm

三个架构模式:

  • MVC:Model-View-Controller,经典模式,很容易理解,主要缺点有两个: 1,View对Model的依赖,会导致View也包含了业务逻辑; 2,Controller会变得很厚很复杂。
  • MVP:Model-View-Presenter,MVC的一个演变模式,将Controller换成了Presenter, 主要为了解决上述第一个缺点,将View和Model解耦, 不过第二个缺点依然没有解决。
  • MVVM:Model-View-ViewModel,是对MVP的一个优化模式, 采用了双向绑定:View的变动,自动反映在ViewModel,反之亦然。 MVC, MVP, MMVM用来解决业务逻辑和视图之间的耦合
Mvc和mvp的最主要区别:
Mvc中model可以直接和view交互
mvp中model	与view	的交互由presenter完成
复制代码

Mvc,mvp,mvvm的优缺点

  • mvc是指用户触发事件的时候,view层会发送指令到controller层,然后controller去通知model层更新数据,model层更新完数据后会直接在view层显示结果。 对android来说 activity几乎承担了view层和controller层两种角色,并且和model层耦合严重,在逻辑复杂的界面维护起来很麻烦。

  • mvp模式下的activity只承担了view层的角色,controller的角色完全由presenter负责,view层和presenter层的通信通过接口实现,所以VP之间不存在耦合问题,view层与model也是完全解耦了。 presenter复用度高,可以随意搬到任何界面。 mvp模式下还方便测试维护: 但是mvp的问题在于view层和presenter层是通过接口连接,在复杂的界面中,维护过多接口的成本很大。

  • Mvvm 的优点: 1,和mvp一样,方便进行单元测试 2,便于代码的移植

  • Mvvm的缺点 1,类会增多 2,viewModel会越来越庞大 3,调用复杂度增加

一,LRUCache原理和部分源码解析

图片的三级缓存:也就是加载图片的时候首先从内存缓存中取,如果没有再从文件缓存中取, 如果文件缓存没有取到,就从网络下载图片并且加入内存和文件缓存 LRU:Least Recently Used,最近最少使用算法。当内存缓存达到设定的最大值 时将内存缓存中近期最少使用的对象移除,有效的避免了OOM的出现。

LruCache中Lru算法:通过LinkedHashMap来实现的。LinkedHashMap继承于HashMap,它使用了一个双向链表来存储Map中的Entry顺序关系,这种顺序有两种, 一种是LRU顺序, 一种是插入顺序, 这可以由其构造函数

public LinkedHashMap(int initialCapacity,float loadFactor, boolean accessOrder)指定
复制代码

所以,对于get、put、remove等操作,LinkedHashMap除了要做HashMap做的事情,还做些调整Entry顺序链表的工作。 LruCache中将LinkedHashMap的顺序设置为LRU顺序来实现LRU缓存,每次调用get(也就是从内存缓存中取图片),则将该对象移到链表的尾端。调用put插入新的对象也是存储在链表尾端,这样当内存缓存达到设定的最大值时,将链表头部的对象(近期最少用到的)移除

部分源码解析:LruCache的初始化构造方法

public LruCache(int maxSize) {
    if (maxSize <= 0) {
        throw new IllegalArgumentException("maxSize <= 0");
    }
    this.maxSize = maxSize;
    this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
    //初始容量为零,0.75是加载因子,
    表示容量达到最大容量的75%的时候会
    把内存增加一半。最后这个参数至关重要
    表示访问元素的排序方式,true表示按照
    访问顺序排序,false表示按照插入的顺序排序
}
复制代码

二,LinkedHashMap详解

LinkedHashMap底层数据结构由链表和哈希表组成。由链表保证元素有序。由哈希表保证元素唯一

LinkedHashMap的两种排序

1,按插入顺序排序(默认) 2,访问顺序排序(lru是这种排序) LinkedHashMap重写了父类HashMap的get方法,实际在调用父类getEntry()方法取得查找的元素后,再判断当排序模式accessOrder为true时,记录访问顺序,将最新访问的元素添加到双向链表的表头,并从原来的位置删除。由于的链表的增加、删除操作是常量级的,故并不会带来性能的损失

三,HashMap和ConcurrentHashMap的原理

1,原理 HashMap的原理:hashmap本质数组加链表。根据key取得hash值,然后计算出数组下标,如果多个key对应到同一个下标,就用链表串起来,新插入的在前面 ConcurrentHashMap原理:在hashMap的基础上,ConcurrentHashMap将数据分为多个segment(类似HashTable),默认16个(concurrency level),然后每次操作对一个segment加锁,避免多线程锁得几率,提高并发效率

2,Hashmap的源码: 构造函数,空参或者单参时都会调用两个参数的

public HashMap(int initialCapacity, float loadFactor) {  
    int capacity = 1;  
    while (capacity < initialCapacity)  
        capacity <<= 1;  
  
    this.loadFactor = loadFactor;  
    threshold = (int)(capacity * loadFactor);  
    table = new Entry[capacity];  
    init();  
}  
复制代码

有3个关键参数: capacity:容量,就是数组大小 loadFactor:比例,用于扩容 threshold:=capacity*loadFactor   最多容纳的Entry数,如果当前元素个数多于这个就要 扩容(capacity扩大为原来的2倍)

Get方法:根据key算hash值,再根据hash值取得数组下标,通过数组下标取出链表,遍历链表用equals取出对应key的value

public V get(Object key) {  
    if (key == null)  
        return getForNullKey();  
    int hash = hash(key.hashCode());  
    for (Entry<K,V> e = table[indexFor(hash, table.length)];  
         e != null;  
         e = e.next) {  
        Object k;  
        if (e.hash == hash && ((k = e.key) == key || key.equals(k)))  
            return e.value;  
    }  
    return null;  
}  
复制代码

3,HashMap和ConcurrentHashMap,hashtable的区别

HashMap:线程不安全,效率高
ConcurrentHashMap:线程安全,效率高,默认提升16倍
Hashtable:线程安全,效率低
复制代码

四,SparseArray原理 SparseArray是android里为<Interger,Object>这样的Hashmap而专门写的类,目的是提高内存效率,其核心是折半查找函数(binarySearch)。注意内存二字很重要,因为它仅仅提高内存效率,而不是提高执行效率 它要比 HashMap 节省内存,结构比HashMap简单(SparseArray内部主要使用两个一维数组来保存数据,一个用来存key,一个用来存value)不需要额外的数据结构(主要是针对HashMap中的HashMapEntry而言的)。

五,系统启动流程

Zygote进程 –> SystemServer进程 –> 各种系统服务 –> 应用进程

当所有的服务都启动完毕后,SystemServer会打印出“Making services ready”,然后通过ActivityManager启动Home界面,并发送“ACTION_BOOT_COMPLETED”广播消息

六,App启动、Application生命周期

app的两种启动方式

1,冷启动

系统会重新创建一个新的进程分配给它,所以会先创建和初始化Application类,再创建和初始化MainActivity类(包括一系列的测量、布局、绘制),最后显示在界面上

2,热启动

进程在后台运行,所以热启动就不会走Application这步了,而是直接走MainActivity(包括一系列的测量、布局、绘制),所以热启动的过程只需要创建和初始化一个MainActivity就行了,而不必创建和初始化Application,因为一个应用从新进程的创建到进程的销毁,Application只会初始化一次。

App启动流程

用户点击app后会通知 ActivityManagerService 启动应用的入口 Activity, ActivityManagerService 发现这个应用还未启动,则会通知 Zygote 进程孵化出应用进程,然后在这个应用进程里执行 ActivityThread 的 main 方法。应用进程接下来通知 ActivityManagerService 应用进程已启动,ActivityManagerService 保存应用进程的一个代理对象,这样 ActivityManagerService 可以通过这个代理对象控制应用进程,然后 ActivityManagerService 通知应用进程创建入口 Activity 的实例,并执行它的生命周期函数。

上面的启动流程是 Android 提供的机制,我们只能在创建入口 Activity 的实例这里做文章,正常Main Activity 的启动流程:

-> Application 构造函数
-> Application.attachBaseContext()
-> Application.onCreate()
-> Activity 构造函数
-> Activity.setTheme()
-> Activity.onCreate()
-> Activity.onStart
-> Activity.onResume
-> Activity.onAttachedToWindow
-> Activity.onWindowFocusChanged
复制代码

统计app启动时长的两种方式

1,本地通过adb命令行

adb shell am start -w packagename/activity 输入adb shell am start -W

com.qcl/com.qcl.MainActivity得到下面结果
Activity: com.qcl/.MainActivity
ThisTime: 83
TotalTime: 83
WaitTime: 94
TotalTime是我们真正的启动时间
复制代码
2,通过收集log(可以用来获取线上启动时间)

开始时间: 冷启动在Application的attachBaseContext 热启动在入口activity的onRestart中 结束时间:在入口activity的onWindowFocusChanged

app的启动优化:

基于上面的启动流程我们尽量做到如下几点

  • 1.Application的创建过程中尽量少的进行耗时操作
  • 2.如果用到SharePreference,尽量在异步线程中操作
  • 3.减少布局的层次,并且生命周期回调的方法中尽量减少耗时的操作
app启动遇见黑屏或者白屏问题
  • 1,产生原因  其实显示黑屏或者白屏实属正常,这是因为还没加载到布局文件,就已经显示了window窗口背景,黑屏白屏就是window窗口背景
  • 2,解决办法 通过设置设置Style  (1)设置背景图Theme  通过设置一张背景图。 当程序启动时,首先显示这张背景图,避免出现黑屏
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> 
    <item name="android:screenOrientation">portrait</item> 
    <item name="android:windowBackground">>@mipmap/splash</item> 
  
    <item name="android:windowIsTranslucent">true</item> 
    <item name="android:windowNoTitle">true</item> 
</style>
复制代码

设置透明Theme  通过把样式设置为透明,程序启动后不会黑屏而是整个透明了,等到界面初始化完才一次性显示出来

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> 
    <item name="android:windowNoTitle">true</item> 
    <item name="android:windowBackground">@android:color/transparent</item> 
    <item name="android:windowIsTranslucent">true</item> 
    <item name="android:screenOrientation">portrait</item> 
  </style>
复制代码
两者对比:

Theme1 程序启动快,界面先显示背景图,然后再刷新其他界面控件。给人刷新不同步感觉。  Theme2 给人程序启动慢感觉,界面一次性刷出来,刷新同步

Application和Process进程

当application在Linux平台开启时,系统会给这个application创建一个进程(process)来运行同时分配内存资源给该application,当程序结束运行时,该进程结束系统回收内存资源。 一个Application就是一个应用,在应用启动时,android会启动一个linux进程和一个主线程,这个进程以应用的包名命名,在默认的情况下,这个Application下的Activity、service、provider、receiver等组件都在这个进程中运行

Application的生命周期
  • 1、onCreate() 在创建应用程序时创建
  • 2、onTerminate() 在模拟环境下执行。当终止应用程序对象时调用,不保证一定被调用,当程序是被内核终止以便为其他应用程序释放资源,那么将不会提醒,并且不调用应用程序的对象的onTerminate方法而直接终止进程。
  • 3、onLowMemory() 低内存时执行。好的应用程序一般会在这个方法里面释放一些不必要的资源来应付当后台程序已经终止,前台应用程序内存还不够时的情况。 4、onConfigurationChanged(Configuration newConfig) 配置改变时触发这个方法。 5、onTrimMemory(int level)程序在进行内存清理时执行​

七,listview优化

1,convertView复用
2,viewholder使用
3,图片优化
4,getView()中不要写过多的逻辑代码,不要创建很多的对象,逻辑代码放在别的地方,
5,item的布局减少层级
6,通过getItemViewType实现复杂布局的复用
7,简单布局可以将listview的scrollingCache和animateCache属性设置false。
如果设置为true会提高显示效果,但是需要消耗更多内存和更长的初始化时间
复制代码

八,RecycleView

基本解决了ListView的一些缺陷,比如:需要手动重写ViewHolder,对于item的动画操作也需要很复杂的逻辑去实现等等 但是也有些缺点 不能简单的加头和尾  2. 不能简单的设置子item的点击事件 所以对于一些简单的列表还是可以用listview的。Recylerview可以用于实现一些复杂的列表。

优化 1,简化item布局 2,图片优化

九,JVM内存模型

1,什么是jvm

JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机。 JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行

2,JDK,JRE,JVM的作用及关系

(1)作用
	JVM:保证Java语言跨平台
	JRE:Java程序的运行环境
	JDK:Java程序的开发环境
(2)关系
	JDK:JRE+工具
	JRE:JVM+类库
简单而言:使用JDK开发完成的Java程序,交给JRE去运行,由JVM来保证跨平台。
复制代码

3,java代码具体执行过程如下图

4,jvm内存结构图(即运行时数据区)

  • 程序计数器:是一块较小的内存空间,可以看作是当前线程所执行的字节码的 行号指示器。为了能够使得每个线程都在线程切换后能够恢复在切换之前 的程序执行位置,每个线程都需要有自己独立的程序计数器,并且不能互相 被干扰,独立存储。 如果正在执行的是一个java方法,记录的是正在执行的虚拟机字节码指令的地址 如果正在执行的是个native方法,这个计数器的值为空。
  • 虚拟机栈:为虚拟机执行java方法(字节码)服务。 这里我们用的比较多的是局部变量表 局部变量表所需的内存空间在编译期间完成分配。当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的。在方法运行期间不会改变局部变量表的大小。
  • 本地方法栈:为虚拟机执行本地方法服务。 本地方法栈与虚拟机栈发挥的作用非常相似。区别如下 虚拟机栈:为虚拟机执行java方法(字节码)服务 本地方法栈:为虚拟机使用到的native方法服务
  • 堆:是用来存储对象本身的以及数组(数组引用是存放在Java栈中的)。堆是被所有 线程共享的,在JVM中只有一个堆。是java虚拟机管理的内存最大的一块。 在虚拟机启动时创建,
  • 方法区:和堆一样,是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息, 常量,静态变量和即时编译器编译后的代码数据等。这个区域的内存回收主要是针对 常量池的回收和对类型的卸载。
方法区有以下特点 
1,方法区是线程安全的。
2、方法区的大小不必是固定的,JVM可根据应用需要动态调整。
3、方法区也可被垃圾收集,当某个类不在被使用(不可触及)时,JVM将卸载这个类,进行垃圾收集
复制代码
堆和栈的主要区别:

栈--主要存放引用和基本数据类型。

堆--用来存放 new 出来的对象实例和数组。

线程和jvm

每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。 每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过

5,JVM执行程序的过程
  1. 加载.class文件
  2. 管理并分配内存
  3. 执行垃圾收集
6,jvm的生命周期
  • a) 启动。启动一个Java程序时,一个JVM实例就产生了,任何一个拥有public static void main(String[] args)函数的class都可以作为JVM实例运行的起点
  • b) 运行。main()作为该程序初始线程的起点,任何其他线程均由该线程启动。JVM内部有两种线程:守护线程和非守护线程,main()属于非守护线程,守护线程通常由JVM自己使用,java程序也可以表明自己创建的线程是守护线程
  • c) 消亡。当程序中的所有非守护线程都终止时,JVM才退出;若安全管理器允许,程序也可以使用Runtime类或者System.exit()来退出

十,GC的回收策略(垃圾回收策略机制)

垃圾回收:主要是对gc堆即Young Generation(年轻代)块和Old Generation(年老代)块内存进行回收,YG用来放新产生的对象,经过几次回收还没回收掉的对象往OG中移动,对YG进行垃圾回收又叫做MinorGC,对 OG垃圾回收又叫MajorGC,两块内存回收互不干涉

更细的划分为Eden和2个survivor space(即幸存区)

  • 1.eden空间: 大部分新对象在这里分配,有些大对象直接在年老区分配,这个区域进行回收后很多情况下会变空。
    1. two survivor 空间:即from survivor和to survivor。当eden区满时会将仍然存活的对象复制至其中一个survivor区,当这个区又满时将复制至另外一个survivor区,如下图:

十一,画出 Android 的大体架构图

十二,Dalvik,ART和jvm

Dalvik是Google公司自己设计用于Android平台的Java虚拟机

Dalvik和jvm的区别 Dalvik是基于寄存器的,运行dex文件 JVM是基于栈的,运行java字节码

Art:空间换时间 art模式需要在程序安装时进行预编译,将apk编译解析成机器码,运行速度相比dalvik模式快。 缺点:安装时间稍长,由于进行了预编译,所以会产生机器码,会占用存储空间。

十三,阿里面试手写单例模式

一般情况下直接使用饿汉式就好了,如果明确要求要懒加载(lazy initialization)会倾向于使用静态内部类,如果涉及到反序列化创建对象时会试着使用枚举的方式来实现单例。

  • 1,饿汉式 会在加载类后一开始就被初始化,即使客户端没有调用 getInstance()方法。饿汉式的创建方式在一些场景中将无法使用:譬如 Singleton 实例的创建是依赖参数或者配置文件的
Public class Singleton{
Private static final Singleton instance=new Singleton();
Private Singleton(){}
Public static Singleton getInstance(){
Return instance;
}
}
复制代码
  • 2,静态内部类 这种写法仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本
Public class Singleton{
Private static class SingletonHolder{
Private static final Singleton INSTANCE=new Singleton();
}
Private Singleton(){}
Public static final Singleton getInstance(){
Return SingletonHolder.INSTANCE;
}
}
复制代码
  • 3,枚举Enum 我们可以通过EasySingleton.INSTANCE来访问实例,这比调用getInstance()方法简单多了。创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象
Public enum Singleton{
INSTANCE;
}
复制代码

十四,安卓源码中用到的设计模式

  • 1,单例模式 保证在应用程序中,一个类Class只有一个实例存在 软键盘管理的 InputMethodManager和安卓系统级别的服务通常用的单例模 式,如WindowsManagerService、ActivityManagerService,更常用的是一个 LayoutInflater的类
  • 2,责任链模式---View事件分发机制 它使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的 耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象 处理它为止 具体体现在View的onTouchEvent方法中返回值的设置, 如果返回false,那么意味着当前的View不会是该次的责任人,将不会对其 持有;如果返回true,此时View会持有该事件并不再向外传递
  • 3,适配器模式----listview的BaseAdapter 将一个类的接口转换成客户希望的另外一个接口 以笔记本电源适配器为例,电源适配器将220V的电压转换到5v。
  • 4,观察者模式---将被观察者和观察者解耦 观察者模式是一种行为类模式,它定义对象间一种一对多的依赖关系,使得每当一个对 象改变状态,则所有依赖于它的对象都会得到通知并被自动更新 DataSetObserver就是一个观察者,它一旦发现BaseAdapter内部数据有变量,就会通过 回调方法DataSetObserver.onChanged和DataSetObserver.onInvalidated来通知 DataSetObserver的实现类。 比如安卓开源项目EventBus和rxjava的核心就是观察者模式
  • 5,工厂模式----BitmapFactory 静态工厂方法在Android中比较明显的例子应该就是BitmapFactory了,通过各种decodeXXX()就可以从不同渠道获得Bitmap对象
  • 6,建造者模式----AlertDialog.Builder 如果一个类的构造需要很多参数,而且这些参数并不都是必须的,那么这种情况下就比较适合Builder模式。比如构建一个AlertDialog,标题、内容、取消按钮、确定按钮、中立按钮,你可能只需要单独设置几个属性即可
  • 7,策略模式----动画的插值器 策略模式就相当于一个影碟机,你往里面插什么碟子,就能放出什么电影 以动画为例,设置不同的插值器对象,就可以得到不同的变化曲线

十五,多线程相关

  • 1,进程和线程的区别 进程:表示一个运行的程序 线程:进程(程序)的执行单元,执行路径

多进程的意义? 提高CPU的使用率 多线程的意义? 提高应用程序的使用率

  • 2,多线程的三种实现方式
1,继承Thread类,
2,实现Runnable接口(推荐,方便的实现资源的共享)
3,通过Callable和Future创建线程
复制代码
  • 3,start和run方法的区别 start会先启动线程,再由jvm调用run方法 run方法只是thread的一个普通方法调用,还是在主线程里执行。

  • 4,线程池 程序启动一个新线程成本比较高,因为它涉及到要与操作系统进行交互,而使用线程池可以很好的提高性能,尤其是当程序要创建大量生存期很短的线程时,更应该使用线程池。 线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。 JKD5之前,我们手动实现自己的线程池,JDK5以后,java内置支持线程池。

代码演示	//创建一个线程池对象,控制要创建几个线程对象
		ExecutorService pool=Executors.newFixedThreadPool(2);
		//可以执行Runnable对象或者Callable对象的线程
		pool.submit(new MyRunnable());
		pool.submit(new MyRunnable());
		//结束线程池
		pool.shutdown();
复制代码
  • 5,synchronized与Lock的区别 synchronized关键字是防止多个线程同时执行一段代码,那么就会很影响程序执行效率 Lock和synchronized有以下几点不同:
1)Lock是一个接口,jdk5后出现,而synchronized是Java中的关键字,synchronized是内置的语言实现;
2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
5)Lock可以提高多个线程进行读操作的效率。
复制代码

  在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择

  • 6,volatile关键字 volatile关键字修饰变量,用来确保将变量的更新操作通知到其他线程 在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized关键字更轻量级的同步 一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义: 1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。 2)禁止进行指令重排序

  • 7,ThreadLocal ThreadLocal类的作用:是为每个线程都创建一个变量副本, 每个线程都可以修改自己所拥有的变量副本, 而不会影响其他线程的副本. 其实这也是解决线程安全的问题的一种方法。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性 ThreadLocal原理:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本。

  • 8,死锁 死锁就是指两个或者两个以上的线程在执行过程中,因争夺资源产生的一种相互等待现象。(比如两个人吃饭,一人一根筷子)

		嵌套的代码体现
		if(x>20){
			synchronized(a){
				synchronized(b){...}
			}
		}else{
			synchronized(b){
				synchronized(a){...}
			}
		}
复制代码

十六,AsyncTask的底层实现

AsyncTask是对Handler与线程池的封装,AsyncTask的本质是一个线程池,所有提交的异步任务都会在这个线程池中的工作线程内执行,当工作线程需要跟UI线程交互时,工作线程会通过向在UI线程创建的Handler传递消息的方式,调用相关的回调函数,从而实现UI界面的更新

十七,进程间通信和同步

1,SP是进程同步的吗?有什么方法做到同步? Android本身的SP可以支持多进程,但是SP不能保证多进程间同步。因为当多个进程同时而又高频的调用SP的commit方法时,就会导致文件被反复覆盖写入,而并没有被及时读取,所以造成进程间数据的不同步 Android的ContentProvider是支持进程同步的,可以利用ContentProvider进行进程的sp同步 进程间通信的四种方式

  • 1,Activity   Activity的跨进程访问与进程内访问略有不同。虽然它们都需要Intent对象,但跨进程访问并不需要指定Context对象和Activity的 Class对象,而需要指定的是要访问的Activity所对应的Action(一个字符串)。有些Activity还需要指定一个Uri(通过 Intent构造方法的第2个参数指定)。        在android系统中有很多应用程序提供了可以跨进程访问的Activity,例如,下面的代码可以直接调用拨打电话的Activity。 Intent callIntent = new  Intent(Intent.ACTION_CALL, Uri.parse("tel:12345678" );   startActivity(callIntent);

  • 2,Content Provider 可以跨进程访问其他应用程序中的数据(以Cursor对象形式返回),当然,也可以对其他应用程序的数据进行增、删、改操 作

  • 3,广播 可以向android系统中所有应用程序发送广播,而需要跨进程通讯的应用程序可以监听这些广播

  • 4,AIDL Service和Content Provider类似,也可以访问其他应用程序中的数据,但不同的是,Content Provider返回的是Cursor对象,而Service返回的是Java对象,这种可以跨进程通讯的服务叫AIDL服务

十八,即时通讯和推送的3种实现方案

  • 1,通过轮询:太low,还消耗性能,消耗流量
  • 2,长连接:这个方案可以解决由轮询带来的性能问题,但是还是会消耗手机的电池 并且我们本地的服务很容易被安卓系统在低内存时杀死。长连接需要依赖心跳机制
  • 3,采用MQTT协议实现Android推送:耗电量小 MQTT协议较之XMPP更为轻量级,其连接的建立与传输的开销都非常小,非常精简,非常适合大量节点在弱网络环境的场景,发布/订阅的模式也比较易于扩展 XMPP是PC时代的产物,其底层通讯的数据格式的XML,数据冗余性太高(约70%),比较耗流量,并且在复杂的移动网络环境下会遇到各种各样的问题。

十九,Bitmap相关

1,android 色彩模式说明:

ALPHA_8:   每个像素占用1byte内存。
ARGB_4444:  每个像素占用2byte内存
ARGB_8888:  每个像素占用4byte内存
RGB_565:    每个像素占用2byte内存
复制代码

假设一张10241024,模式为ARGB_8888的图片,那么它占有的内存就是:10241024*4 = 4MB Android默认的色彩模式为ARGB_8888,通常我们优化Bitmap时,当需要做性能优化或者防止OOM(Out Of Memory),我们通常会使用Bitmap.Config.RGB_565这个配置,因为Bitmap.Config.ALPHA_8只有透明度,显示一般图片没有意义,Bitmap.Config.ARGB_4444显示图片不清楚,Bitmap.Config.ARGB_8888占用内存最多

  • 2,使用Bitmap时的一些注意事项 不用的Bitmap及时释放
if (!bmp.isRecycle()) {
    bmp.recycle();   //回收图片所占的内存
    bitmap = null;
    system.gc();  //提醒系统及时回收
}
复制代码

虽然调用recycle()并不能保证立即释放占用的内存,但是可以加速Bitmap的内存的释放。 释放内存以后,就不能再使用该Bitmap对象了,如果再次使用,就会抛出异常。所以一定要保证不再使用的时候释放。比如,如果是在某个Activity中使用Bitmap,就可以在Activity的onStop()或者onDestroy()方法中进行回收

  • 3,圆形bitmap的实现原理 主要靠paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));这行代码实现圆形图片 SRC_IN这种模式,取两个绘制的效果叠加后的交集,第一个绘制的是个圆形,第二个绘制的是个Bitmap,于是交集为圆形,展现的是BItmap,就实现了圆形图片效果

转载于:https://juejin.im/post/5a93744c6fb9a0636263f574

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值