Android 相关知识点搜集总结

android 常见问题及答案收集。
不断收集


HashMap实现原理,如果hashCode冲突怎么办,为什么线程不安全,与Hashtable有什么区别

  • 主要通过计算数据的hashCode来插入
  • hashCode相同的元素插入同一个链表,才用数组+链表方式存储
  • 可能会有多个线程同时put数据,若同时push了hashCode相同数据,后面的数据可能会将上一条数据覆盖掉
    Hashtable几乎在每个方法上都加上synchronized(同步锁),实现线程安全

synchronized 修饰实例方法和修饰静态方法有什么不一样

  • synchronized修饰普通方法时锁对象是this对象,而使用两个对象去访问,不是同一把锁

  • 当synchronized修饰静态方法时,锁对象为当前类的字节码文件对象。使用不同的对象访问,结果是同步的,因为当修饰静态方法时,锁对象是class字节码文件对象,而两个对象是同一个class文件,所以使用的是一个锁

final 、finally、finalize 区别

  • final关键字用于基本数据类型前:这时表明该关键字修饰的变量是一个常量,在定义后该变量的值就不能被修改。
    final关键字用于方法声明前:这时意味着该方法时最终方法,只能被调用,不能被覆盖,但是可以被重载。
    final关键字用于类名前:此时该类被称为最终类,该类不能被其他类继承。

  • 用try{}catch(){} 捕获异常时,无论室友有异常,finally代码块中代码都会执行。

  • finalize方法来自于java.lang.Object,用于回收资源。
    可以为任何一个类添加finalize方法。finalize方法将在垃圾回收器清除对象之前调用

Looper相关总结

  • Looper通过prepare方法进行实例化,先从他的成员变量sThreadLocal中拿取,没有的话就new 一个Looper,然后放到sThreadLocal中缓存。每个线程只能创建一个Looper实例
  • Looper通过loop方法开启循环队列,里面开启了死循环,没有msg时候会阻塞
  • 在ActivityThread的main方法中也就是Activity启动的时候,已经调用了Looper.prepareMainLopper()方法

ThreadLocal在Looper中的使用

  • 为了解决多个线程访问同一个数据问题,同步锁的思路是线程不能同时访问一片内存区域.而ThreadLocal的思路是,干脆给每个线程Copy一份一摸一样的对象,线程之间各自玩自己的,互相不影响对方
    (简而言之:先拿到当前线程,再从当前线程中拿到ThreadLocalMap,通过ThreadLocalMap来存储数据。(ThreadLocalMap是Thread的成员变量))

Service 和 IntentService

Activity对事件响应不超过5秒,BroadcastReceiver执行不超过10秒,Service耗时操作为20秒。否则系统会报ANR

  • 使用startService()方法启用服务后,调用者与服务之间没有关连。调用者直接退出而没有调用stopService的话,Service会一直在后台运行
  • 使用bindService()方法启用服务,调用者与服务绑定在一起了,调用者一旦退出,服务也就自动终止
  • IntentService是Service的子类,会创建子线程来处理所有的Intent请求,其onHandleIntent()方法实现的代码,无需处理多线程问题

FragmentPageAdapter和FragmentPageStateAdapter的区别

  • FragmentPageAdapter在每次切换页面的的时候,没有完全销毁Fragment,适用于固定的,少量的Fragment情况。默认notifyDataSetChanged()刷新是无效的
  • FragmentPageStateAdapter在每次切换页面的时候,是将Fragment进行回收,适合页面较多的Fragment使用,这样就不会消耗更多的内存

java对象的拷贝

  • 浅拷贝

    • implements Cloneable 实现克隆接口
  • 深拷贝

    • 第一步 implements Cloneable 实现克隆接口
    • 第二步 创建拷贝类的一个新对象,这样就和原始对象相互独立 【深拷贝】
  • 序列化实现深拷贝

    • 第一步 implements Serializable 实现序列化接口
    • 第二步 拷贝
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      T c1 = new T();
      // 通过序列化实现深拷贝
      ByteArrayOutputStream bos = new ByteArrayOutputStream();
      oos = new ObjectOutputStream(bos);
      // 序列化以及传递这个对象
      oos.writeObject(c1);
      oos.flush();
      ByteArrayInputStream bin = new ByteArrayInputStream(bos.toByteArray());
      ois = new ObjectInputStream(bin);
      // 返回新的对象
      c2 = (T) ois.readObject();
  • 思考,如果抉择用哪种方式拷贝:

如果对象的属性全是基本类型的,那么可以使用浅拷贝,但是如果对象有引用属性,那就要基于具体的需求来选择浅拷贝还是深拷贝。
如果对象引用任何时候都不会被改变,那么没必要使用深拷贝,只需要使用浅拷贝就行了。如果对象引用经常改变,那么就要使用深拷贝。
没有一成不变的规则,一切都取决于具体需求。

Serilizable接口下 transient的作用及使用方法

在实际开发过程中,我们常常会遇到这样的问题,这个类的有些属性需要序列化,而其他属性不需要被序列化,打个比方,如果一个用户有一些敏感信息(如密码,银行卡号等),为了安全起见,不希望在网络操作(主要涉及到序列化操作,本地序列化缓存也适用)中被传输,这些信息对应的变量就可以加上transient关键字。换句话说,这个字段的生命周期仅存于调用者的内存中而不会写到磁盘里持久化。

1
2
3
4
5
class User implements Serializable {
private static final long serialVersionUID = 8294180014912103005L;
private String username;
private transient String passwd;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class TransientTest {

public static void main(String[] args) {

User user = new User();
user.setUsername("Alexia");
user.setPasswd("123456");

System.out.println("read before Serializable: ");
System.out.println("username: " + user.getUsername());
System.err.println("password: " + user.getPasswd());

try {
ObjectOutputStream os = new ObjectOutputStream(
new FileOutputStream("C:/user.txt"));
os.writeObject(user); // 将User对象写进文件
os.flush();
os.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
try {
ObjectInputStream is = new ObjectInputStream(new FileInputStream(
"C:/user.txt"));
user = (User) is.readObject(); // 从流中读取User的数据
is.close();

System.out.println("\nread after Serializable: ");
System.out.println("username: " + user.getUsername());
System.err.println("password: " + user.getPasswd());

} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}

输出为:

1
2
3
4
5
6
7
read before Serializable: 
username: Alexia
password: 123456

read after Serializable:
username: Alexia
password: null

transient使用小结

  • 一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。
  • transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。
  • 被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。
  • 若实现的是Externalizable接口,则没有任何东西可以自动序列化,需要在writeExternal方法中进行手工指定所要序列化的变量,这与是否被transient修饰无关。

finally语句不会被执行的场景

  • try语句没有被执行到,如在try语句之前就返回了,这样finally语句就不会执行,这也说明了finally语句被执行的必要而非充分条件是:相应的try语句一定被执行到。
  • 在try块中有System.exit(0);这样的语句,System.exit(0);是终止Java虚拟机JVM的,连JVM都停止了,所有都结束了,当然finally语句也不会被执行到。

注意事项:

  • finally块的语句在try或catch中的return语句执行之后返回之前执行且finally里的修改语句可能影响也可能不影响try或catch中 return已经确定的返回值,若finally里也有return语句则覆盖try或catch中的return语句直接返回

Activity的启动过程(不要回答生命周期)

在Activity中如何保存/恢复状态?

  • onSaveInstanceState(Bundle outState) 保存状态

  • onRestoreInstanceState(Bundle outState) 恢复状态

  • 不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行两次;

  • 设置Activity的android:configChanges=”orientation”时,切屏还是会重新调用各个生命周期,切横、竖屏时只会执行一次;

  • 设置Activity的android:configChanges=”orientation|keyboardHidden”时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法;

Broadcast注册方式与区别

  • Manifest.xml中静态注册
    • 第一种不是常驻型广播,也就是说广播跟随程序的生命周期。
  • 代码中动态注册
    • 第二种是常驻型,也就是说当应用程序关闭后,如果有信息广播来,程序也会被系统调用自动运行。

发送广播的两种方式

  • 无序广播:所有的接收者都会接收事件,不可以被拦截,不可以被修改。
  • 有序广播:按照优先级,一级一级的向下传递,接收者可以修改广播数据,也可以终止广播事件。

HTTPS和HTTP的区别

  • https 用的 443 端口, http 用的 80 端口
  • https协议需要到ca申请证书,一般免费证书很少,需要交费。
  • http是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议。
  • http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
  • http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

View的Touch事件传递机制

  • public boolean dispatchTouchEvent(MotionEvent ev); //用来分派event

    • 用来分派事件。其中调用了onInterceptTouchEvent()和onTouchEvent(),一般不重写该方法,返回true则表示该事件被消费
  • public boolean onInterceptTouchEvent(MotionEvent ev); //用来拦截event

    • 用来拦截事件。ViewGroup类中的源码实现就是{return false;}表示不拦截该事件,事件将向下传递(传递给其子View);若手动重写该方法,使其返回true则表示拦截,事件将终止向下传递,事件由当前ViewGroup类来处理,就是调用该类的onTouchEvent()方法
  • public boolean onTouchEvent(MotionEvent ev); //用来处理event

    • 用来处理事件。返回true则表示该View能处理该事件,事件将终止向上传递(传递给其父View);返回false表示不能处理,则把事件传递给其父View的onTouchEvent()方法来处理

Android中有几种动画

  • FrameAnimation(逐帧动画):将多张图片组合起来进行播放,类似于早期电影的工作原理,很多App的loading是采用这种方式。
  • TweenAnimation(补间动画):是对某个View进行一系列的动画的操作,包括淡入淡出(AlphaAnimation),缩放(ScaleAnimation),平移(TranslateAnimation),旋转(RotateAnimation)四种模式。
  • PropertyAnimation(属性动画):属性动画不再仅仅是一种视觉效果了,而是一种不断地对值进行操作的机制,并将值赋到指定对象的指定属性上,可以是任意对象的任意属性。

Handle、Message、MessageQueue、Looper之间的关系

  • Handle:主要发送 Message
  • Message:消息
  • MessageQueue:消息队列.保存由Looper发送的消息列表。
  • Looper:用于为线程运行消息循环的类。Thread默认情况下没有与之关联,通过prepare()循环运行在线程中,通过loop(for(;;))来处理消息。

热修复相关

  • 热修复包含:
    • 资源替换
      • 思路:Andorid APP默认的类加载是PathClassLoader,这个只能加载自己APK的dex文件,所以我们需要使用DexClassLoader。我们用DexClassLoader加载外部的APK之后,通过反射获取对应的资源。
    • 类替换(四大组件、类)
      • 通过PathClassLoader 来加载我们自身App的dex
      • 通过DexClassLoader来加载我们的补丁dex文件,这里面就是没有bug的dex
      • 反射两个classLoader的<DexPathList pathList;>
      • 接着我们来继续反射两个classloader中的pathList(注意:是两个!一个是我们自己应用的,另一个是我们补丁的,PathClassLoader和DexClassLoader都继承BaseDexClassLoader),DexPathList里面的<Element[] dexElements;>,没错还是拿到这个数组的值
      • 合并两个反射到的Element 数组!这里是重中之重.我们需要把我们的补丁dex放在数组的最前面!
      • 将合并的新的数组,通过Field重新设置到我们自身App的DexPathList->dexElements.没错!就是合并之后覆盖有bug那个loader的Element 数组!!
      • 通过Android build-tools 中的dx命令打包一个没有bug的dex

图片加载缓存相关

设计一套图片异步加载缓存方案,缓存层分为三层:内存层,磁盘层,网络层

  • 内存层:内存缓存相对于磁盘缓存而言,速度要来的快很多,但缺点容量较小且会被系统回收,这里的实现我用到了LruCache。
  • 磁盘层:相比内存缓存而言速度要来得慢很多,但容量很大,这里的实现我用到了DiskLruCache类。
  • 网络层:网络访问实现我用到了开源框架Volley

Bitmap

Config 单位像素所占字节数 解析
Bitmap.Config.ALPHA_8 1 颜色信息只由透明度组成,占8位
Bitmap.Config.ARGB_4444 2 颜色信息由rgba四部分组成,每个部分都占4位,总共占16位
Bitmap.Config.ARGB_8888 4 颜色信息由rgba四部分组成,每个部分都占8位,总共占32位。是Bitmap默认的颜色配置信息,也是最占空间的一种配置
Bitmap.Config.RGB_565 2 颜色信息由rgb三部分组成,R占5位,G占6位,B占5位,总共占16位
RGBA_F16 8 Android 8.0 新增(更丰富的色彩表现HDR)
HARDWARE Special Android 8.0 新增 (Bitmap直接存储在graphic memory)
CompressFormat 解析
Bitmap.CompressFormat.JPEG 表示以JPEG压缩算法进行图像压缩,压缩后的格式可以是”.jpg”或者”.jpeg”,是一种有损压缩
Bitmap.CompressFormat.PNG 颜色信息由rgba四部分组成,每个部分都占4位,总共占16位
Bitmap.Config.ARGB_8888 颜色信息由rgba四部分组成,每个部分都占8位,总共占32位。是Bitmap默认的颜色配置信息,也是最占空间的一种配置
Bitmap.Config.RGB_565 颜色信息由rgb三部分组成,R占5位,G占6位,B占5位,总共占16位
  • 常用操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    Matrix matrix = new Matrix();  
    // 缩放
    matrix.postScale(0.8f, 0.9f);
    // 左旋,参数为正则向右旋
    matrix.postRotate(-45);
    // 平移, 在上一次修改的基础上进行再次修改 set 每次操作都是最新的 会覆盖上次的操作
    matrix.postTranslate(100, 80);
    // 裁剪并执行以上操作
    Bitmap bitmap = Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix, true);

    注意:虽然Matrix还可以调用postSkew方法进行倾斜操作,但是却不可以在此时创建Bitmap时使用。

  • Bitmap与Drawable转换

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // Drawable -> Bitmap
    public static Bitmap drawableToBitmap(Drawable drawable) {
    Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565);
    Canvas canvas = new Canvas(bitmap);
    drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight();
    drawable.draw(canvas);
    return bitmap;
    }
    // Bitmap -> Drawable
    public static Drawable bitmapToDrawable(Resources resources, Bitmap bm) {
    Drawable drawable = new BitmapDrawable(resources, bm);
    return drawable;
    }
  • 保存与释放

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
    File file = new File(getFilesDir(),"test.jpg");
    if(file.exists()){
    file.delete();
    }
    try {
    FileOutputStream outputStream=new FileOutputStream(file);
    bitmap.compress(Bitmap.CompressFormat.JPEG,90,outputStream);
    outputStream.flush();
    outputStream.close();
    } catch (FileNotFoundException e) {
    e.printStackTrace();
    } catch (IOException e) {
    e.printStackTrace();
    }
    //释放bitmap的资源,这是一个不可逆转的操作
    bitmap.recycle();
  • 图片压缩

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public static Bitmap compressImage(Bitmap image) {
    if (image == null) {
    return null;
    }
    ByteArrayOutputStream baos = null;
    try {
    baos = new ByteArrayOutputStream();
    image.compress(Bitmap.CompressFormat.JPEG, 100, baos);
    byte[] bytes = baos.toByteArray();
    ByteArrayInputStream isBm = new ByteArrayInputStream(bytes);
    Bitmap bitmap = BitmapFactory.decodeStream(isBm);
    return bitmap;
    } catch (OutOfMemoryError e) {
    e.printStackTrace();
    } finally {
    try {
    if (baos != null) {
    baos.close();
    }
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    return null;
    }
  • Option类

常用方法 说明
boolean inJustDecodeBounds 如果设置为true,不获取图片,不分配内存,但会返回图片的高度宽度信息
int inSampleSize 图片缩放的倍数
int outWidth 获取图片的宽度值
int outHeight 获取图片的高度值
int inDensity 用于位图的像素压缩比
int inTargetDensity 用于目标位图的像素压缩比(要生成的位图)
byte[] inTempStorage 创建临时文件,将图片存储
boolean inScaled 设置为true时进行图片压缩,从inDensity到inTargetDensity
boolean inDither 如果为true,解码器尝试抖动解码
Bitmap.Config inPreferredConfig 设置解码器这个值是设置色彩模式,默认值是ARGB_8888,在这个模式下,一个像素点占用4bytes空间,一般对透明度不做要求的话,一般采用RGB_565模式,这个模式下一个像素点占用2bytes
String outMimeType 设置解码图像
boolean inPurgeable 当存储Pixel的内存空间在系统内存不足时是否可以被回收
boolean inInputShareable inPurgeable为true情况下才生效,是否可以共享一个InputStream
boolean inPreferQualityOverSpeed 为true则优先保证Bitmap质量其次是解码速度
boolean inMutable 配置Bitmap是否可以更改,比如:在Bitmap上隔几个像素加一条线段
int inScreenDensity 当前屏幕的像素密度

Serializable/Parcelable

  • Serializable 使用 I/O 读写存储在硬盘上,而 Parcelable 是直接 在内存中读写
  • Serializable 会使用反射,序列化和反序列化过程需要大量 I/O 操作, Parcelable 自已实现封送和解封(marshalled &unmarshalled)操作不需要用反射,数据也存放在 Native 内存中,效率要快很多

屏幕适配

  • 单位

    • dpi每英寸像素数(dot per inch)
    • dp 密度无关像素 - 一种基于屏幕物理密度的抽象单元。 这些单位相对于160 dpi的屏幕,因此一个dp是160 dpi屏幕上的一个px。 dp与像素的比率将随着屏幕密度而变化,但不一定成正比。为不同设备的UI元素的实际大小提供了一致性。
    • sp与比例无关的像素 - 这与dp单位类似,但它也可以通过用户的字体大小首选项进行缩放。建议在指定字体大小时使用此单位,以便根据屏幕密度和用户偏好调整它们。
      1
      2
      3
      dpi = px / inch
      density = dpi / 160
      dp = px / density
  • 头条适配方案

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    private static void setCustomDensity(@NonNull Activity activity, @NonNull final Application application) {
    final DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics();
    if (sNoncompatDensity == 0) {
    sNoncompatDensity = appDisplayMetrics.density;
    sNoncompatScaledDensity = appDisplayMetrics.scaledDensity;
    // 监听字体切换
    application.registerComponentCallbacks(new ComponentCallbacks() {
    @Override
    public void onConfigurationChanged(Configuration newConfig) {
    if (newConfig != null && newConfig.fontScale > 0) {
    sNoncompatScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;
    }
    }

    @Override
    public void onLowMemory() {

    }
    });
    }

    // 适配后的dpi将统一为360dpi
    final float targetDensity = appDisplayMetrics.widthPixels / 360;
    final float targetScaledDensity = targetDensity * (sNoncompatScaledDensity / sNoncompatDensity);
    final int targetDensityDpi = (int)(160 * targetDensity);

    appDisplayMetrics.density = targetDensity;
    appDisplayMetrics.scaledDensity = targetScaledDensity;
    appDisplayMetrics.densityDpi = targetDensityDpi;

    final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
    activityDisplayMetrics.density = targetDensity;
    activityDisplayMetrics.scaledDensity = targetScaledDensity;
    activityDisplayMetrics.densityDpi = targetDensityDpi
    }

SharedPreferences

SharedPreferences采用key-value(键值对)形式, 主要用于轻量级的数据存储, 尤其适合保存应用的配置参数, 但不建议使用SharedPreferences来存储大规模的数据, 可能会降低性能.
SharedPreferences采用xml文件格式来保存数据, 该文件所在目录位于/data/data//shared_prefs

从Android N开始, 创建的SP文件模式, 不允许MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE模块, 否则会直接抛出异常SecurityException。MODE_MULTI_PROCESS这种多进程的方式也是Google不推荐的方式, 后续同样会不再支持。
当设置MODE_MULTI_PROCESS模式, 则每次getSharedPreferences过程, 会检查SP文件上次修改时间和文件大小, 一旦所有修改则会重新从磁盘加载文件。

获取方式

  • getPreferences

    1
    2
    3
    4
    5
    Activity.getPreferences(mode): 以当前Activity的类名作为SP的文件名. 即xxxActivity.xml
    Activity.java
    public SharedPreferences getPreferences(int mode) {
    return getSharedPreferences(getLocalClassName(), mode);
    }
  • getDefaultSharedPreferences

    1
    2
    3
    4
    5
    PreferenceManager.getDefaultSharedPreferences(Context): 以包名加上_preferences作为文件名, 以MODE_PRIVATE模式创建SP文件. 即packgeName_preferences.xml.
    public static SharedPreferences getDefaultSharedPreferences(Context context) {
    return context.getSharedPreferences(getDefaultSharedPreferencesName(context),
    getDefaultSharedPreferencesMode());
    }
  • getSharedPreferences
    直接调用Context.getSharedPreferences(name, mode),所有的方法最终都是调用到如下方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    class ContextImpl extends Context {
    private ArrayMap<String, File> mSharedPrefsPaths;

    public SharedPreferences getSharedPreferences(String name, int mode) {
    File file;
    synchronized (ContextImpl.class) {
    if (mSharedPrefsPaths == null) {
    mSharedPrefsPaths = new ArrayMap<>();
    }
    //先从mSharedPrefsPaths查询是否存在相应文件
    file = mSharedPrefsPaths.get(name);
    if (file == null) {
    //如果文件不存在, 则创建新的文件
    file = getSharedPreferencesPath(name);
    mSharedPrefsPaths.put(name, file);
    }
    }

    return getSharedPreferences(file, mode);
    }
    }
  • apply / commit

    • apply没有返回值, commit有返回值能知道修改是否提交成功
    • apply是将修改提交到内存,再异步提交到磁盘文件,而commit是同步的提交到磁盘文件
      多并发的提交commit时,需等待正在处理的commit数据更新到磁盘文件后才会继续往下执行,从而降低效率; 而apply只是原子更新到内存,后调用apply函数会直接覆盖前面内存数据,从一定程度上提高很多效率
  • 注意
    • 强烈建议不要在sp里面存储特别大的key/value,有助于减少卡顿/anr
    • 不要高频地使用apply,尽可能地批量提交
    • 不要使用MODE_MULTI_PROCESS
    • 高频写操作的key与高频读操作的key可以适当地拆分文件,由于减少同步锁竞争
    • 不要连续多次edit(),应该获取一次获取edit(),然后多次执行putxxx(),减少内存波动