(4.2.46)AndroidGodEye源码整体结构分析

涉及各种业务监听方法;
涉及本地架设server web的方法;
涉及js与java交互的方法;
涉及观察者模式和rxjava的基本运用;

一、概览

Android开发者在性能检测方面的工具一直比较匮乏,仅有的一些工具,比如Android Device Monitor,使用起来也有些繁琐,使用起来对开发者有一定的要求。而线上的App监控更无从谈起。

所以需要有一个系统能够提供Debug和Release阶段全方位的监控,更深入地了解对App运行时的状态

这里写图片描述
【图1】

AndroidGodEye是一个可以在PC浏览器中实时监控Android数据指标(比如性能指标,但是不局限于性能)的工具,你可以通过wifi/usb连接手机和pc,通过pc浏览器实时监控手机性能。

系统分为三部分:

  1. Core 核心部分,提供所有模块
  2. Debug Monitor部分,提供Debug阶段开发者面板
  3. Toolbox 快速接入工具集,给开发者提供各种便捷接入的工具

AndroidGodEye提供了多种监控模块,比如cpu、内存、卡顿、内存泄漏等等,并且提供了Debug阶段的Monitor看板实时展示这 些数据。而且提供了api供开发者在release阶段进行数据上报。

1.1 快速开始

参考Demo:https://github.com/Kyson/AndroidGodEyeDemo

STEP1: 引入依赖,使用gradle

dependencies {
  implementation 'cn.hikyson.godeye:godeye-core:VERSION_NAME'
  debugImplementation 'cn.hikyson.godeye:godeye-monitor:VERSION_NAME'
  releaseImplementation 'cn.hikyson.godeye:godeye-monitor-no-op:VERSION_NAME'
  implementation 'cn.hikyson.godeye:godeye-toolbox:VERSION_NAME'
}

VERSION_NAME可以看github的release名称

STEP2: 模块安装,GodEye类是AndroidGodEye的核心类,所有模块由它提供。

在应用入口安装所有模块:

// v1.7以下
// GodEye.instance().installAll(getApplication(),new CrashFileProvider(context))
// v1.7.0以上installAll api删除,使用如下:
GodEye.instance().install(Cpu.class, new CpuContextImpl())
                .install(Battery.class, new BatteryContextImpl(this))
                .install(Fps.class, new FpsContextImpl(this))
                .install(Heap.class, Long.valueOf(2000))
                .install(Pss.class, new PssContextImpl(this))
                .install(Ram.class, new RamContextImpl(this))
                .install(Sm.class, new SmContextImpl(this, 1000, 300, 800))
                .install(Traffic.class, new TrafficContextImpl())
                .install(Crash.class, new CrashFileProvider(this))
                .install(ThreadDump.class, new ThreadContextImpl())
                .install(DeadLock.class, new DeadLockContextImpl(GodEye.instance().getModule(ThreadDump.class).subject(), new DeadlockDefaultThreadFilter()))
                .install(Pageload.class, new PageloadContextImpl(this))
                .install(LeakDetector.class, new LeakContextImpl2(this, new PermissionRequest() {
                    @Override
                    public Observable<Boolean> dispatchRequest(Activity activity, String... permissions) {
                        return new RxPermissions(activity).request(permissions);
                    }
                }));

推荐在application中进行安装,否则部分模块可能工作异常

模块名需要安装数据引擎数据生产时机权限
battery内置定时
cpu内置定时
crash外部驱动安装后,一次性
fps内置定时
leakDetector内置发生时WRITE_EXTERNAL_STORAGE
heap内置定时
pss内置定时
ram内置定时
network外部驱动-
pageload内置发生时
sm内置发生时
startup外部驱动-
thread dump内置定时
deadlock内置定时并发生时
traffic外部驱动定时
可选部分

不需要的时候卸载模块(不推荐):

// v1.7以下
// GodEye.instance().uninstallAll();
// v1.7.0以上uninstallAll api删除,不提供便捷的卸载方法,如果非要卸载:
GodEye.instance().getModule(Cpu.class).uninstall();

注意:network和startup模块不需要安装和卸载

安装完之后相应的模块就开始输出数据了,一般来说可以使用模块的consume方法进行消费,比如cpu模块:

// v1.7以下
// GodEye.instance().cpu().subject().subscribe()
// v1.7.0以上使用class获取对应模块
GodEye.instance().getModule(Cpu.class).subject().subscribe();

就像我们之后会提到的Debug Monitor,也是通过消费这些数据进行展示的

STEP3: Debug面板安装,GodEyeMonitor类是AndroidGodEye的Debug监控面板的主要类,用来开始或者停止Debug面板的监控。

开始消费GodEye各个模块数据并输出到Debug面板:

GodEyeMonitor.work(context)

结束消费,关闭Debug面板:

GodEyeMonitor.shutDown()

STEP4:完成!开始使用:

手机与pc连接同一网段,在pc浏览器中访问手机ip+端口。或者如果你是用USB连接的话,执行adb forward tcp:5390 tcp:5390,然后pc浏览器中访问http://localhost:5390/

即可看到Debug面板!

端口默认是5390,也可以在GodEyeMonitor.work(context)中指定,一般在开发者在调用GodEyeMonitor.work(context)之后可以看到日志输出 ‘Open AndroidGodEye dashboard [ http://xxx.xxx.xxx.xxx:5390” ] in your browser…’ 中包含了访问地址。

二、cn.hikyson.godeye.core源码分析

Core 核心部分,提供所有模块

2.1 安装 和 观察者模式

  • 相关模块的管理器,继承Install并实现自己的执行操作(如果有的话)
public interface Install<T> {
    void install(T config);

    void uninstall();
}
  • 管理器往往继承ProduceableSubject,以实现观察者模式
    • 外部业务调用可以通过subject()获取被观察者对象实例,来自行subscribe注册监听;
    • 业务引擎中持有管理器,并调用produce(T)从而产生事件—>并通知所有观察者响应
public class ProduceableSubject<T> implements SubjectSupport<T>, Producer<T> {
    private Subject<T> mSubject;

    public ProduceableSubject() {
        mSubject = createSubject();
    }

    protected Subject<T> createSubject() {
        return PublishSubject.create();
    }

    @Override
    public void produce(T data) {
        mSubject.onNext(data);
    }

    @Override
    public Observable<T> subject() {
        return mSubject;
    }
}

public interface SubjectSupport<T> {
    Observable<T> subject();
}


public interface Producer<T> {
    void produce(T data);
}

2.1.1 示例

public class Cpu extends ProduceableSubject<CpuInfo> implements Install<CpuContext> {
    private CpuEngine mCpuEngine;

    public synchronized void install() {
        install(new CpuContextImpl());
    }

    @Override
    public synchronized void install(CpuContext config) {
        if (mCpuEngine != null) {
            L.d("cpu already installed , ignore.");
            return;
        }
        mCpuEngine = new CpuEngine(this, config.intervalMillis(), config.sampleMillis());
        mCpuEngine.work();
        L.d("cpu installed");
    }

    @Override
    public synchronized void uninstall() {
        if (mCpuEngine == null) {
            L.d("cpu already uninstalled , ignore.");
            return;
        }
        mCpuEngine.shutdown();
        mCpuEngine = null;
        L.d("cpu uninstalled");
    }
}

2.2 业务引擎

  • 对应模块的具体业务控制器,实现Engine来完成自己的功能
    • engine会在Install子类中实例化,并借助对应的Config参数进行实例化;
    • engine的相关函数会在Install的相关类中调用
    • engine的实现类会持有观察者模式的producer,以通知被观察者信息产生
public interface Engine {
    void work();

    void shutdown();
}

2.2.1 示例

public class CpuEngine implements Engine {
    private Producer<CpuInfo> mProducer;
    private long mIntervalMillis;
    private long mSampleMillis;
    private CompositeDisposable mCompositeDisposable;

    public CpuEngine(Producer<CpuInfo> producer, long intervalMillis, long sampleMillis) {
        mProducer = producer;
        mIntervalMillis = intervalMillis;
        mSampleMillis = sampleMillis;
        mCompositeDisposable = new CompositeDisposable();
    }

    @Override
    public void work() {
        mCompositeDisposable.add(Observable.interval(mIntervalMillis, TimeUnit.MILLISECONDS).
                concatMap(new Function<Long, ObservableSource<CpuInfo>>() {
                    @Override
                    public ObservableSource<CpuInfo> apply(Long aLong) throws Exception {
                        ThreadUtil.ensureWorkThread("cpu");
                        return create();
                    }
                }).subscribe(new Consumer<CpuInfo>() {
            @Override
            public void accept(CpuInfo food) throws Exception {
                mProducer.produce(food);
            }
        }, new Consumer<Throwable>() {
            @Override
            public void accept(Throwable throwable) throws Exception {
                L.e(String.valueOf(throwable));
            }
        }));
    }

    @Override
    public void shutdown() {
        mCompositeDisposable.dispose();
    }

    private Observable<CpuInfo> create() {
        final CpuSnapshot startSnapshot = CpuSnapshot.snapshot();
        return Observable.timer(mSampleMillis, TimeUnit.MILLISECONDS).map(new Function<Long, CpuInfo>() {
            @Override
            public CpuInfo apply(Long aLong) throws Exception {
                CpuSnapshot endSnapshot = CpuSnapshot.snapshot();
                float totalTime = (endSnapshot.total - startSnapshot.total) * 1.0f;
                if (totalTime <= 0) {
                    throw new GodEyeInvalidDataException("totalTime must greater than 0");
                }
                long idleTime = endSnapshot.idle - startSnapshot.idle;
                double totalRatio = (totalTime - idleTime) / totalTime;
                double appRatio = (endSnapshot.app - startSnapshot.app) / totalTime;
                double userRatio = (endSnapshot.user - startSnapshot.user) / totalTime;
                double systemRatio = (endSnapshot.system - startSnapshot.system) / totalTime;
                double ioWaitRatio = (endSnapshot.ioWait - startSnapshot.ioWait) / totalTime;
                if (!isValidRatios(totalRatio, appRatio, userRatio, systemRatio, ioWaitRatio)) {
                    throw new GodEyeInvalidDataException("not valid ratio");
                }
                return new CpuInfo(totalRatio, appRatio, userRatio, systemRatio, ioWaitRatio);
            }
        });
    }

    private boolean isValidRatios(Double... ratios) {
        for (double ratio : ratios) {
            if (ratio < 0 || ratio > 1) {
                return false;
            }
        }
        return true;
    }
}

三、cn.hikyson.android.godeye.toolbox源码分析

Toolbox 快速接入工具集,给开发者提供各种便捷接入的工具

  1. 提供默认的Crash File控制器;
  2. 提供默认的权限申请机制;
  3. 提供基本的gson序列对象;
  4. 提供Startup的快捷使用方式;

四、no-op: cn.hikyson.godeye.monitor源码分析

4.1 什么是no-op

一般私有的module会比较稳定,并且对外暴露的方法不多,甚至会是别的项目组开发的,所以如果这个module本身是用来做监控统计这样的不影响功能工作的话,建议和开发者商量提供no-op版本

debugCompile(project(':share-lib-no-op')) {}
releaseCompile(project(':share-lib')) {}
debugCompile(project(':zxing-no-op')) {}
releaseCompile(project(':zxing')) {}

用no-op版本的好处就是只使用接口不使用实现,将实现的代码全部剔除,如果做的好的话甚至可以再debug时不用multidex

4.2 源码分析

空实现的“Debug Monitor部分,提供Debug阶段开发者面板”,release版本中使用

public class GodEyeMonitor {

    public interface AppInfoConext {
        Context getContext();

        Map<String, Object> getAppInfo();
    }

    public static void work(Context context) {
        //no op
    }

    public static void work(Context context, int port) {
        //no op
    }

    public static void shutDown() {
        //no op
    }

    public static void injectAppInfoConext(AppInfoConext appInfoConext) {
        //no op
    }
}

五、cn.hikyson.godeye.monitor源码分析

  • GodEyeMonitor监控管理器
    • Watcher:数据引擎,调用GodEye注册观察者,并用于生产各项数据
      • Pipe: 数据管道,单例模式,用于存储引擎生产的数据,并支持server 的读取
    • ClientServer: 开启线程,接收http请求
      • RequestHandler: 实际的http请求处理器
      • Router:响应http请求地址,并返回对应的字节流
      • 接收http://localhost:5390/,通过Router返回对应的本地资源文件index.html
      • 接收各种.html中的资源文件请求,并返回对应本地资源文件.js.css
      • 接收.js的内部请求,并调用对应Module接口的popdata接口,该接口会从Pipe中读取数据并转化为字节数组

5.1 GodEyeMonitor监控管理器

  1. 实例构建socket server;
  2. 实现server的运转和停止;
  /**
     * monitor开始工作
     */
    public static synchronized void work(Context context, int port) {
        if (sIsWorking) {
            return;
        }
        sIsWorking = true;
        if (context == null) {
            throw new IllegalStateException("context can not be null.");
        }
        Context applicationContext = context.getApplicationContext();
        sWatcher = new Watcher();
        sWatcher.observeAll();
        Router.get().init(applicationContext);
        initServer(applicationContext, port);
        L.d("GodEye monitor is working...");
    }

    //启动socket server
    private static void initServer(Context context, int port) {
        sClientServer = new ClientServer(port);
        sClientServer.start();
        L.d(getAddressLog(context, port));
        L.d("Leak dump files are in /storage/download/leakcanary-" + context.getPackageName());
    }

    /**
     * monitor停止工作
     */
    public static synchronized void shutDown() {
        if (sClientServer != null) {
            sClientServer.stop();
            sClientServer = null;
        }
        if (sWatcher != null) {
            sWatcher.cancelAllObserve();
            sWatcher = null;
        }
        sIsWorking = false;
        L.d("GodEye monitor stopped.");
    }

5.2 Watcher数据引擎

调用GodEye注册观察者,并用于生产各项数据

public class Watcher {
    private Pipe mPipe;
    private CompositeDisposable mCompositeDisposable;

    public Watcher() {
        mPipe = Pipe.instance();
        mCompositeDisposable = new CompositeDisposable();
    }

    /**
     * 监听所有的数据
     */
    public void observeAll() {
        GodEye godEye = GodEye.instance();
        mCompositeDisposable.add(godEye.getModule(Battery.class).subject().subscribe(new Consumer<BatteryInfo>() {
            @Override
            public void accept(BatteryInfo batteryInfo) throws Exception {
                mPipe.pushBatteryInfo(batteryInfo);
            }
        }));
        mCompositeDisposable.add(godEye.getModule(Cpu.class).subject().subscribe(new Consumer<CpuInfo>() {
            @Override
            public void accept(CpuInfo cpuInfo) throws Exception {
                mPipe.pushCpuInfo(cpuInfo);
            }
        }));
        mCompositeDisposable.add(godEye.getModule(Traffic.class).subject().subscribe(new Consumer<TrafficInfo>() {
            @Override
            public void accept(TrafficInfo trafficInfo) throws Exception {
                mPipe.pushTrafficInfo(trafficInfo);
            }
        }));
        mCompositeDisposable.add(godEye.getModule(Fps.class).subject().subscribe(new Consumer<FpsInfo>() {
            @Override
            public void accept(FpsInfo fpsInfo) throws Exception {
                mPipe.pushFpsInfo(fpsInfo);
            }
        }));
        mCompositeDisposable.add(godEye.getModule(LeakDetector.class).subject().subscribe(new Consumer<LeakQueue.LeakMemoryInfo>() {
            @Override
            public void accept(LeakQueue.LeakMemoryInfo leakMemoryInfo) throws Exception {
                mPipe.pushLeakMemoryInfos(leakMemoryInfo);
            }
        }));
        mCompositeDisposable.add(godEye.getModule(Sm.class).subject().subscribe(new Consumer<BlockInfo>() {
            @Override
            public void accept(BlockInfo blockInfo) throws Exception {
                mPipe.pushBlockInfos(blockInfo);
            }
        }));
        mCompositeDisposable.add(godEye.getModule(Network.class).subject().subscribe(new Consumer<RequestBaseInfo>() {
            @Override
            public void accept(RequestBaseInfo requestBaseInfo) throws Exception {
                mPipe.pushRequestBaseInfos(requestBaseInfo);
            }
        }));
        mCompositeDisposable.add(godEye.getModule(Startup.class).subject().subscribe(new Consumer<StartupInfo>() {
            @Override
            public void accept(StartupInfo startupInfo) throws Exception {
                mPipe.pushStartupInfo(startupInfo);
            }
        }));
        mCompositeDisposable.add(godEye.getModule(Ram.class).subject().subscribe(new Consumer<RamInfo>() {
            @Override
            public void accept(RamInfo ramInfo) throws Exception {
                mPipe.pushRamInfo(ramInfo);
            }
        }));
        mCompositeDisposable.add(godEye.getModule(Pss.class).subject().subscribe(new Consumer<PssInfo>() {
            @Override
            public void accept(PssInfo pssInfo) throws Exception {
                mPipe.pushPssInfo(pssInfo);
            }
        }));
        mCompositeDisposable.add(godEye.getModule(Heap.class).subject().subscribe(new Consumer<HeapInfo>() {
            @Override
            public void accept(HeapInfo heapInfo) throws Exception {
                mPipe.pushHeapInfo(heapInfo);
            }
        }));
        mCompositeDisposable.add(godEye.getModule(ThreadDump.class).subject().map(new Function<List<Thread>, List<ThreadInfo>>() {
            @Override
            public List<ThreadInfo> apply(List<Thread> threads) throws Exception {
                return ThreadInfo.convert(threads);
            }
        }).subscribe(new Consumer<List<ThreadInfo>>() {
            @Override
            public void accept(List<ThreadInfo> threads) throws Exception {
                mPipe.pushThreadInfo(threads);
            }
        }));
        mCompositeDisposable.add(godEye.getModule(DeadLock.class).subject().map(new Function<List<Thread>, List<Long>>() {
            @Override
            public List<Long> apply(List<Thread> threads) throws Exception {
                List<Long> threadIds = new ArrayList<>();
                for (Thread thread : threads) {
                    if (thread == null) {
                        continue;
                    }
                    threadIds.add(thread.getId());
                }
                return threadIds;
            }
        }).subscribe(new Consumer<List<Long>>() {
            @Override
            public void accept(List<Long> threads) throws Exception {
                mPipe.pushDeadLocks(threads);
            }
        }));
        mCompositeDisposable.add(godEye.getModule(Crash.class).subject().map(new Function<List<CrashInfo>, CrashInfo>() {
            @Override
            public CrashInfo apply(List<CrashInfo> crashInfos) throws Exception {
                //获取最近的一次崩溃
                if (crashInfos == null || crashInfos.isEmpty()) {
                    return CrashInfo.INVALID;
                }
                Collections.sort(crashInfos, new Comparator<CrashInfo>() {
                    @Override
                    public int compare(CrashInfo o1, CrashInfo o2) {
                        if (o1.timestampMillis < o2.timestampMillis) {
                            return 1;
                        }
                        if (o1.timestampMillis > o2.timestampMillis) {
                            return -1;
                        }
                        return 0;
                    }
                });
                return crashInfos.get(0);
            }
        }).subscribe(new Consumer<CrashInfo>() {
            @Override
            public void accept(CrashInfo crashInfo) throws Exception {
                if (crashInfo == CrashInfo.INVALID) {
                    return;
                }
                mPipe.pushCrashInfo(crashInfo);
            }
        }));
        mCompositeDisposable.add(godEye.getModule(Pageload.class).subject().subscribe(new Consumer<List<PageloadInfo>>() {
            @Override
            public void accept(List<PageloadInfo> pageloadInfos) throws Exception {
                mPipe.pushPageloadInfo(pageloadInfos);
            }
        }));
    }

    public void cancelAllObserve() {
        mCompositeDisposable.dispose();
    }
}

5.3 Pipe数据管道

单例模式,用于存储引擎生产的数据,并支持server 的读取

public class Pipe {

    private Pipe() {
    }

    private static class InstanceHolder {
        private static final Pipe sInstance = new Pipe();
    }

    public static Pipe instance() {
        return InstanceHolder.sInstance;
    }

    private BatteryInfo mBatteryInfo;

    public void pushBatteryInfo(BatteryInfo batteryInfo) {
        mBatteryInfo = batteryInfo;
    }

    public BatteryInfo popBatteryInfo() {
        return mBatteryInfo;
    }

    private List<CpuInfo> mCpuInfos = new ArrayList<>();
    private final Object mLockForCpu = new Object();

    public void pushCpuInfo(CpuInfo cpuInfo) {
        synchronized (mLockForCpu) {
            mCpuInfos.add(cpuInfo);
        }
    }

    public Collection<CpuInfo> popCpuInfo() {
        synchronized (mLockForCpu) {
            final Collection<CpuInfo> cpuInfos = cloneList(mCpuInfos);
            mCpuInfos.clear();
            return cpuInfos;
        }
    }
}   

5.4 ClientServer

开启线程,接收http请求

public class ClientServer implements Runnable {
    private static final String TAG = "AndroidGodEyeServer";

    private final int mPort;

    private boolean mIsRunning;

    private ServerSocket mServerSocket;

    private final RequestHandler mRequestHandler;

    public ClientServer(int port) {
        mRequestHandler = new RequestHandler();
        mPort = port;
    }

    public void start() {
        mIsRunning = true;
        new Thread(this).start();
    }

    public void stop() {
        try {
            mIsRunning = false;
            if (null != mServerSocket) {
                mServerSocket.close();
                mServerSocket = null;
            }
        } catch (Exception e) {
            Log.e(TAG, "Error closing the server socket.", e);
        }
    }

    @Override
    public void run() {
        try {
            mServerSocket = new ServerSocket(mPort);
            while (mIsRunning) {
                Socket socket = mServerSocket.accept();
                mRequestHandler.handle(socket);
                socket.close();
            }
        } catch (SocketException e) {
            //服务器关闭
            Log.e(TAG, "socket exception,maybe server closed.", e);
        } catch (IOException e) {
            Log.e(TAG, "Web server error.", e);
        } catch (Throwable ignore) {
            Log.e(TAG, "other exception.", ignore);
        }
    }

    public boolean isRunning() {
        return mIsRunning;
    }
}

5.5 RequestHandler: 实际的http请求处理器

public class RequestHandler {

    public RequestHandler() {
    }

    public void handle(Socket socket) throws Throwable {
        BufferedReader reader = null;
        PrintStream output = null;
        try {
            String pathAndParams = null;
            reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String line;
            while (!TextUtils.isEmpty(line = reader.readLine())) {
                if (line.startsWith("GET /")) {
                    int start = line.indexOf('/') + 1;
                    int end = line.indexOf(' ', start);
                    pathAndParams = line.substring(start, end);
                    break;
                }
            }
            output = new PrintStream(socket.getOutputStream());
            if (pathAndParams == null || pathAndParams.isEmpty()) {
                pathAndParams = "index.html";
            }
            Uri uri = parseUri(pathAndParams);
            byte[] bytes = Router.get().process(uri);
            if (null == bytes) {
                writeServerError(output);
                return;
            }
            output.println("HTTP/1.0 200 OK");
            output.println("Content-Type: " + prepareMimeType(uri.getPath()));
            output.println("Content-Length: " + bytes.length);
            output.println();
            output.write(bytes);
            output.flush();
        } finally {
            IoUtil.closeSilently(output);
            IoUtil.closeSilently(reader);
        }
    }

    private Uri parseUri(String url) throws UnsupportedEncodingException {
        return Uri.parse(URLDecoder.decode(url, "UTF-8"));
    }

    private void writeServerError(PrintStream output) {
        output.println("HTTP/1.0 500 Internal Server Error");
        output.flush();
    }

    private static String prepareMimeType(String fileName) {
        if (TextUtils.isEmpty(fileName)) {
            return null;
        } else if (fileName.endsWith(".html")) {
            return "text/html";
        } else if (fileName.endsWith(".js")) {
            return "application/javascript";
        } else if (fileName.endsWith(".css")) {
            return "text/css";
        } else {
            return "application/octet-stream";
        }
    }
}

5.6 Router

响应http请求地址,并返回对应的字节流

  • 接收http://localhost:5390/,通过Router返回对应的本地资源文件index.html
  • 接收各种.html中的资源文件请求,并返回对应本地资源文件.js.css
  • 接收.js的内部请求,并调用对应Module接口的popdata接口,该接口会从Pipe中读取数据并转化为字节数组
public class Router {
    private Map<String, Module> mRouteModules;

    private Router() {
    }

    private static class InstanceHolder {
        private static Router sInstance = new Router();
    }

    public static Router get() {
        return InstanceHolder.sInstance;
    }

    public void init(Context context) {
        mRouteModules = new ArrayMap<>();
        AssetsModule assetsModule = new AssetsModule(context, "androidgodeye");
        mRouteModules.put("assets", assetsModule);
        AppInfoModule appInfoModule = new AppInfoModule();
        mRouteModules.put("appinfo", appInfoModule);
        BatteryModule batteryModule = new BatteryModule();
        mRouteModules.put("battery", batteryModule);
        BlockModule blockModule = new BlockModule();
        mRouteModules.put("block", blockModule);
        CpuModule cpuModule = new CpuModule();
        mRouteModules.put("cpu", cpuModule);
        FpsModule fpsModule = new FpsModule();
        mRouteModules.put("fps", fpsModule);
        HeapModule heapModule = new HeapModule();
        mRouteModules.put("heap", heapModule);
        LeakMemoryModule LeakMemoryModule = new LeakMemoryModule();
        mRouteModules.put("leakMemory", LeakMemoryModule);
        NetworkModule networkModule = new NetworkModule();
        mRouteModules.put("network", networkModule);
        PssModule pssModule = new PssModule();
        mRouteModules.put("pss", pssModule);
        RamModule ramModule = new RamModule();
        mRouteModules.put("ram", ramModule);
        StartUpModule startUpModule = new StartUpModule();
        mRouteModules.put("startup", startUpModule);
        TrafficModule trafficModule = new TrafficModule();
        mRouteModules.put("traffic", trafficModule);
        ThreadModule threadModule = new ThreadModule();
        mRouteModules.put("thread", threadModule);
        CrashModule crashModule = new CrashModule();
        mRouteModules.put("crash", crashModule);
        PageloadModule pageloadModule = new PageloadModule();
        mRouteModules.put("pageload", pageloadModule);
    }

    public byte[] process(Uri uri) throws Throwable {
        String moduleName = uri.getPath();
        Module module = mRouteModules.get(moduleName);
        if (module == null) {
            return mRouteModules.get("assets").process(moduleName, uri);
        }
        return module.process(moduleName, uri);
    }
}

5.6.1 Module业务模块数据流生成器

  • 返回byte[]给js
  • 定义ResultWrapper为server和js的通讯协议
public interface Module {
    public byte[] process(String path, Uri uri) throws Throwable;

    public static class ResultWrapper<T> {

        public static final int SUCCESS = 1;
        public static final int DEFAULT_FAIL = 0;
        public T data;
        public int code;
        public String message;

        public ResultWrapper(int code, String message, T data) {
            this.data = data;
            this.code = code;
            this.message = message;
        }

        public ResultWrapper(String message) {
            this.data = null;
            this.code = DEFAULT_FAIL;
            this.message = message;
        }

        public ResultWrapper(T data) {
            this.data = data;
            this.code = SUCCESS;
            this.message = "success";
        }

        public byte[] toBytes() throws UnsupportedEncodingException {
            return GsonUtil.toJson(this).getBytes("UTF-8");
        }
    }
}
  • 封装
public abstract class BaseModule<T> implements Module {
    @Override
    public byte[] process(String path, Uri uri) throws Throwable {
        T t = popData();
        if (t == null) {
            return new ResultWrapper("no data for " + getClass().getSimpleName()).toBytes();
        }
        return new ResultWrapper<>(t).toBytes();
    }

    abstract T popData();
}

public abstract class BaseListModule<T> implements Module {
    @Override
    public byte[] process(String path, Uri uri) throws Throwable {
        Collection<T> ts = popData();
        if (ts == null || ts.isEmpty()) {
            return new ResultWrapper("no datas for " + getClass().getSimpleName()).toBytes();
        }
        return new ResultWrapper<>(ts).toBytes();
    }

    abstract Collection<T> popData();
}

public class AssetsModule implements Module {
    private AssetManager mAssets;
    private String mPreffix;

    public AssetsModule(Context context, String preffix) {
        mPreffix = preffix;
        mAssets = context.getResources().getAssets();
    }

    @Override
    public byte[] process(String path, Uri uri) throws Throwable {
        String fileName = mPreffix + "/" + uri.getPath();
        return loadContent(fileName, mAssets);
    }

    private static byte[] loadContent(String fileName, AssetManager assetManager) throws IOException {
        InputStream input = null;
        try {
            ByteArrayOutputStream output = new ByteArrayOutputStream();
            input = assetManager.open(fileName);
            byte[] buffer = new byte[1024];
            int size;
            while (-1 != (size = input.read(buffer))) {
                output.write(buffer, 0, size);
            }
            output.flush();
            return output.toByteArray();
        } catch (FileNotFoundException e) {
            return null;
        } finally {
            try {
                if (null != input) {
                    input.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
  • 具体实现
public class CpuModule extends BaseListModule<CpuInfo> {

    @Override
    Collection<CpuInfo> popData() {
        return Pipe.instance().popCpuInfo();
    }
}

5.7 js调用

5.7.1 .js

  1. 构建请求地址
  2. 处理回到数据
'use strict';
$(document).ready(function () {
    cpuUtil.setup(document.getElementById('cpu_chart'));
    setInterval(refresh, interval)
});

var interval = 2000;

function refresh() {
    requestUtil.getData("/cpu", function (data) {
        refreshView(data);
    }, function () {

    });
}

function refreshView(cpuInfos) {
    if (cpuInfos.length > 0) {
        cpuUtil.refreshChart(cpuInfos);
    }
}

5.7.2 .util.js

'use strict';
var cpuUtil = function () {

    var cpuChart;
    var cpuOptions;

    function setup(chartContainer) {
        cpuChart = echarts.init(chartContainer, 'dark');

        cpuOptions = {
            // title: {
            //     text: 'CPU',
            //     left: "center",
            //     top: '2%'
            // },
            legend: {
                data: ['Total', 'App', 'UserProcess', 'SystemProcess'],
                top: '2%'
            },
            tooltip: {
                trigger: 'axis',
                axisPointer: {
                    type: 'cross',
                    label: {
                        backgroundColor: '#6a7985'
                    }
                },
                formatter: function (params) {
                    var tip = "";
                    for (var i = 0; i < params.length; i++) {
                        var data = params[i];
                        tip = tip + data.seriesName + ' : ' + data.value.toFixed(3) + ' % <br/>';
                    }
                    return tip;
                }
            },
            animation: true,
            dataZoom: {
                show: false,
                start: 0,
                end: 100
            },
            toolbox: {
                feature: {
                    saveAsImage: {}
                }
            },
            grid: {
                left: '6%',
                right: '5%',
                bottom: '5%',
                top: '15%',
                containLabel: true
            },
            xAxis: [
                {
                    type: 'category',
                    boundaryGap: false,
                    data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
                }
            ],
            yAxis: [
                {
                    type: 'value',
                    name: "Cpu Usage Rate(Percentage)",
                    nameLocation: 'middle',
                    nameRotate: 90,
                    nameGap: 35,
                    min: 0,
                    max: 100
                }
            ],
            series: [
                {
                    name: 'Total',
                    type: 'line',
                    data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
                },
                {
                    name: 'App',
                    type: 'line',
                    data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
                },
                {
                    name: 'UserProcess',
                    type: 'line',
                    data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
                },
                {
                    name: 'SystemProcess',
                    type: 'line',
                    data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
                }
            ]
        };
        cpuChart.setOption(cpuOptions);
    }

    function refreshCpu(cpuInfos) {
        for (var i = 0; i < cpuInfos.length; i++) {
            var cpuInfo = cpuInfos[i];
            var axisData = (new Date()).toLocaleTimeString();
            cpuOptions.xAxis[0].data.shift();
            cpuOptions.xAxis[0].data.push(axisData);
            cpuOptions.series[0].data.shift();
            cpuOptions.series[0].data.push(cpuInfo.totalUseRatio * 100);
            cpuOptions.series[1].data.shift();
            cpuOptions.series[1].data.push(cpuInfo.appCpuRatio * 100);
            cpuOptions.series[2].data.shift();
            cpuOptions.series[2].data.push(cpuInfo.userCpuRatio * 100);
            cpuOptions.series[3].data.shift();
            cpuOptions.series[3].data.push(cpuInfo.sysCpuRatio * 100);
        }
        cpuChart.setOption(cpuOptions);
    }

    return {
        setup: setup,
        refreshChart: refreshCpu
    }

}();

六、与app server的交互方式

 public void handle(Socket socket) throws Throwable {
        BufferedReader reader = null;
        PrintStream output = null;
        try {
            String pathAndParams = null;
            reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String line;
            while (!TextUtils.isEmpty(line = reader.readLine())) {
                if (line.startsWith("GET /")) {//【步骤1】
                    int start = line.indexOf('/') + 1;
                    int end = line.indexOf(' ', start);
                    pathAndParams = line.substring(start, end);
                    break;
                }
            }
            output = new PrintStream(socket.getOutputStream());
            if (pathAndParams == null || pathAndParams.isEmpty()) {//【步骤2】
                pathAndParams = "index.html";
            }
            Uri uri = parseUri(pathAndParams);
            byte[] bytes = Router.get().process(uri);//【步骤3】
            if (null == bytes) {
                writeServerError(output);
                return;
            }
            output.println("HTTP/1.0 200 OK");
            output.println("Content-Type: " + prepareMimeType(uri.getPath()));//【步骤4】
            output.println("Content-Length: " + bytes.length);
            output.println();
            output.write(bytes);
            output.flush();
        } finally {
            IoUtil.closeSilently(output);
            IoUtil.closeSilently(reader);
        }
    }

    private Uri parseUri(String url) throws UnsupportedEncodingException {
        return Uri.parse(URLDecoder.decode(url, "UTF-8"));
    }

    private void writeServerError(PrintStream output) {
        output.println("HTTP/1.0 500 Internal Server Error");
        output.flush();
    }

    private static String prepareMimeType(String fileName) {
        if (TextUtils.isEmpty(fileName)) {
            return null;
        } else if (fileName.endsWith(".html")) {
            return "text/html";
        } else if (fileName.endsWith(".js")) {
            return "application/javascript";
        } else if (fileName.endsWith(".css")) {
            return "text/css";
        } else {
            return "application/octet-stream";
        }
    }
    public byte[] process(Uri uri) throws Throwable {
        String moduleName = uri.getPath();
        Module module = mRouteModules.get(moduleName);
        if (module == null) {
            return mRouteModules.get("assets").process(moduleName, uri);
        }
        return module.process(moduleName, uri);
    }

192.200.36.43:5390 就会触发到该主机socket的监听
内部文件的请求,默认就是当前 socket地址,再加上 path

【步骤1】GET / HTTP/1.1
【步骤2】pathAndParams = “index.html”
【步骤3】module == null —>返回assets中文件
【步骤4】.html

  • html中的资源:< script src=”resources/jquery.min.js”>< /script>

【步骤1】GET /resources/jquery.min.js HTTP/1.1
【步骤2】pathAndParams = “/resources/jquery.min.js”
【步骤3】module == null —>返回assets中文件
【步骤4】.js

  • js中的请求:/block

【步骤1】GET /block HTTP/1.1
【步骤2】pathAndParams = “/block”
【步骤3】module == block —>返回block实体的json字符串所转化的byte[]数据流。该数据会在js中被json解析
【步骤4】application/octet-stream

参考文献

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值