深入学习d3.js:d3-dispatch

这是d3设置的一种调度机制,类似于node里的eventEmitter。

例如,要为开始和结束时间创建调度。

var dispatch = d3.dispatch("start", "end");

然后用on来注册回调函数。

dispatch.on("start", callback1);
dispatch.on("start.foo", callback2);
dispatch.on("end", callback3);

然后可以使用dispatch.call和dispatch.apply启用回调:

dispatch.call("start");

就像function.call你可以指定上下文,和任意参数‘

dispatch.call("start", {about: "I am a context object"}, "I am an argument");

d3.dispatch(types…)

为指定的事件类型创建新的调度,时间类型都是字符串,例如start,end

dispatch.on(typenames[, callback]) 

为时间指定callback,如果该事件已经指定了callback,则替换掉。

dispatch.copy() 

返回dispatch对象的值拷贝

dispatch.call(type[, that[, arguments…]])

激活指定类型的回调函数,并可以指定context 

dispatch.apply(type[, that[, arguments]])

和call类似,举个例子,如果想在click后调用custom 的回调函数

selection.on("click", function() {
  dispatch.apply("custom", this, arguments);
});

dispatch的源码算是比较简单吧,默认返回的是一个封装好的dispatch对象,通过调用会产生一个Dispatch,该对象对用的方法就是上述提到的方法,此外两个函数get和set,一个是获取给定类型的callback函数,以一个设置callback,Dispatch中用一个对象存储类型和callback函数。

var noop = {value: function() {}};

function dispatch() {
  for (var i = 0, n = arguments.length, _ = {}, t; i < n; ++i) {
    if (!(t = arguments[i] + "") || (t in _)) throw new Error("illegal type: " + t);
    _[t] = [];
  }
  return new Dispatch(_);
}

function Dispatch(_) {
  this._ = _;
}

function parseTypenames(typenames, types) {
  return typenames.trim().split(/^|\s+/).map(function(t) {
    var name = "", i = t.indexOf(".");
    if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i);
    if (t && !types.hasOwnProperty(t)) throw new Error("unknown type: " + t);
    return {type: t, name: name};
  });
}

Dispatch.prototype = dispatch.prototype = {
  constructor: Dispatch,
  on: function(typename, callback) {
    var _ = this._,
        T = parseTypenames(typename + "", _),
        t,
        i = -1,
        n = T.length;

    // If no callback was specified, return the callback of the given type and name.
    if (arguments.length < 2) {
      while (++i < n) if ((t = (typename = T[i]).type) && (t = get(_[t], typename.name))) return t;
      return;
    }

    // If a type was specified, set the callback for the given type and name.
    // Otherwise, if a null callback was specified, remove callbacks of the given name.
    if (callback != null && typeof callback !== "function") throw new Error("invalid callback: " + callback);
    while (++i < n) {
      if (t = (typename = T[i]).type) _[t] = set(_[t], typename.name, callback);
      else if (callback == null) for (t in _) _[t] = set(_[t], typename.name, null);
    }

    return this;
  },
  copy: function() {
    var copy = {}, _ = this._;
    for (var t in _) copy[t] = _[t].slice();
    return new Dispatch(copy);
  },
  call: function(type, that) {
    if ((n = arguments.length - 2) > 0) for (var args = new Array(n), i = 0, n, t; i < n; ++i) args[i] = arguments[i + 2];
    if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type);
    for (t = this._[type], i = 0, n = t.length; i < n; ++i) t[i].value.apply(that, args);
  },
  apply: function(type, that, args) {
    if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type);
    for (var t = this._[type], i = 0, n = t.length; i < n; ++i) t[i].value.apply(that, args);
  }
};

function get(type, name) {
  for (var i = 0, n = type.length, c; i < n; ++i) {
    if ((c = type[i]).name === name) {
      return c.value;
    }
  }
}

function set(type, name, callback) {
  for (var i = 0, n = type.length; i < n; ++i) {
    if (type[i].name === name) {
      type[i] = noop, type = type.slice(0, i).concat(type.slice(i + 1));
      break;
    }
  }
  if (callback != null) type.push({name: name, value: callback});
  return type;
}

export default dispatch;

 

07-01 11:39:12.926 2846 2846 W Looper : Slow dispatch took 124ms main h=com.android.server.notification.NotificationManagerService$WorkerHandler c=com.android.server.notification.NotificationManagerService$$ExternalSyntheticLambda0@8145441 m=0 07-01 11:39:12.928 6568 6568 D OMG_Observer: mScreenWidthDisplay = 1080, mScreenHeightDisplay = 2400, mRotation = 0, mOrient = 1 , mMiniSizeLimit = 660, mNormalSizeLimit =1800 07-01 11:39:12.933 1288 1865 D VRR [OTI]: handleContentIdleCheck [0.000000] 07-01 11:39:12.951 5245 5245 D BaseDepthController: setSurface: 07-01 11:39:12.951 5245 5245 D BaseDepthController: mWaitingOnSurfaceValidity: false 07-01 11:39:12.951 5245 5245 D BaseDepthController: mSurface: null 07-01 11:39:12.952 5245 5245 D LAUNCHER3: Launcher, handlePendingRequest: mPendingActivityRequestCode = -1, handled = false 07-01 11:39:12.954 5245 5245 D StateManager: goToState - fromState: Normal, toState: Normal, partial trace: 07-01 11:39:12.954 5245 5245 D StateManager: at com.android.launcher3.statemanager.StateManager.moveToRestState(StateManager.java:500) 07-01 11:39:12.954 5245 5245 D StateManager: at com.android.launcher3.statemanager.StateManager.moveToRestState(StateManager.java:491) 07-01 11:39:12.954 5245 5245 D StateManager: at com.android.launcher3.statemanager.StatefulActivity.onStop(StatefulActivity.java:119) 07-01 11:39:12.955 5245 5245 D StatsLog: LAUNCHER_ONSTOP 07-01 11:39:12.958 5245 5245 D ViewNative: java.lang.ClassNotFoundException: com.oplus.inner.view.ViewWrapper 07-01 11:39:12.958 5245 5245 D ViewNative: java.lang.ClassNotFoundException: com.color.inner.view.ViewWrapper 07-01 11:39:12.959 5245 5245 D ViewNative: java.lang.ClassNotFoundException: com.oplus.inner.view.ViewWrapper 07-01 11:39:12.959 5245 5245 D ViewNative: java.lang.ClassNotFoundException: com.color.inner.view.ViewWrapper 07-01 11:39:12.959 5245 5245 D ViewNative: java.lang.ClassNotFoundException: com.oplus.inner.view.ViewWrapper 07-01 11:39:12.959 5245 5245 D ViewNative: java.lang.ClassNotFoundException: com.color.inner.view.ViewWrapper 07-01 11:39:12.959 5245 5245 D ViewNative: java.lang.ClassNotFoundException: com.oplus.inner.view.ViewWrapper 07-01 11:39:12.959 5245 5245 D ViewNative: java.lang.ClassNotFoundException: com.color.inner.view.ViewWrapper 07-01 11:39:12.976 6306 6329 I droid.apps.maps: Waiting for a blocking GC ProfileSaver
最新发布
07-17
<think>我们面对的是Android系统日志分析任务,重点包括: 1. Looper慢分发通知(Slow dispatch) 2. Launcher类找不到异常(ClassNotFoundException) 3. 系统性能问题排查 根据日志片段,我们已经看到了一些相关记录: - Looper慢分发:`07-01 11:39:12.926 2846 2846 W Looper : Slow dispatch took 124ms main h=com.android.server.notification.NotificationManagerService$WorkerHandler c=com.android.server.notification.NotificationManagerService$$ExternalSyntheticLambda0@8145441 m=0` - Launcher类找不到:`07-01 11:39:12.958 5245 5245 D ViewNative: java.lang.ClassNotFoundException: com.oplus.inner.view.ViewWrapper`(多次出现) 另外,我们还注意到一些可能影响性能的日志: - 音频设备设置参数(可能涉及系统资源操作) - VRR(可变刷新率)处理 - 内存相关(如`I am_low_memory`,但当前日志片段未出现,不过我们可以关注) 我们将按照以下步骤进行分析: ### 一、Looper慢分发(Slow dispatch)分析 **问题特征**:Looper在分发消息时耗时超过阈值(通常为100ms),导致主线程(或关键线程)阻塞,可能引起ANR或界面卡顿。 **日志示例**: ``` 07-01 11:39:12.926 2846 2846 W Looper : Slow dispatch took 124ms main h=... c=... m=0 ``` **分析步骤**: 1. **定位慢分发发生的位置**: - 查看日志中标记为`Slow dispatch`的行,记录耗时(如124ms)和线程(如`main`)。 - 注意`h=`后面的是Handler,`c=`后面的是回调(Runnable)。这里回调是`NotificationManagerService`的一个内部类,说明是系统通知服务在处理消息时耗时过长。 2. **排查耗时原因**: - 检查该时间段内(11:39:12.926前后)同一线程(主线程或该WorkerHandler所在线程)的其他日志,看是否有耗时操作(如I/O、数据库操作、复杂计算等)。 - 使用Traceview或Systrace工具进一步分析该时间段内的CPU执行情况。 3. **解决方案**: - 将耗时操作移出主线程,使用工作线程(如AsyncTask、ThreadPoolExecutor、Kotlin协程等)。 - 优化通知处理逻辑,避免在通知处理中执行耗时操作。 ### 二、类找不到异常(ClassNotFoundException)分析 **问题特征**:在加载类时,系统无法找到指定的类。这通常发生在动态加载类或反射调用时。 **日志示例**: ``` 07-01 11:39:12.958 5245 5245 D ViewNative: java.lang.ClassNotFoundException: com.oplus.inner.view.ViewWrapper ...(多次出现,尝试了不同包名:com.oplus.inner和com.color.inner) ``` **分析步骤**: 1. **确认缺失的类**:这里缺失的是`com.oplus.inner.view.ViewWrapper`(以及`com.color.inner.view.ViewWrapper`),说明系统或应用试图通过反射加载这个类但失败了。 2. **可能原因**: - **类不存在**:该设备(Oppo/Realme等)的系统框架中可能没有提供这个内部类。这可能是由于设备厂商定制Android系统时,对内部API进行了修改或隐藏。 - **类加载器问题**:当前类加载器(可能是系统类加载器)无法加载这个类,因为这个类属于系统私有API,通常不对应用开放。 3. **解决方案**: - **避免使用内部API**:检查代码中是否有使用`com.oplus.inner`或`com.color.inner`包中的类。如果有,应改用公开的Android API。 - **兼容性处理**:如果必须使用,应确保只在支持该内部类的设备上调用,并做好异常处理(捕获ClassNotFoundException并回退到其他实现)。 ### 三、系统性能问题排查 从日志中我们还注意到一些可能影响性能的点: 1. **音频参数设置频繁**(11:39:12.901附近): - 当屏幕状态改变(如亮屏)时,系统会通知音频服务重新配置参数。如果过于频繁,可能消耗系统资源。 - 建议:检查是否在屏幕状态变化时触发了不必要的音频操作。 2. **VRR(可变刷新率)处理**: - `07-01 11:39:12.933 1288 1865 D VRR [OTI]: handleContentIdleCheck [0.000000]` - VRR用于动态调整屏幕刷新率以节省电量。如果处理不当,可能导致显示性能问题。 - 建议:关注VRR模块的日志,看是否有异常或频繁调用。 3. **内存与GC**: - 日志中出现了`Waiting for a blocking GC ProfileSaver`(来自Google Maps应用),说明GC操作可能阻塞了线程。 - 建议:使用内存分析工具(如Android Studio的Profiler)检查内存使用情况,避免内存泄漏和频繁GC。 4. **Launcher状态切换**(11:39:12.954): - Launcher在状态切换(onStop)时可能涉及资源释放和保存状态,如果耗时过长可能影响返回主界面的流畅度。 - 建议:优化Launcher的状态保存逻辑,避免在主线程进行复杂操作。 ### 四、工具使用建议 1. **Systrace**:用于分析系统级性能问题,可以捕获CPU调度、渲染、磁盘I/O等信息。 ```bash python systrace.py -a com.example.app -b 16384 -o mytrace.html sched gfx view wm am ``` 2. **Android Profiler**:实时监测应用的内存、CPU、网络使用情况,定位内存泄漏和CPU热点。 3. **StrictMode**:在开发模式下启用,检测主线程中的磁盘访问和网络操作。 ```java StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectDiskReads().detectDiskWrites().detectNetwork().penaltyLog().build()); ``` ### 五、总结解决方案 1. **Looper慢分发**:优化通知处理逻辑,避免在主线程(或关键工作线程)执行耗时操作。 2. **类找不到异常**:移除对系统内部类(`com.oplus.inner.view.ViewWrapper`)的依赖,改用公开API。 3. **系统性能**: - 检查音频服务在屏幕状态变化时的操作频率。 - 监控VRR模块的日志,确保其正常运行。 - 优化Launcher状态切换逻辑,避免主线程阻塞。 - 监控内存使用,减少GC次数。 §§相关问题§§ 1. 如何捕获和分析Android系统的ANR日志? 2. 有哪些工具可以检测Android应用的内存泄漏? 3. 如何避免在Android主线程执行耗时操作? 4. 如何适配不同厂商Android系统的内部API差异?</think>### Android系统日志分析:性能问题与类加载异常排查 --- #### **一、Looper慢分发通知分析(Slow Dispatch)** **问题特征**: 日志中出现 `Slow dispatch took [time]ms`(如 `Slow dispatch took 124ms`),表示主线程消息处理超时,可能导致界面卡顿或ANR[^1]。 ```log 07-01 11:39:12.926 W Looper: Slow dispatch took 124ms main ... # 主线程消息处理耗时124ms ``` **排查步骤**: 1. **定位耗时操作**: - 检查 `Slow dispatch` 前后的日志,找到同一线程(如 `main`)中耗时的操作: ```log 07-01 11:39:12.901 D AudioFlinger: setParameters(): ... # 音频操作可能阻塞 07-01 11:39:12.976 I droid.apps.maps: Waiting for a blocking GC ... # GC暂停 ``` - 重点关注 `I/O`、`Binder调用`、`GC` 等操作。 2. **优化方案**: - **异步处理**:将音频操作(如 `setParameters`)移至工作线程。 - **减少GC压力**:避免频繁创建临时对象(如循环内 `new` 对象)。 - **使用Systrace定位**: ```bash python systrace.py -a com.example.app -b 16384 -o trace.html sched gfx view ``` --- #### **二、Launcher类找不到异常(ClassNotFoundException)** **问题特征**: 日志中出现 `java.lang.ClassNotFoundException`,表示类加载失败(如 `com.oplus.inner.view.ViewWrapper`): ```log 07-01 11:39:12.958 D ViewNative: java.lang.ClassNotFoundException: com.oplus.inner.view.ViewWrapper ``` **原因分析**: 1. **依赖缺失**: - OPPO系统内部类 `com.oplus.inner.view.ViewWrapper` 未包含在设备ROM中。 - 常见于厂商定制系统(如ColorOS),公开API与内部API不一致。 2. **动态加载失败**: - Launcher可能通过反射调用内部类,但设备未提供实现。 **解决方案**: 1. **替换内部API**: - 改用Android标准API(如 `android.view.View`),避免依赖厂商内部类。 - 示例反射调用兼容性处理: ```java try { Class<?> clazz = Class.forName("com.oplus.inner.view.ViewWrapper"); } catch (ClassNotFoundException e) { // 回退到标准实现 View view = new View(context); } ``` 2. **检查ProGuard规则**: - 确保厂商SDK的类未被混淆: ```proguard -keep class com.oplus.inner.** { *; } ``` --- #### **三、系统性能问题排查** **关键日志线索**: 1. **内存压力**: ```log 07-01 11:39:12.888 I am_low_memory: 系统内存不足 # 引用[2] ``` - 触发场景:频繁应用启动/大内存申请(如Maps)。 2. **UI渲染延迟**: ```log 07-01 11:39:12.933 D VRR [OTI]: handleContentIdleCheck [0.000000] # 刷新率调整 ``` - 可变刷新率(VRR)可能因内容延迟导致卡顿。 3. **进程资源竞争**: ```log 07-01 11:39:12.976 I droid.apps.maps: Waiting for a blocking GC # Maps的GC阻塞 ``` **优化工具与方法**: 1. **内存泄漏检测**: - 使用Android Profiler捕获堆转储(Heap Dump),过滤 `Bitmap` 或 `Activity` 实例。 - 分析持有链:查找未释放的静态引用或后台线程。 2. **帧率分析**: - 启用GPU渲染模式分析: ```bash adb shell dumpsys gfxinfo com.example.app ``` - 关注 `Draw`/`Prepare`/`Process` 阶段的耗时(>16ms即掉帧)。 3. **Binder调用优化**: - 减少跨进程通信(如频繁调用 `setParameters()`),批量处理数据。 --- #### **四、总结解决方案** | 问题类型 | 关键日志特征 | 解决方案 | |-------------------------|----------------------------------|---------------------------------------| | Looper慢分发 | `Slow dispatch took [time]ms` | 移耗时操作到工作线程,减少主线程阻塞 | | 类找不到异常 | `ClassNotFoundException` | 替换内部API,增加反射兼容逻辑 | | 内存压力 | `am_low_memory` | 优化Bitmap,修复内存泄漏 | | UI渲染延迟 | `VRR`/`Surface` 相关日志 | 简化布局,减少过度绘制 | > 提示:系统级问题需结合 **内核日志**(`dmesg`)和 **ANR Traces**(`/data/anr/traces.txt`)分析[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值