Zhujiaqqq Blog


  • 首页

  • 关于

  • 标签

  • 分类

如何对图片进行缓存

发表于 2019-09-05 | 分类于 Android

设计缓存算法的时候需要考虑

  • 哪些应该保存
  • 哪些应该丢弃
  • 什么时候丢弃
  • 获取数据的成本和缓存数据的成本
  • 缓存价值(命中率)

LRU(Least Recently Used)算法的实现

  • LRU内部包含一个LinkedHashMap
  • 统计监控(额外的开销,可以监控算法的有效性)
1
2
3
4
5
private int putCount;//放入个数
private int createCount;//创建个数
private int evictionCount;//弹出个数
private int hitCount;//命中个数
private int missCount;//没命中个数

LFU(Least Frequently Used)

如何避免OOM产生

发表于 2019-09-03 | 分类于 Android

OOM的产生

  • 已使用内存+新申请内存>可分配内存
  • OOM几乎覆盖所有的内存区域,通常指堆内存
  • Native Heap在物理内存不够时也会抛出OOM

使用合适的数据结构

  • HashMap——大于1000个数、增删频繁
  • ArrayMap——key不是整型
  • SparseArray——Key是整型(池化技术、内存复用)

避免使用枚举

每一个枚举就是一个对象,对象至少占24字节。所以使用静态常量:

1
2
3
4
5
6
7
@Retention(value = RetentionPolicy.SOURCE)
@IntDef(value = {COMMON, LOAD_MORE, LOAD_REFRESH})
public @interface LoadType {}

public static final int COMMON = 0;
public static final int LOAD_MORE = 1;
public static final int LOAD_REFRESH = 2;

Kotlin 可以使用内联类,编译时转化为int,节省内存。

Bitmap

  • 尽量根据实际需求选择合适的分辨率

  • 不用帧动画,使用代码实现动效

  • 使用Bitmap重采用和复用配置

    使用NDK

  • Native Heap没有专门的使用限制

  • 内存大户的核心逻辑主要在Native 层

内存优化5R法则

  • Reduce缩减:降低图片分辨率/重采样/抽稀策略
  • Reuse复用:池化策略/避免频繁创建对象,减小GC压力
  • Recycle回收:主动销毁、结束,避免内存泄露/生命周期闭环
  • Refactor重构:更合适的数据结构/更合理的程序架构
  • Revalue重审:谨慎使用Large Heap/多进程/第三方框架

RxJava中CompositeDisposable注意事项

发表于 2019-09-01 | 分类于 Android

在RxJava中,Observable被订阅后,可以得到一个Disposable对象。为了防止内存泄露,一般情况下,都会在Activity结束的时候调用Disposable对象的dispose()方法,解除订阅。

RxJava2中,提供了一个CompositeDisposable类作为Disposable的容器。

1
2
3
4
5
6
7
8
9
10
11
12
13
public final class CompositeDisposable implements Disposable, DisposableContainer {

OpenHashSet<Disposable> resources;

volatile boolean disposed;

/**
* Creates an empty CompositeDisposable.
*/
public CompositeDisposable() {
}
...
}

可以看出,CompositeDisposable实现了Disposable接口(void dispose();方法);实现了DisposableContainer接口,可以add、remove、delete操作Disposable。

重点要关注两个方法:

1、add(@NonNull Disposable d)这个方法是将一个dispoable加入CompositeDisposable中,当Activity或者Fragment的生命周期结束的时候,通过

dispose()方法将Observable的订阅事件取消关联,防止内存泄露。

2、public void dispose()这个方法就是CompositeDisposable中将所有的Observable的订阅事件取消关联使用。


之前遇到过一个问题:

使用MVP模式开发的时候,Activity A的Presenter中包含CompositeDisposable对象,Activity A的onStart()方法中关联了A的Presenter,当Activity A 跳转到Activity B的时候,我在A的onStop()方法中调用了CompositeDisposable.dispose()方法。当Activity B 调用finish()方法结束时候,回到Activity A页面,Activity A上的所有关于RxJava的调用都失效。

我通过Debug的方式一层层的查看方法的调用栈对比正常的情况,最终发现:

CompositeDisposable的dispose()方法在被调用之后,所有的add()进来的Disposable都会直接调用自身的dispose()方法,也就是说在事件刚被绑定监听的时候就被解除绑定了。

查看源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public void dispose() {
if (disposed) {
return;
}
OpenHashSet<Disposable> set;
synchronized (this) {
if (disposed) {
return;
}
disposed = true;
set = resources;
resources = null;
}

dispose(set);
}

在dispose() 方法调用的时候,会将全局变量disposed置为true。

再看add方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public boolean add(@NonNull Disposable d) {
ObjectHelper.requireNonNull(d, "d is null");
if (!disposed) {
synchronized (this) {
if (!disposed) {
OpenHashSet<Disposable> set = resources;
if (set == null) {
set = new OpenHashSet<Disposable>();
resources = set;
}
set.add(d);
return true;
}
}
}
d.dispose();
return false;
}

在add一个disposable的时候,如果全局变量disposed为true,那么不会走if条件,而是直接走下面的d.dispose();这句,直接将监听的事件解除绑定。

所有,CompositeDisposable的dispose()方法一定要放到Activity和Fragment生命周期结束的地方——onDestroy()中。

实现一个Handler-Looper框架

发表于 2019-08-28 | 分类于 Android

Handler的核心能力

  • 线程间通信
  • 延迟任务执行

sendMessageDelayed(Message msg, long delay)

postDelayed(Runnable r, long delay)

Looper的核心能力

  • Looper是线程特有的,所以要有ThreadLocal
  • 包含MessageQueue
  • loop()方法,在死循环中获取消息队列中的消息,分发给handler处理

MessageQueue的核心能力

  • 持有消息(链表)
  • 消息按时间排序(优先队列)
  • 队列为空的时候阻塞读取(阻塞队列)
  • 头节点有延时可以定时阻塞(延时阻塞队列)

使用DelayQueue

Message

仿造Android的message


类结构

DelayQueue

  • 实现BlockingQueue接口(实现阻塞功能)

  • 有PriorityQueue对象(可以优先排序)

    DelayQueue的阻塞机制:通过condition.await方法

存在的问题

没有实现remove的方法


为什么Android不直接使用DelayQueue作为消息队列

  • DelayQueue没有提供合适的remove机制
  • 更大的自由度,可以定制许多功能,特别是与Native层结合
  • Android的MessageQueue可以针对单线程读取的场景做优化,只有插入队列的时候加锁,而读取的时候不用加锁;DelayQueue读写都加锁

Android为什么非UI线程不能更新UI

发表于 2019-08-27 | 分类于 Android

UI 线程的什么

Zygote fork出的线程就是UI线程。

zygote创建App的过程中调用ActivityThread类的main()方法,main中会自行Looper.loop();。

主线程如何工作

  • handler
  • loop
  • messageQueue

如果把UI设计成线程安全的

那么需要对控件加锁,哪一个线程拿到锁才能设置UI。

UI为什么不设计成线程安全的

  • UI具有可变性,甚至是高频可变性
  • UI对响应时间的敏感性要求UI操作必须高效
  • UI组件必须批量绘制来保证效率

加锁可以变成线程安全,但是加锁是有性能开销的,会使UI的整体性能下降

非UI线程一定不能更新UI吗

SurfaceView可以在非UI线程更新UI:

lockCanvas -> draw -> unLockCanvasAndPost

如何实现类似微信右滑返回的效果

发表于 2019-08-25 | 分类于 Android

Fragment的实现

  • 对于Fragment的控制相对简单
  • 不涉及window的控制,只是View级别的操作
  • 实现View跟随手势滑动移动的效果
  • 实现手势结束后判断取消或返回执行归位动画

Activity的实现

  • 处理好window的控制

    • 修改activity的style属性:

      1
      2
      3
      4
      <style name="AppTranslucentTheme" parent="AppTheme">
      <item name="android:windowBackground">@android:color/transparent</item>
      <item name="android:windowIsTranslucent">true</item>
      </style>
  • Activity联动(多个activity栈)

    Activity A和Activity C在Task#0

    Activity B在Task#1

    当Activity A跳转Activity B时没有问题(有动画效果)

    再当Activity B跳转Activity C时,会先出现Activity A,然后动画效果到Activity C

    处理方法:再Activity B跳转Activity C的时候将B页面整体截屏放到Activity C的下面。

    获取Activity的栈:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    Activity.java
    public int getTaskId() {
    try {
    return ActivityManager.getService()
    .getTaskForActivity(mToken, false);
    } catch (RemoteException e) {
    return -1;
    }
    }

Activity透明对生命周期的影响

1
2
3
4
5
graph TB
Created -- onStart --> Started
Started -- onResume --> Resumed
Resumed -- onPause -->Started
Started -- onStop --> Created

A、B、C、D四个Activity依次跳转,此时Activity栈中:A->B->C->D。(默认都不透明)

  • 如果D为不透明Activity,那么:D为Resumed状态(可操作),ABC为Created状态(不可见)。
  • 如果D为透明Activity,那么:D为Resumed状态(可操作),C为Started状态(可见,不可操作),AB为Created状态(不可见)
  • 如果C、D为透明Activity,那么::D为Resumed状态(可操作),B、C为Started状态(可见,不可操作),A为Created状态(不可见)

实现滑动返回接口

如何解决Activity参数的类型安全及接口繁琐的问题

发表于 2019-08-24
  • 类型安全:Bundle的K-V不能在编译期保证类型
  • 接口繁琐:启动Activity 时参数和结果传递都依赖Intent

onActivityResult为什么麻烦

  1. 代码处理逻辑分离,容易出现遗漏和不一致的问题
  2. setResult()写法不够直观,结果数据没有类型安全保障
  3. 结果种类较多时,onActivityResult就会逐渐臃肿难以维护

为什么Activity的参数存在类型安全问题

intent.putExtra()时和getIntent.getXxxExtra()时需要人工去保证类型安全


如何实现类型安全

  • 注解处理器生成Builder

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @Builder
    public class UserActivity extends Activity{
    @Required
    String name;
    @Required
    int age;
    @Optional
    String title;
    @Optional
    String company;
    }

    UserActivityBuilder.builder(age, name)
    .title(title)
    .company(company);

    通过注解处理器生成注入逻辑

注解处理器程序的开发注意事项

  • 注意注解标注的类的继承关系
  • 注意注解标注的类为内部类的情况
  • 注意kotlin与java的类型映射的问题
  • 把握好代码生成和直接依赖的边界

如何跨App启动Activity

发表于 2019-08-24 | 分类于 Android
1
2
graph LR
AppA的ActivityA -- 启动 --> AppB的ActivityB
  • 共享uid的App

在AndroidManifest中:manifest标签中添加android:sharedUserId="xxxx"

启动时:startActivity(new Intent().setComponent(new ComponentName("com.example.zhu","com.example.zhu.XxxActivity")));

  • 使用exported

在Manifest中添加exported属性<activity android:name=".BActivity" android:exported="true"/>

启动时:startActivity(new Intent().setComponent(new ComponentName("com.example.zhu","com.example.zhu.XxxActivity")));

  • 使用intentFilter(隐式跳转)

在Manifest的Activity标签中添加:

1
2
3
4
<intent-filter>
<action android:name="android.intent.action.TEST" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>

启动时:startActivity(new Intent("android.intent.action.TEST"))


Activity加权限

  1. 给AppA的manifest中添加权限:

    <uses-permission android:name="com.example.zhu"/>

  2. gei AppB中需要启动的Activity添加permission属性:

    android:permission="com.example.zhu"

  3. 启动时:使用隐式跳转

这种添加权限的方法必须要AppB先安装,否则AppA无法获取权限

拒绝服务漏洞

在被启动的Activity中,getIntent().getExtras()获取的bundle对象中如果有的序列化数据,无法被反序列化成对象,程序就会崩溃(报找不到反序列化对象的exception),此时需要对bundle的操作加try-catch。

Activity的启动流程

发表于 2019-08-22 | 分类于 Android

Activity跨进程启动

  1. 请求进程A通过startActivity方法调用AMP
  2. system_server进程中的AMS执行:
    1. 解析Activity信息(androidManifest.xml)
    2. 处理启动参数
    3. 启动目标进程
    4. 绑定新进程
    5. 调用ATP(ApplicationThread代理)
  3. 目标进程B通过ApplicationThread调用ActivityThread,最终启动Activity的生命周期

AMP -> AMS 以及 ATP -> ApplicationThread都是通过binder进行通信的

Activity进程内启动

  1. 请求进程A通过startActivity方法调用AMP
  2. system_server进程中的AMS执行:
    1. 解析Activity信息(androidManifest.xml)
    2. 处理启动参数
    3. 调用ATP(ApplicationThread代理)
  3. 进程A过ApplicationThread调用ActivityThread,最终启动Activity的生命周期

Activity的参数传递

1
2
graph LR
请求进程A的Bundle -- Binder缓冲区 --> system_server进程的Bundle
  • 大小受缓冲区大小限制
  • 数据必须可以序列化

解决办法:

1
2
3
4
graph LR
ActivityA的Bundle -- Binder缓冲区-key --> ActivityB的Bundle
ActivityA的Bundle -- setData --> Model-data
Model-data -- getData --> ActivityB的Bundle

Activity之间的数据传递不能太大

Activity实例化

通过反射newInstance实现,所以不能对Activity添加构造方法。

同理,fragment也一样。

Activity的窗口如何展示

Activity转场动画的实现机制

Native方法与Native函数怎么绑定的

发表于 2019-08-21 | 分类于 Android

如何绑定

  • 静态绑定:通过命名规则映射

    1
    2
    3
    4
    package com.example.nativec;
    public class NativeCInf{
    public static native void callNativeStatic();
    }

    1
    2
    extern "C" JNIEXPORT void JNICALL
    Java_com_example_nativec_NativeCInf_callNativeStatic(JNIEnv *, jclass)
  • 动态绑定:通过JNI函数注册

    • 动态绑定可以在任何时刻触发
    • 动态绑定之前根据静态规则查找Native函数
    • 动态绑定可以在绑定后的任意时刻取消
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    int registerMethods(JNUEnv *env, const char *className, const JNINativeMethod *methods, int methodsLength){
    //获取Class
    jclass clazz = env -> FindClass(className);
    if(clazz == NULL) return JNI_ERR;
    //注册方法
    if((env -> RegisterNative(clazz, methods, methodsLength)) < 0){
    return JNI_ERR;
    }
    return JNI_OK;
    }

动态和静态绑定对比

动态绑定 静态绑定
Native函数名 无需求 按照固有规则编写且采用C的名称修饰规则
Native函数可见性 无需求 可见
动态更换 可以 不可以
调用性能 无需查找 有额外的查找开销
开发体验 几乎无副作用 重构代码时较为繁琐
Android Studio支持 不能自动关联跳转 自动关联JNI函数可跳转
123…5

Zhujiaqqq

Java Android Python Algorithm and Machine learning

43 日志
6 分类
55 标签
© 2019 Zhujiaqqq
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4