diff --git a/core/runtime/android/runtime/src/main/aidl/org/hapjs/analyzer/model/LogData.aidl b/core/runtime/android/runtime/src/main/aidl/org/hapjs/analyzer/model/LogData.aidl new file mode 100644 index 00000000..6d0cc2df --- /dev/null +++ b/core/runtime/android/runtime/src/main/aidl/org/hapjs/analyzer/model/LogData.aidl @@ -0,0 +1,6 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ +package org.hapjs.analyzer.model; + +parcelable LogData; \ No newline at end of file diff --git a/core/runtime/android/runtime/src/main/aidl/org/hapjs/runtime/sandbox/ILogListener.aidl b/core/runtime/android/runtime/src/main/aidl/org/hapjs/runtime/sandbox/ILogListener.aidl new file mode 100644 index 00000000..0f0df905 --- /dev/null +++ b/core/runtime/android/runtime/src/main/aidl/org/hapjs/runtime/sandbox/ILogListener.aidl @@ -0,0 +1,11 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ + +package org.hapjs.runtime.sandbox; + +import org.hapjs.analyzer.model.LogData; + +interface ILogListener { + void onLog(in List logs); +} \ No newline at end of file diff --git a/core/runtime/android/runtime/src/main/aidl/org/hapjs/runtime/sandbox/ISandbox.aidl b/core/runtime/android/runtime/src/main/aidl/org/hapjs/runtime/sandbox/ISandbox.aidl index 0453b550..ba4ef604 100644 --- a/core/runtime/android/runtime/src/main/aidl/org/hapjs/runtime/sandbox/ISandbox.aidl +++ b/core/runtime/android/runtime/src/main/aidl/org/hapjs/runtime/sandbox/ISandbox.aidl @@ -6,10 +6,12 @@ package org.hapjs.runtime.sandbox; import android.os.ParcelFileDescriptor; +import org.hapjs.runtime.sandbox.ILogListener; import org.hapjs.runtime.sandbox.ILogProvider; interface ISandbox { void init(in Map configs); ParcelFileDescriptor[] createChannel(in ParcelFileDescriptor[] readSide); void setLogProvider(ILogProvider logProvider); + void setLogListener(ILogListener listener); } \ No newline at end of file diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/model/LogData.java b/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/model/LogData.java new file mode 100644 index 00000000..6b00b13f --- /dev/null +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/model/LogData.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ +package org.hapjs.analyzer.model; + +import android.os.Parcel; +import android.os.Parcelable; + +public class LogData implements Parcelable { + public @LogPackage.LogLevel + int mLevel; + public @LogPackage.LogType + int mType; + public String mContent; + + public LogData(@LogPackage.LogLevel int level, @LogPackage.LogType int type, String content) { + mLevel = level; + mType = type; + mContent = content; + } + + protected LogData(Parcel in) { + mLevel = in.readInt(); + mType = in.readInt(); + mContent = in.readString(); + } + + public static final Creator CREATOR = new Creator() { + @Override + public LogData createFromParcel(Parcel in) { + return new LogData(in); + } + + @Override + public LogData[] newArray(int size) { + return new LogData[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mLevel); + dest.writeInt(mType); + dest.writeString(mContent); + } +} diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/model/LogPackage.java b/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/model/LogPackage.java index f8003bfc..e20e97e1 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/model/LogPackage.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/model/LogPackage.java @@ -28,18 +28,6 @@ public LogPackage(int position, List datas) { this.datas = datas; } - public static class LogData { - public @LogLevel int mLevel; - public @LogType int mType; - public String mContent; - - public LogData(@LogLevel int level, @LogType int type, String content) { - mLevel = level; - mType = type; - mContent = content; - } - } - @IntDef({LOG_LEVEL_DEFAULT, Log.VERBOSE, Log.DEBUG, Log.INFO, Log.WARN, Log.ERROR}) public @interface LogLevel { } diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/monitors/AbsLogDumper.java b/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/monitors/AbsLogDumper.java new file mode 100644 index 00000000..8fa1e790 --- /dev/null +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/monitors/AbsLogDumper.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ +package org.hapjs.analyzer.monitors; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import org.hapjs.analyzer.model.LogData; +import org.hapjs.analyzer.model.LogPackage; +import org.hapjs.common.executors.Executors; +import org.hapjs.common.utils.FileUtils; + +public abstract class AbsLogDumper implements Runnable { + protected static final String STR_DUMP_FAIL = "--- LOGCAT_CONSOLE dump log fail ! ---"; + private static final int DUMP_LOG_INTERVAL = 100; + private static final int DUMP_LOG_BATCH_CNT = 100; + private static final int MSG_DUMP_LOG = 0; + + private java.lang.Process mLogcatProcess; + private boolean mIsStop; + private List mPendingLogs = new ArrayList<>(); + private final Handler mMainHandler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + if (msg.what == MSG_DUMP_LOG) { + List pendingLogs = new ArrayList<>(); + synchronized (AbsLogDumper.this) { + pendingLogs.addAll(mPendingLogs); + mPendingLogs.clear(); + } + doDumpLog(pendingLogs); + } + } + }; + + void clearLogcat() { + Executors.io().execute(new Runnable() { + @Override + public void run() { + java.lang.Process process = null; + try { + process = Runtime.getRuntime().exec("logcat -b main -c"); + Thread.sleep(1000); + } catch (Exception e) { + // ignore + } finally { + if (process != null) { + process.destroy(); + } + } + } + }); + } + + private BufferedReader getLogReader() throws IOException { + String command = "logcat -b main -v time --pid " + android.os.Process.myPid(); + if (mLogcatProcess != null) { + mLogcatProcess.destroy(); + } + mLogcatProcess = Runtime.getRuntime().exec(command); + return new BufferedReader(new InputStreamReader(mLogcatProcess.getInputStream(), StandardCharsets.UTF_8)); + } + + @Override + public void run() { + BufferedReader reader = null; + try { + String line; + reader = getLogReader(); + while (!mIsStop) { + line = reader.readLine(); + if (line == null) { + dumpFailLog(); + break; + } else { + dumpLog(line); + } + } + } catch (Exception e) { + // ignore + } finally { + FileUtils.closeQuietly(reader); + } + } + + private void dumpLog(String log) { + int logLevel = getLogLevel(log); + boolean isJsLog = log.contains(LogcatMonitor.JS_TAG); + LogData logData = new LogData(logLevel, isJsLog ? LogPackage.LOG_TYPE_JS : LogPackage.LOG_TYPE_NATIVE, log); + dumpLog(logData); + } + + public void dumpFailLog() { + LogData logData = new LogData(LogPackage.LOG_LEVEL_DEFAULT, LogPackage.LOG_TYPE_DEFAULT, STR_DUMP_FAIL); + dumpLog(logData); + } + + private synchronized void dumpLog(LogData log) { + List logs = new ArrayList<>(); + logs.add(log); + dumpLog(logs); + } + + protected synchronized void dumpLog(List logs) { + boolean noPending = mPendingLogs.isEmpty(); + mPendingLogs = mergeLogs(mPendingLogs, logs); + if (noPending) { + if (!mMainHandler.hasMessages(MSG_DUMP_LOG)) { + mMainHandler.sendEmptyMessageDelayed(MSG_DUMP_LOG, DUMP_LOG_INTERVAL); + } + } else if (mPendingLogs.size() > DUMP_LOG_BATCH_CNT) { + if (!mMainHandler.hasMessages(MSG_DUMP_LOG)) { + mMainHandler.sendEmptyMessage(MSG_DUMP_LOG); + } + } + } + + private static List mergeLogs(List logs1, List logs2) { + if (logs1 == null || logs1.isEmpty()) { + return logs2; + } + if (logs2 == null || logs2.isEmpty()) { + return logs1; + } + if (compareTimestamp(logs1.get(logs1.size() - 1), logs2.get(0)) <= 0) { + logs1.addAll(logs2); + return logs1; + } + if (compareTimestamp(logs2.get(logs2.size() - 1), logs1.get(0)) <= 0) { + logs2.addAll(logs1); + return logs2; + } + + List mergedLogs = new ArrayList<>(); + for (int i = 0, j = 0; i < logs1.size() || j < logs2.size(); ) { + if (i >= logs1.size()) { + mergedLogs.add(logs2.get(j++)); + } else if (j >= logs2.size()) { + mergedLogs.add(logs1.get(i++)); + } else if (compareTimestamp(logs1.get(i), logs2.get(j)) <= 0) { + mergedLogs.add(logs1.get(i++)); + } else { + mergedLogs.add(logs2.get(j++)); + } + } + return mergedLogs; + } + + private static int compareTimestamp(LogData logData1, LogData logData2) { + return getTimestamp(logData1).compareTo(getTimestamp(logData2)); + } + + private static String getTimestamp(LogData logData) { + int length = logData.mContent.length(); + return logData.mContent.substring(0, Math.min(length, 18)); + } + + protected abstract void doDumpLog(List log); + + public void close() { + mIsStop = true; + clearLogcat(); + closeLogcatProcess(); + } + + private void closeLogcatProcess() { + if (mLogcatProcess != null) { + try { + mLogcatProcess.destroy(); + } catch (Exception e) { + // ignore + } + } + mLogcatProcess = null; + } + + private @LogPackage.LogLevel int getLogLevel(String log) { + if (log.length() < 20) { + return Log.VERBOSE; + } + char level = log.charAt(19); + switch (level) { + case 'V': + return Log.VERBOSE; + case 'D': + return Log.DEBUG; + case 'I': + return Log.INFO; + case 'W': + return Log.WARN; + case 'E': + return Log.ERROR; + } + return Log.VERBOSE; + } +} diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/monitors/LogcatMonitor.java b/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/monitors/LogcatMonitor.java index 71f861b2..98735fbe 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/monitors/LogcatMonitor.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/monitors/LogcatMonitor.java @@ -4,34 +4,44 @@ */ package org.hapjs.analyzer.monitors; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; import android.os.Handler; +import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.RemoteException; import android.text.TextUtils; import android.util.Log; +import org.hapjs.analyzer.Analyzer; +import org.hapjs.analyzer.model.LogData; import org.hapjs.analyzer.model.LogPackage; import org.hapjs.analyzer.monitors.abs.AbsMonitor; import org.hapjs.analyzer.tools.AnalyzerThreadManager; import org.hapjs.common.executors.Executors; -import org.hapjs.common.utils.FileUtils; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import org.hapjs.common.utils.ProcessUtils; + +import org.hapjs.render.jsruntime.SandboxProvider; +import org.hapjs.runtime.ProviderManager; +import org.hapjs.runtime.sandbox.ILogListener; +import org.hapjs.runtime.sandbox.ISandbox; public class LogcatMonitor extends AbsMonitor { + private static final String TAG = "LogcatMonitor"; public static final String NAME = "logcat"; public static final int LOG_JS = 1; public static final int LOG_NATIVE = 1 << 1; public static final int TYPE_LOG_STYLE_WEB = 0; public static final int TYPE_LOG_STYLE_ANDROID = 1; - private static final String JS_TAG = "LOGCAT_CONSOLE"; + public static final String JS_TAG = "LOGCAT_CONSOLE"; private static final int CACHE_SIZE = 500; private static final int MSG_SEND_ONE_LOG = 1; private int mLogLevel = Log.VERBOSE; @@ -39,7 +49,7 @@ public class LogcatMonitor extends AbsMonitor { private int mLogStyle = TYPE_LOG_STYLE_WEB; private String mFilter = ""; private Dumper mDumper; - private LinkedList mCaches = new LinkedList<>(); + private LinkedList mCaches = new LinkedList<>(); private final Handler mMainHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { @@ -104,7 +114,7 @@ private void onRuleChanged() { if (mCaches.isEmpty()) { return; } - List logDatas; + List logDatas; synchronized (LogcatMonitor.this) { logDatas = filterData(new ArrayList<>(mCaches)); } @@ -112,20 +122,14 @@ private void onRuleChanged() { }); } - private List filterData(LogPackage.LogData logData) { - ArrayList singleList = new ArrayList<>(1); - singleList.add(logData); - return filterData(singleList); - } - - private List filterData(List originData) { + private List filterData(List originData) { if (originData == null || originData.isEmpty()) { return new ArrayList<>(); } String filter = mFilter == null ? "" : mFilter.toLowerCase(); - Iterator iterator = originData.iterator(); + Iterator iterator = originData.iterator(); while (iterator.hasNext()) { - LogPackage.LogData logData = iterator.next(); + LogData logData = iterator.next(); if (logData.mType == LogPackage.LOG_TYPE_DEFAULT) { continue; } @@ -156,11 +160,11 @@ private List filterData(List originData) return originData; } - private synchronized void cacheLog(LogPackage.LogData logData) { - if (mCaches.size() >= CACHE_SIZE) { + private synchronized void cacheLog(List logDatas) { + mCaches.addAll(logDatas); + while (mCaches.size() >= CACHE_SIZE) { mCaches.removeFirst(); } - mCaches.add(logData); } public void clearLog() { @@ -168,7 +172,7 @@ public void clearLog() { mCaches.clear(); } - private void sendLogDatas(int position, List logDatas) { + private void sendLogDatas(int position, List logDatas) { if (logDatas != null) { Pipeline pipeline = getPipeline(); if (pipeline != null) { @@ -177,77 +181,22 @@ private void sendLogDatas(int position, List logDatas) { } } - private class Dumper implements Runnable { - private static final String STR_DUMP_FAIL = "--- LOGCAT_CONSOLE dump log fail ! ---"; - private Process mLogcatProcess; - private boolean mIsStop; + private class Dumper extends AbsLogDumper { + private ISandbox mSandbox; + private ServiceConnection mConnection; - void clearLogcat() { - Executors.io().execute(new Runnable() { - @Override - public void run() { - Process process = null; - try { - process = Runtime.getRuntime().exec("logcat -b main -c"); - Thread.sleep(1000); - } catch (Exception e) { - // ignore - } finally { - if (process != null) { - process.destroy(); - } - } - } - }); - } - - private BufferedReader getLogReader() throws IOException { - String command = "logcat -b main -v time --pid " + android.os.Process.myPid(); - if (mLogcatProcess != null) { - mLogcatProcess.destroy(); + public Dumper() { + SandboxProvider sandboxProvider = ProviderManager.getDefault().getProvider(SandboxProvider.NAME); + if (sandboxProvider != null && sandboxProvider.isSandboxEnabled()) { + connectSandboxService(); } - mLogcatProcess = Runtime.getRuntime().exec(command); - return new BufferedReader(new InputStreamReader(mLogcatProcess.getInputStream(), StandardCharsets.UTF_8)); } @Override - public void run() { - BufferedReader reader = null; - try { - String line; - reader = getLogReader(); - while (!mIsStop) { - line = reader.readLine(); - if (line == null) { - dumpFailLog(); - break; - } else { - dumpLog(line); - } - } - } catch (Exception e) { - // ignore - } finally { - FileUtils.closeQuietly(reader); - } - } - - private void dumpLog(String log) { - int logLevel = getLogLevel(log); - boolean isJsLog = log.contains(JS_TAG); - LogPackage.LogData logData = new LogPackage.LogData(logLevel, isJsLog ? LogPackage.LOG_TYPE_JS : LogPackage.LOG_TYPE_NATIVE, log); - dumpLog(logData); - } - - private void dumpFailLog() { - LogPackage.LogData logData = new LogPackage.LogData(LogPackage.LOG_LEVEL_DEFAULT, LogPackage.LOG_TYPE_DEFAULT, STR_DUMP_FAIL); - dumpLog(logData); - } - - private void dumpLog(LogPackage.LogData logData) { - cacheLog(logData); + protected void doDumpLog(List logs) { + cacheLog(logs); AnalyzerThreadManager.getInstance().getAnalyzerHandler().post(() -> { - List filterData = filterData(logData); + List filterData = filterData(logs); if (!filterData.isEmpty()) { Message message = mMainHandler.obtainMessage(MSG_SEND_ONE_LOG); message.obj = new LogPackage(filterData); @@ -256,41 +205,58 @@ private void dumpLog(LogPackage.LogData logData) { }); } - private @LogPackage.LogLevel int getLogLevel(String log) { - if (log.length() < 20) { - return Log.VERBOSE; + @Override + public void close() { + super.close(); + + if (mConnection != null) { + Context context = Analyzer.get().getApplicationContext(); + context.unbindService(mConnection); + mConnection = null; } - char level = log.charAt(19); - switch (level) { - case 'V': - return Log.VERBOSE; - case 'D': - return Log.DEBUG; - case 'I': - return Log.INFO; - case 'W': - return Log.WARN; - case 'E': - return Log.ERROR; + if (mSandbox != null) { + try { + mSandbox.setLogListener(null); + mSandbox = null; + } catch (RemoteException e) { + Log.e(TAG, "failed to setLogListener null", e); + } } - return Log.VERBOSE; } - void close() { - mIsStop = true; - clearLogcat(); - closeLogcatProcess(); - } + private void connectSandboxService() { + String currentProcessName = ProcessUtils.getCurrentProcessName(); + String sandboxName = "org.hapjs.runtime.sandbox.SandboxService$Sandbox" + + currentProcessName.charAt(currentProcessName.length() - 1); - void closeLogcatProcess(){ - if (mLogcatProcess != null) { - try { - mLogcatProcess.destroy(); - } catch (Exception e) { - // ignore + Context context = Analyzer.get().getApplicationContext(); + Intent intent = new Intent(); + intent.setClassName(context.getPackageName(), sandboxName); + + mConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + mSandbox = ISandbox.Stub.asInterface(service); + try { + mSandbox.setLogListener(new ILogListener.Stub() { + public void onLog(List logs) { + dumpLog(logs); + } + }); + } catch (RemoteException e) { + Log.e(TAG, "failed to setLogListener", e); + } } + + @Override + public void onServiceDisconnected(ComponentName name) { + } + }; + + boolean bindResult = context.bindService(intent, mConnection, 0); + if (!bindResult) { + Log.e(TAG, "bind sandboxService failed. sandboxName=" + sandboxName); } - mLogcatProcess = null; } } } diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/panels/ConsolePanel.java b/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/panels/ConsolePanel.java index debb8972..83d15fc4 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/panels/ConsolePanel.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/panels/ConsolePanel.java @@ -30,6 +30,7 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import org.hapjs.analyzer.model.LogData; import org.hapjs.analyzer.model.LogPackage; import org.hapjs.analyzer.monitors.LogcatMonitor; import org.hapjs.analyzer.tools.AnalyzerThreadManager; @@ -321,7 +322,7 @@ protected void onShowAnimationFinished() { super.onShowAnimationFinished(); mVisibleComplete = true; if (!mTmpLogCache.isEmpty()) { - List logDatas = new LinkedList<>(); + List logDatas = new LinkedList<>(); for (LogPackage p : mTmpLogCache) { logDatas.addAll(p.datas); } @@ -381,7 +382,7 @@ private void addLog(LogPackage logPackage) { private static class LogListAdapter extends RecyclerView.Adapter { private Context mContext; - private LinkedList mLogDatas; + private LinkedList mLogDatas; private RecyclerView mRecyclerView; private boolean mLockLog = false; @@ -400,7 +401,7 @@ public LogItemHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) @Override public void onBindViewHolder(@NonNull LogItemHolder holder, int position) { - LogPackage.LogData logData = mLogDatas.get(position); + LogData logData = mLogDatas.get(position); holder.setLogData(logData); } @@ -409,19 +410,19 @@ public int getItemCount() { return mLogDatas.size(); } - void addLogDatas(int position, List logDatas) { + void addLogDatas(int position, List logDatas) { if (logDatas == null || logDatas.isEmpty()) { return; } if (logDatas.size() > MAX_DISPLAY__COUNT) { - ListIterator iterator = logDatas.listIterator(0); + ListIterator iterator = logDatas.listIterator(0); for (int i = 0, n = logDatas.size() - MAX_DISPLAY__COUNT; i < n; i++) { iterator.next(); iterator.remove(); } } if (mLogDatas.size() >= MAX_DISPLAY__COUNT - logDatas.size()) { - ListIterator iterator = mLogDatas.listIterator(0); + ListIterator iterator = mLogDatas.listIterator(0); for (int i = 0, n = MAX_DISPLAY__COUNT - logDatas.size(); i < n; i++) { iterator.next(); iterator.remove(); @@ -476,7 +477,7 @@ void setTextColor(int color) { mTv.setTextColor(color); } - void setLogData(LogPackage.LogData logData) { + void setLogData(LogData logData) { setText(logData.mContent); itemView.setSelected(logData.mType == LogPackage.LOG_TYPE_JS); switch (logData.mLevel) { diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/SandboxService.java b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/SandboxService.java index 6d24e4a5..d3418131 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/SandboxService.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/SandboxService.java @@ -14,7 +14,11 @@ import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import java.io.IOException; +import java.util.List; import java.util.Map; +import org.hapjs.analyzer.model.LogData; +import org.hapjs.analyzer.monitors.AbsLogDumper; +import org.hapjs.common.executors.Executors; import org.hapjs.logging.LogProvider; import org.hapjs.render.jsruntime.SandboxJsThread; import org.hapjs.runtime.ProviderManager; @@ -22,6 +26,9 @@ public class SandboxService extends Service { private static final String TAG = "SandboxService"; + private ILogListener mLogListener; + private Dumper mDumper; + @Nullable @Override public IBinder onBind(Intent intent) { @@ -64,6 +71,22 @@ public void setLogProvider(ILogProvider logProviderImpl) { Log.e(TAG, "failed to linkToDeath", e); } } + + @Override + public void setLogListener(ILogListener listener) { + mLogListener = listener; + if (listener != null) { + if (mDumper == null) { + mDumper = new Dumper(); + Executors.io().execute(mDumper); + } + } else { + if (mDumper != null) { + mDumper.close(); + mDumper = null; + } + } + } }; } @@ -74,6 +97,13 @@ public void onDestroy() { if (logProvider instanceof SandboxLogProviderImpl) { ((SandboxLogProviderImpl) logProvider).setLogProvider(null); } + + mLogListener = null; + Dumper dumper = mDumper; + mDumper = null; + if (dumper != null) { + dumper.close(); + } } public static class Sandbox0 extends SandboxService {} @@ -81,4 +111,18 @@ public static class Sandbox1 extends SandboxService {} public static class Sandbox2 extends SandboxService {} public static class Sandbox3 extends SandboxService {} public static class Sandbox4 extends SandboxService {} + + private class Dumper extends AbsLogDumper { + @Override + protected void doDumpLog(List logs) { + ILogListener logListener = mLogListener; + if (logListener != null) { + try { + logListener.onLog(logs); + } catch (RemoteException e) { + Log.e(TAG, "failed to onLog", e); + } + } + } + } }