Androidカートン自動化検出実現

5842 ワード

最近、Androidの文章を読んだとき、Androidカートンを検出するオンライン実装案を見て、自分で簡単に実現しました.
その原理はAndroidのメッセージ処理メカニズムに由来し、1つのスレッドはいくらHandlerがあっても、1つのLooperしか存在せず、メインスレッドが実行する任意のコードはLooperを通過する.loop()メソッドが実行されます.Looper関数では、各message処理の前後で呼び出されるmLoggingオブジェクトがあります.メインスレッドにカートンが発生しました.それはdispatchMessage()メソッドで時間のかかる操作を実行したに違いありません.では、このmLoggingオブジェクトを介してdispatchMessage()を監視することができます.
Looperのメッセージループメソッドloopの実装:
public static void loop() {
        final Looper me = myLooper();
        final MessageQueue queue = me.mQueue;

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
           
            ...

            try {
                msg.target.dispatchMessage(msg);
            } catch (Exception exception) {
                
            } finally {
                
            }
            
            ...

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

        }
    }

Looperのloop()メソッドでは、各メッセージを実行する前後にloggingによって印刷出力が行われる.メッセージを実行する前に出力される「>>>>>>Dispatching to」であり、メッセージを実行した後に出力される「<<<したがって、具体的な実装手順は、次のとおりです.
  • 1、まずLooperを使う必要があります.getMainLooper().setMessageLogging()は、独自のPrinter実装クラスを設定して出力loggingを印刷します.これにより、各messageが実行される前と後に、私たちが設定したこのPrinterインプリメンテーションクラスが呼び出されます.
  • 2、もし私たちが">>>>>Dispatching to"に一致したら、私たちは1行のコードを実行することができます:つまり指定した時間のしきい値の後で、私たちはサブスレッドで1つのタスクを実行して、このタスクは現在のメインスレッドのスタック情報と現在のシーン情報を取得して、例えば:メモリサイズ、携帯電話、ネットワークの状態など.
  • 3、指定したしきい値内に「<<<<
    上記の実装スキームにより、初歩的なコードを実装します.
    import android.os.Looper;
    import android.util.Log;
    import android.util.LogPrinter;
    
    public class LogMonitorPrinter extends LogPrinter {
        private static final String TAG = "LogMonitorPrinter";
        private long beginTime;
        private int timeoutInterval = 1000;
        private MonitorThread monitorThread;
    
        /**
         * Create a new Printer that sends to the log with the given priority
         * and tag.
         *
         * @param priority The desired log priority:
         *                 {@link Log#VERBOSE Log.VERBOSE},
         *                 {@link Log#DEBUG Log.DEBUG},
         *                 {@link Log#INFO Log.INFO},
         *                 {@link Log#WARN Log.WARN}, or
         *                 {@link Log#ERROR Log.ERROR}.
         * @param tag      A string tag to associate with each printed log statement.
         */
        public LogMonitorPrinter(int priority, String tag) {
            super(priority, tag);
        }
    
    
        public void setTimeoutInterval(int interval) {
            this.timeoutInterval = interval;
        }
    
        @Override
        public void println(String x) {
            if (x.startsWith(">>>>> Dispatching to")) {
                beginTime = System.currentTimeMillis();
                monitorThread = new MonitorThread(timeoutInterval);
                monitorThread.start();
            }
    
            if (x.startsWith("<<<<< Finished to")) {
                long taskTime = System.currentTimeMillis() - beginTime;
                monitorThread.interrupt();
                if (taskTime > timeoutInterval) {
                    Log.w(TAG, "taskTime: " + taskTime);
                }
            }
            super.println(x);
        }
    
        private static String getMainStackTrace() {
            StackTraceElement[] stackTraceElements = Looper.getMainLooper().getThread().getStackTrace();
            StringBuilder sb = new StringBuilder();
            for (StackTraceElement element : stackTraceElements) {
                sb.append(element.toString());
                sb.append("
    "); } return sb.toString(); } static class MonitorThread extends Thread { private long interval; public MonitorThread(int interval) { this.interval = interval; } @Override public void run() { try { Thread.sleep(interval); } catch (InterruptedException e) { return; } Log.w(TAG, getMainStackTrace()); } } }

    メインスレッドLooperのmLoggingを設定するには:
    Looper.getMainLooper().setMessageLogging(new LogMonitorPrinter(Log.DEBUG, "Monitor"));

    独自に適用したApplication onCreate()メソッドでカスタムmLoggingを設定できます.
    では、システムでLooperのmLoggingはどのように設定されているのでしょうか.Looperを追跡するsetMessageLogging()メソッドは、Activity Threadのmainメソッドで呼び出されたものであり、システムがこの設定を閉じただけであることがわかります.
            if (false) {
                Looper.myLooper().setMessageLogging(new
                        LogPrinter(Log.DEBUG, "ActivityThread"));
            }

    上記のコードは、検証によってカートンタスクのスタック情報を取得することができるが、printlnではタスクごとにnew Threadが必要であり、スレッドの頻繁な作成と破棄はパフォーマンスの問題を引き起こすため、スレッドプールを作成することによってスレッドを多重化することができる.ここではHandlerThreadの使用を考慮することもできる.
    システムのソースコードはActivity ThreadでmLoggingの設定がオフになっていることがわかります.log情報の出力もcpu性能を消費するので、実際に使用するときはカートン情報だけを出力することができます.
     
    このシナリオをオンラインに展開するには,ユーザのサンプリング,スタック情報の重さ除去,ファイル圧縮など,収集情報のアップロードも考慮し,適切なタイミングでアップロードするなどする必要がある.
     
    参照先:https://juejin.im/post/5e41fb7de51d4526c80e9108