[Feat]新增性能监控工具
This commit is contained in:
@@ -243,6 +243,9 @@ dependencies {
|
||||
debugImplementation rootProject.ext.dependencies.debugleakcanary
|
||||
releaseImplementation rootProject.ext.dependencies.releaseleakcanary
|
||||
implementation rootProject.ext.dependencies.android_start_up
|
||||
|
||||
// // 暂不使用Shizuku-API
|
||||
// implementation rootProject.ext.dependencies.shizuku_provider
|
||||
if (Boolean.valueOf(USE_MAVEN_PACKAGE)) {
|
||||
implementation rootProject.ext.dependencies.ttspad
|
||||
implementation rootProject.ext.dependencies.mogo_core_function_hmi
|
||||
|
||||
@@ -31,6 +31,15 @@
|
||||
android:value="android.startup.provider.config" />
|
||||
</provider>
|
||||
|
||||
<!-- <!– 暂不使用Shizuku-API –>-->
|
||||
<!-- <provider-->
|
||||
<!-- android:name="rikka.shizuku.ShizukuProvider"-->
|
||||
<!-- android:authorities="${applicationId}.shizuku"-->
|
||||
<!-- android:enabled="true"-->
|
||||
<!-- android:exported="true"-->
|
||||
<!-- android:multiprocess="false"-->
|
||||
<!-- android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />-->
|
||||
|
||||
<!-- 配置APP ID -->
|
||||
<meta-data
|
||||
android:name="BUGLY_APPID"
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.mogo.launcher;
|
||||
|
||||
import com.mogo.eagle.core.function.main.MainMoGoApplication;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import com.mogo.eagle.core.utilcode.mogo.logger.LogLevel;
|
||||
@@ -11,11 +10,7 @@ import com.mogo.launcher.crash.CrashSystem;
|
||||
import com.mogo.thread.ext.core.ThreadManager;
|
||||
import com.mogo.thread.ext.core.config.ThreadConfig;
|
||||
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.RejectedExecutionHandler;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author congtaowang
|
||||
|
||||
@@ -240,7 +240,11 @@ ext {
|
||||
//========================= DataStore ======================
|
||||
androidx_datastore_preferences : "androidx.datastore:datastore-preferences:1.0.0",
|
||||
androidx_datastore : "androidx.datastore:datastore:1.0.0",
|
||||
android_start_up : "com.mogo:android-startup:1.0.1"
|
||||
android_start_up : "com.mogo:android-startup:1.0.1",
|
||||
|
||||
//========================= Shizuku ======================
|
||||
shizuku_api : "dev.rikka.shizuku:api:12.1.0",
|
||||
shizuku_provider : "dev.rikka.shizuku:provider:12.1.0"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,6 @@ dependencies {
|
||||
implementation rootProject.ext.dependencies.androidxconstraintlayout
|
||||
implementation rootProject.ext.dependencies.androidxrecyclerview
|
||||
implementation rootProject.ext.dependencies.flexbox
|
||||
|
||||
if (Boolean.valueOf(USE_MAVEN_PACKAGE)) {
|
||||
implementation rootProject.ext.dependencies.mogoserviceapi
|
||||
implementation rootProject.ext.dependencies.modulecommon
|
||||
|
||||
@@ -12,6 +12,8 @@ import com.mogo.eagle.core.data.deva.scene.SceneTAG
|
||||
import com.mogo.eagle.core.function.api.devatools.IDevaToolsProvider
|
||||
import com.zhjt.mogo_core_function_devatools.badcase.BadCaseManager
|
||||
import com.zhjt.mogo_core_function_devatools.logcatch.MogoLogCatchManager
|
||||
import com.zhjt.mogo_core_function_devatools.monitor.MonitorManager
|
||||
import com.zhjt.mogo_core_function_devatools.monitor.remote.UserServiceManager
|
||||
import com.zhjt.mogo_core_function_devatools.scene.SceneManager.Companion.sceneManager
|
||||
import com.zhjt.mogo_core_function_devatools.status.*
|
||||
import com.zhjt.mogo_core_function_devatools.trace.TraceManager.Companion.traceManager
|
||||
@@ -110,4 +112,12 @@ class DevaToolsProvider : IDevaToolsProvider {
|
||||
override fun hideStatusBar() {
|
||||
StatusManager.hide()
|
||||
}
|
||||
|
||||
override fun startMonitor() {
|
||||
mContext?.let { MonitorManager.getInstance(it)?.startMonitor() }
|
||||
}
|
||||
|
||||
override fun stopMonitor() {
|
||||
mContext?.let { MonitorManager.getInstance(it)?.stopMonitor() }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package com.zhjt.mogo_core_function_devatools.monitor
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.os.Process
|
||||
import android.util.Log
|
||||
import com.zhjt.mogo_core_function_devatools.monitor.db.CpuInfo
|
||||
import com.zhjt.mogo_core_function_devatools.monitor.db.MonitorDb
|
||||
import com.zhjt.mogo_core_function_devatools.monitor.remote.UserServiceManager
|
||||
import com.zhjt.mogo_core_function_devatools.monitor.utils.CpuUtils
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
class CpuMonitor private constructor(var context: Context) {
|
||||
|
||||
var isRunning = false
|
||||
|
||||
private val pid by lazy {
|
||||
Process.myPid()
|
||||
}
|
||||
|
||||
private val cpuInfoList by lazy {
|
||||
ArrayList<CpuInfo>()
|
||||
}
|
||||
|
||||
private val dateFormat by lazy {
|
||||
SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "CpuMonitor"
|
||||
const val INTERVAL_PERF = 1000L
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
@Volatile
|
||||
private var sInstance: CpuMonitor? = null
|
||||
fun getInstance(context: Context): CpuMonitor? {
|
||||
if (sInstance == null) {
|
||||
synchronized(CpuMonitor::class.java) {
|
||||
if (sInstance == null) {
|
||||
sInstance = CpuMonitor(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
return sInstance
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 请先运行Shizuku的服务
|
||||
*/
|
||||
@Synchronized
|
||||
fun startRun() {
|
||||
if (!isRunning) {
|
||||
Thread(CpuRunnable()).start()
|
||||
isRunning = true
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun isStarted() = isRunning
|
||||
|
||||
@Synchronized
|
||||
fun stop() {
|
||||
isRunning = false
|
||||
}
|
||||
|
||||
fun saveCpuInfos() {
|
||||
synchronized(this) {
|
||||
MonitorDb.getDb(context).monitorDao().saveAllCpuInfos(*cpuInfoList.toTypedArray())
|
||||
cpuInfoList.clear()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCpuValue(): Double {
|
||||
var value = 0.0
|
||||
try {
|
||||
// 应用获取当前进程CPU使用时间,Binder服务端获取系统CPU时间,中间用耗时,不如使用top命令
|
||||
// value = UserServiceManager.getCpuUsage(Process.myPid())
|
||||
value = CpuUtils.getCpuUsageForO()
|
||||
return value
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, e.message ?: "未知异常!")
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
inner class CpuRunnable : Runnable {
|
||||
override fun run() {
|
||||
while (isRunning) {
|
||||
val cpu = getCpuValue()
|
||||
if (cpu > 0) {
|
||||
synchronized(this@CpuMonitor) {
|
||||
cpuInfoList.add(
|
||||
CpuInfo(
|
||||
saveTime = dateFormat.format(System.currentTimeMillis()),
|
||||
cpuPercent = cpu
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
try {
|
||||
Thread.sleep(INTERVAL_PERF)
|
||||
} catch (e: InterruptedException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package com.zhjt.mogo_core_function_devatools.monitor
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.os.Process.myPid
|
||||
import android.util.Log
|
||||
import com.zhjt.mogo_core_function_devatools.monitor.db.MemInfo
|
||||
import com.zhjt.mogo_core_function_devatools.monitor.db.MonitorDb
|
||||
import com.zhjt.mogo_core_function_devatools.monitor.utils.HookUtils
|
||||
import com.zhjt.mogo_core_function_devatools.monitor.utils.MemUtils
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
class MemMonitor private constructor(var context: Context) {
|
||||
|
||||
var isRunning = false
|
||||
|
||||
private val memInfoList by lazy {
|
||||
ArrayList<MemInfo>()
|
||||
}
|
||||
|
||||
private val dateFormat by lazy {
|
||||
SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "MemMonitor"
|
||||
const val INTERVAL_PERF = 1000L
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
@Volatile
|
||||
private var sInstance: MemMonitor? = null
|
||||
fun getInstance(context: Context): MemMonitor? {
|
||||
if (sInstance == null) {
|
||||
synchronized(MemMonitor::class.java) {
|
||||
if (sInstance == null) {
|
||||
sInstance = MemMonitor(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
return sInstance
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun startRun() {
|
||||
if (!isRunning) {
|
||||
Thread(MemoryRunnable()).start()
|
||||
isRunning = true
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun isStarted() = isRunning
|
||||
|
||||
@Synchronized
|
||||
fun stop() {
|
||||
isRunning = false
|
||||
}
|
||||
|
||||
fun saveMemInfos() {
|
||||
synchronized(this) {
|
||||
MonitorDb.getDb(context).monitorDao().saveAllMemInfos(*memInfoList.toTypedArray())
|
||||
memInfoList.clear()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 单位MB
|
||||
*/
|
||||
private fun getMemoryValue(): Double {
|
||||
var value = 0.0
|
||||
try {
|
||||
value = MemUtils.getPssMemValue(context, intArrayOf(myPid()))
|
||||
return value
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, e.message ?: "未知异常!")
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* 单位MB
|
||||
*/
|
||||
private fun getMemoryArray(): DoubleArray {
|
||||
var value = doubleArrayOf()
|
||||
try {
|
||||
value = MemUtils.getPssArray(context, intArrayOf(myPid()))
|
||||
return value
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, e.message ?: "未知异常!")
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
inner class MemoryRunnable : Runnable {
|
||||
override fun run() {
|
||||
while (isRunning) {
|
||||
// 0: 总PSS,1: dalvik总Pss, 2: native总Pss, 3: other总Pss
|
||||
val pssArray = getMemoryArray()
|
||||
if (pssArray[0] > 0) {
|
||||
synchronized(this@MemMonitor) {
|
||||
memInfoList.add(
|
||||
MemInfo(
|
||||
saveTime = dateFormat.format(System.currentTimeMillis()),
|
||||
totalPss = pssArray[0],
|
||||
dalvikPss = pssArray[1],
|
||||
nativePss = pssArray[2],
|
||||
otherPss = pssArray[3]
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
try {
|
||||
Thread.sleep(INTERVAL_PERF)
|
||||
} catch (e: InterruptedException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.zhjt.mogo_core_function_devatools.monitor
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.os.Looper
|
||||
import com.mogo.eagle.core.utilcode.util.ToastUtils
|
||||
import java.util.*
|
||||
|
||||
class MonitorManager private constructor(var context: Context) {
|
||||
|
||||
private var timer: Timer? = null
|
||||
|
||||
private var isStarted = false
|
||||
|
||||
companion object {
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
@Volatile
|
||||
private var sInstance: MonitorManager? = null
|
||||
fun getInstance(context: Context): MonitorManager? {
|
||||
if (sInstance == null) {
|
||||
synchronized(MonitorManager::class.java) {
|
||||
if (sInstance == null) {
|
||||
sInstance = MonitorManager(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
return sInstance
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 主线程中执行
|
||||
*/
|
||||
fun startMonitor() {
|
||||
if (Thread.currentThread() == Looper.getMainLooper().thread) {
|
||||
if (!isStarted) {
|
||||
CpuMonitor.getInstance(context)?.startRun()
|
||||
MemMonitor.getInstance(context)?.startRun()
|
||||
if (timer == null) {
|
||||
timer = Timer()
|
||||
}
|
||||
timer!!.schedule(object : TimerTask() {
|
||||
override fun run() {
|
||||
CpuMonitor.getInstance(context)?.saveCpuInfos()
|
||||
MemMonitor.getInstance(context)?.saveMemInfos()
|
||||
}
|
||||
}, 1000, 60000)
|
||||
isStarted = true
|
||||
ToastUtils.showShort("性能监控启动成功!")
|
||||
} else {
|
||||
ToastUtils.showShort("性能监控已启动,请勿重复启动!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun stopMonitor() {
|
||||
CpuMonitor.getInstance(context)?.stop()
|
||||
MemMonitor.getInstance(context)?.stop()
|
||||
timer?.cancel()
|
||||
timer = null
|
||||
isStarted = false
|
||||
ToastUtils.showShort("性能监控停止成功!")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.zhjt.mogo_core_function_devatools.monitor.db
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "t_cpu")
|
||||
data class CpuInfo(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
@ColumnInfo(name = "id")
|
||||
val uuid: Long = 0,
|
||||
|
||||
@ColumnInfo(name = "save_time")
|
||||
val saveTime: String?,
|
||||
|
||||
@ColumnInfo(name = "cpu_percent")
|
||||
val cpuPercent: Double
|
||||
)
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.zhjt.mogo_core_function_devatools.monitor.db
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "t_memory")
|
||||
data class MemInfo(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
@ColumnInfo(name = "id")
|
||||
val uuid: Long = 0,
|
||||
|
||||
@ColumnInfo(name = "save_time")
|
||||
val saveTime: String,
|
||||
|
||||
@ColumnInfo(name = "total_pss")
|
||||
val totalPss: Double,
|
||||
|
||||
@ColumnInfo(name = "dalvik_pss")
|
||||
val dalvikPss: Double,
|
||||
|
||||
@ColumnInfo(name = "native_pss")
|
||||
val nativePss: Double,
|
||||
|
||||
@ColumnInfo(name = "other_pss")
|
||||
val otherPss: Double
|
||||
)
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.zhjt.mogo_core_function_devatools.monitor.db
|
||||
|
||||
import androidx.room.*
|
||||
|
||||
@Dao
|
||||
interface MonitorDao {
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun saveCpu(info: CpuInfo)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun saveAllCpuInfos(vararg cpuInfo: CpuInfo)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun saveMemory(info: MemInfo)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun saveAllMemInfos(vararg memInfo: MemInfo)
|
||||
|
||||
@Query("SELECT * FROM t_cpu WHERE id =:id")
|
||||
fun getAllCPUById(id: Long): List<CpuInfo>
|
||||
|
||||
@Query("SELECT * FROM t_memory WHERE id =:id")
|
||||
fun getAllMemById(id: Long): List<MemInfo>
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.zhjt.mogo_core_function_devatools.monitor.db
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Database
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
|
||||
@Database(entities = [CpuInfo::class, MemInfo::class], version = 1, exportSchema = false)
|
||||
abstract class MonitorDb: RoomDatabase() {
|
||||
abstract fun monitorDao(): MonitorDao
|
||||
|
||||
companion object {
|
||||
const val INTERNAL_DB_NAME = "mogo_monitor.db"
|
||||
|
||||
private var db: MonitorDb? = null
|
||||
|
||||
@JvmStatic
|
||||
fun getDb(context: Context): MonitorDb {
|
||||
if (db == null) {
|
||||
db = Room.databaseBuilder(context.applicationContext, MonitorDb::class.java, INTERNAL_DB_NAME)
|
||||
.fallbackToDestructiveMigration()
|
||||
.build()
|
||||
}
|
||||
return db!!
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.zhjt.mogo_core_function_devatools.monitor.remote;
|
||||
|
||||
import android.os.IBinder;
|
||||
import android.os.IInterface;
|
||||
import android.os.RemoteException;
|
||||
|
||||
public interface IUserInterface extends IInterface {
|
||||
|
||||
String DESCRIPTOR = "rikka.shizuku.demo.IUserInterface";
|
||||
|
||||
int TRANSACTION_exec = IBinder.FIRST_CALL_TRANSACTION;
|
||||
int TRANSACTION_getCpuUsage = IBinder.FIRST_CALL_TRANSACTION + 1;
|
||||
|
||||
String exec(String cmd) throws RemoteException;
|
||||
|
||||
double getCpuUsage(int pid) throws RemoteException;
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package com.zhjt.mogo_core_function_devatools.monitor.remote;
|
||||
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.os.IInterface;
|
||||
import android.os.Parcel;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.zhjt.mogo_core_function_devatools.monitor.utils.CpuUtils;
|
||||
|
||||
/**
|
||||
* Server端Binder类
|
||||
*/
|
||||
public class RemoteUserServiceImp extends Binder implements IUserInterface {
|
||||
|
||||
public RemoteUserServiceImp() {
|
||||
attachInterface(this, DESCRIPTOR);
|
||||
}
|
||||
|
||||
public static IUserInterface asInterface(IBinder obj) {
|
||||
if ((obj == null)) {
|
||||
return null;
|
||||
}
|
||||
IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
|
||||
if (iin instanceof IUserInterface) {
|
||||
return ((IUserInterface) iin);
|
||||
}
|
||||
return new Proxy(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String exec(String cmd) throws RemoteException {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getCpuUsage(int pid) throws RemoteException {
|
||||
return CpuUtils.getCpuUsagePercentWithPid(pid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理Client端请求
|
||||
*
|
||||
* @param code
|
||||
* @param data
|
||||
* @param reply
|
||||
* @param flags
|
||||
* @return
|
||||
* @throws RemoteException
|
||||
*/
|
||||
@Override
|
||||
protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
|
||||
final String descriptor = DESCRIPTOR;
|
||||
switch (code) {
|
||||
case TRANSACTION_exec:
|
||||
data.enforceInterface(descriptor);
|
||||
String _arg0;
|
||||
_arg0 = data.readString();
|
||||
String _result = this.exec(_arg0);
|
||||
reply.writeNoException();
|
||||
reply.writeString(_result);
|
||||
return true;
|
||||
default:
|
||||
return super.onTransact(code, data, reply, flags);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getInterfaceDescriptor() {
|
||||
return super.getInterfaceDescriptor();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回给Client端并被封装成BinderProxy
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public IBinder asBinder() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public static class Proxy implements IUserInterface {
|
||||
private IBinder mRemote;
|
||||
|
||||
public Proxy(IBinder remote) {
|
||||
mRemote = remote;
|
||||
}
|
||||
|
||||
public String getInterfaceDescriptor() {
|
||||
return DESCRIPTOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder asBinder() {
|
||||
// 返回Server端Binder对象
|
||||
return mRemote;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String exec(String cmd) throws RemoteException {
|
||||
// 参数传递给Server端并挂起当前线程,等待Server端执行完返回结果
|
||||
Parcel _data = Parcel.obtain();
|
||||
Parcel _reply = Parcel.obtain();
|
||||
String _result;
|
||||
try {
|
||||
_data.writeInterfaceToken(DESCRIPTOR);
|
||||
_data.writeString(cmd);
|
||||
mRemote.transact(TRANSACTION_exec, _data, _reply, 0);
|
||||
_reply.readException();
|
||||
_result = _reply.readString();
|
||||
} finally {
|
||||
_reply.recycle();
|
||||
_data.recycle();
|
||||
}
|
||||
return _result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getCpuUsage(int pid) throws RemoteException {
|
||||
Parcel _data = Parcel.obtain();
|
||||
Parcel _reply = Parcel.obtain();
|
||||
double _result;
|
||||
try {
|
||||
_data.writeInterfaceToken(DESCRIPTOR);
|
||||
_data.writeInt(pid);
|
||||
mRemote.transact(TRANSACTION_getCpuUsage, _data, _reply, 0);
|
||||
_reply.readException();
|
||||
_result = _reply.readDouble();
|
||||
} finally {
|
||||
_reply.recycle();
|
||||
_data.recycle();
|
||||
}
|
||||
return _result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package com.zhjt.mogo_core_function_devatools.monitor.remote
|
||||
|
||||
import rikka.shizuku.Shizuku
|
||||
import android.content.ServiceConnection
|
||||
import android.content.ComponentName
|
||||
import android.os.IBinder
|
||||
import rikka.shizuku.Shizuku.UserServiceArgs
|
||||
import android.content.pm.PackageManager
|
||||
import android.util.Log
|
||||
import com.mogo.eagle.core.utilcode.util.AppUtils
|
||||
import com.mogo.eagle.core.utilcode.util.ToastUtils
|
||||
import rikka.shizuku.Shizuku.OnBinderReceivedListener
|
||||
import rikka.shizuku.Shizuku.OnBinderDeadListener
|
||||
import rikka.shizuku.Shizuku.OnRequestPermissionResultListener
|
||||
import java.lang.StringBuilder
|
||||
|
||||
object UserServiceManager {
|
||||
|
||||
private const val TAG = "UserServiceManager"
|
||||
private const val REQUEST_CODE_BIND = 1
|
||||
private const val REQUEST_CODE_UNBIND = 2
|
||||
|
||||
private var userServiceProxy: IUserInterface? = null
|
||||
private var isFirst = true
|
||||
|
||||
fun init() {
|
||||
if (isFirst) {
|
||||
addListener()
|
||||
isFirst = false
|
||||
}
|
||||
bindUserService()
|
||||
}
|
||||
|
||||
fun stopService() {
|
||||
removeListener()
|
||||
unbindUserService()
|
||||
}
|
||||
|
||||
fun isServiceActive(): Boolean {
|
||||
var isActive = false
|
||||
val service = userServiceProxy
|
||||
if (service != null && service is IBinder && service.isBinderAlive) {
|
||||
isActive = true
|
||||
}
|
||||
return isActive
|
||||
}
|
||||
|
||||
private fun addListener() {
|
||||
Shizuku.addBinderReceivedListenerSticky(BINDER_RECEIVED_LISTENER)
|
||||
Shizuku.addBinderDeadListener(BINDER_DEAD_LISTENER)
|
||||
Shizuku.addRequestPermissionResultListener(REQUEST_PERMISSION_RESULT_LISTENER)
|
||||
}
|
||||
|
||||
private fun removeListener() {
|
||||
Shizuku.removeBinderReceivedListener(BINDER_RECEIVED_LISTENER)
|
||||
Shizuku.removeBinderDeadListener(BINDER_DEAD_LISTENER)
|
||||
Shizuku.removeRequestPermissionResultListener(REQUEST_PERMISSION_RESULT_LISTENER)
|
||||
}
|
||||
|
||||
private fun bindUserService() {
|
||||
if (checkPermission(REQUEST_CODE_BIND)) {
|
||||
val res = StringBuilder()
|
||||
try {
|
||||
if (Shizuku.getVersion() < 10) {
|
||||
res.append("requires Shizuku API 10")
|
||||
} else {
|
||||
Shizuku.bindUserService(userServiceArgs, userServiceConnection)
|
||||
}
|
||||
} catch (tr: Throwable) {
|
||||
tr.printStackTrace()
|
||||
res.append(tr.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun unbindUserService() {
|
||||
if (checkPermission(REQUEST_CODE_UNBIND)) {
|
||||
val res = StringBuilder()
|
||||
try {
|
||||
if (Shizuku.getVersion() < 10) {
|
||||
res.append("requires Shizuku API 10")
|
||||
} else {
|
||||
Shizuku.unbindUserService(userServiceArgs, userServiceConnection, true)
|
||||
}
|
||||
} catch (tr: Throwable) {
|
||||
tr.printStackTrace()
|
||||
res.append(tr.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun exec(cmd: String): String {
|
||||
return userServiceProxy?.exec(cmd) ?: ""
|
||||
}
|
||||
|
||||
/**
|
||||
* 单位:%,保留1位小数
|
||||
*/
|
||||
fun getCpuUsage(pid: Int): Double {
|
||||
return userServiceProxy?.getCpuUsage(pid) ?: -1.0
|
||||
}
|
||||
|
||||
private val userServiceConnection: ServiceConnection = object : ServiceConnection {
|
||||
override fun onServiceConnected(componentName: ComponentName, binder: IBinder?) {
|
||||
if (binder != null && binder.pingBinder()) {
|
||||
val service = RemoteUserServiceImp.asInterface(binder)
|
||||
userServiceProxy = service
|
||||
ToastUtils.showShort("监控权限获取成功!")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(componentName: ComponentName) {}
|
||||
}
|
||||
|
||||
private val userServiceArgs by lazy {
|
||||
val appInfo = AppUtils.getAppInfo()
|
||||
UserServiceArgs(
|
||||
ComponentName(
|
||||
appInfo?.packageName ?: "com.mogo.launcher.f",
|
||||
RemoteUserServiceImp::class.java.name
|
||||
)
|
||||
)
|
||||
.daemon(false)
|
||||
.processNameSuffix("service")
|
||||
.debuggable(true)
|
||||
.version(appInfo?.versionCode ?: 1)
|
||||
}
|
||||
|
||||
private fun checkPermission(code: Int): Boolean {
|
||||
if (Shizuku.isPreV11()) {
|
||||
Log.w(TAG, "Version is preV11!")
|
||||
return false
|
||||
}
|
||||
try {
|
||||
return if (Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED) {
|
||||
true
|
||||
} else if (Shizuku.shouldShowRequestPermissionRationale()) {
|
||||
Log.e(TAG, "User denied permission (shouldShowRequestPermissionRationale=true)")
|
||||
false
|
||||
} else {
|
||||
Shizuku.requestPermission(code)
|
||||
false
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
Log.e(TAG, Log.getStackTraceString(e))
|
||||
ToastUtils.showLong("请先打开Shizuku并启动它!")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun onRequestPermissionsResult(requestCode: Int, grantResult: Int) {
|
||||
if (grantResult == PackageManager.PERMISSION_GRANTED) {
|
||||
when (requestCode) {
|
||||
REQUEST_CODE_BIND -> {
|
||||
bindUserService()
|
||||
}
|
||||
REQUEST_CODE_UNBIND -> {
|
||||
unbindUserService()
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "User denied permission")
|
||||
}
|
||||
}
|
||||
|
||||
private val BINDER_RECEIVED_LISTENER = OnBinderReceivedListener {
|
||||
if (Shizuku.isPreV11()) {
|
||||
Log.w(TAG, "Shizuku pre-v11 is not supported")
|
||||
} else {
|
||||
Log.d(TAG, "Binder received")
|
||||
}
|
||||
}
|
||||
private val BINDER_DEAD_LISTENER = OnBinderDeadListener { Log.w(TAG, "Binder dead") }
|
||||
private val REQUEST_PERMISSION_RESULT_LISTENER =
|
||||
OnRequestPermissionResultListener { requestCode: Int, grantResult: Int ->
|
||||
onRequestPermissionsResult(
|
||||
requestCode,
|
||||
grantResult
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
package com.zhjt.mogo_core_function_devatools.monitor.utils;
|
||||
|
||||
import android.os.Process;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.RandomAccessFile;
|
||||
|
||||
/**
|
||||
* CPU相关工具类。
|
||||
*/
|
||||
public class CpuUtils {
|
||||
private static boolean initCpu = true;
|
||||
private static double oCpu = 0.0;
|
||||
private static double oIdle = 0.0;
|
||||
|
||||
private static double pJif = 0.0;
|
||||
private static double pCpu = 0.0;
|
||||
private static double aCpu = 0.0;
|
||||
private static double opCpu = 0.0;
|
||||
private static double oaCpu = 0.0;
|
||||
|
||||
/**
|
||||
* 8.0以上获取cpu的方式
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static double getCpuUsageForO() {
|
||||
java.lang.Process process = null;
|
||||
try {
|
||||
process = Runtime.getRuntime().exec("top -n 1");
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||
String line;
|
||||
int cpuIndex = -1;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
line = line.trim();
|
||||
if (TextUtils.isEmpty(line)) {
|
||||
continue;
|
||||
}
|
||||
int tempIndex = getCPUIndex(line);
|
||||
if (tempIndex != -1) {
|
||||
cpuIndex = tempIndex;
|
||||
continue;
|
||||
}
|
||||
if (line.startsWith(String.valueOf(Process.myPid()))) {
|
||||
if (cpuIndex == -1) {
|
||||
continue;
|
||||
}
|
||||
String[] param = line.split("\\s+");
|
||||
if (param.length <= cpuIndex) {
|
||||
continue;
|
||||
}
|
||||
String cpu = param[cpuIndex];
|
||||
if (cpu.endsWith("%")) {
|
||||
cpu = cpu.substring(0, cpu.lastIndexOf("%"));
|
||||
}
|
||||
return Double.parseDouble(cpu) / Runtime.getRuntime().availableProcessors();
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (process != null) {
|
||||
process.destroy();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int getCPUIndex(String line) {
|
||||
if (line.contains("CPU")) {
|
||||
String[] titles = line.split("\\s+");
|
||||
for (int i = 0; i < titles.length; i++) {
|
||||
if (titles[i].contains("CPU")) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取应用的CPU占用率,单位:%,保留1位小数
|
||||
* @param pid
|
||||
* @return
|
||||
*/
|
||||
public static double getCpuUsagePercentWithPid(int pid) {
|
||||
double usage = 0.0;
|
||||
String[] result1;
|
||||
String[] result2;
|
||||
if (pid >= 0) {
|
||||
result1 = getCpuWithPid(pid);
|
||||
if (null != result1) {
|
||||
pCpu = Double.parseDouble(result1[1])
|
||||
+ Double.parseDouble(result1[2]);
|
||||
}
|
||||
result2 = getSystemCpu();
|
||||
if (null != result2) {
|
||||
aCpu = 0.0;
|
||||
for (int i = 2; i < result2.length; i++) {
|
||||
if (result2[i] != null) {
|
||||
aCpu += Double.parseDouble(result2[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((aCpu - oaCpu) != 0) {
|
||||
usage = DoubleUtils.div(((pCpu - opCpu) * 100.00),
|
||||
(aCpu - oaCpu), 1);
|
||||
if (usage < 0) {
|
||||
usage = 0;
|
||||
}
|
||||
else if (usage > 100)
|
||||
{
|
||||
usage = 100;
|
||||
}
|
||||
|
||||
}
|
||||
opCpu = pCpu;
|
||||
oaCpu = aCpu;
|
||||
}
|
||||
pJif = pCpu;
|
||||
return usage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取CPU使用率
|
||||
*
|
||||
* @return CPU使用率,单位:%
|
||||
*/
|
||||
public static double getSysCpuUsagePercent() {
|
||||
double usage = 0.0;
|
||||
if (initCpu) {
|
||||
initCpu = false;
|
||||
RandomAccessFile reader = null;
|
||||
try {
|
||||
reader = new RandomAccessFile("/proc/stat",
|
||||
"r");
|
||||
String load;
|
||||
load = reader.readLine();
|
||||
String[] toks = load.split(" ");
|
||||
oIdle = Double.parseDouble(toks[5]);
|
||||
oCpu = Double.parseDouble(toks[2])
|
||||
+ Double.parseDouble(toks[3])
|
||||
+ Double.parseDouble(toks[4])
|
||||
+ Double.parseDouble(toks[6])
|
||||
+ Double.parseDouble(toks[8])
|
||||
+ Double.parseDouble(toks[7]);
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
finally
|
||||
{
|
||||
FileUtil.closeRandomAccessFile(reader);
|
||||
}
|
||||
} else {
|
||||
RandomAccessFile reader = null;
|
||||
try {
|
||||
reader = new RandomAccessFile("/proc/stat", "r");
|
||||
String load;
|
||||
load = reader.readLine();
|
||||
String[] toks = load.split(" ");
|
||||
double cIdle = Double.parseDouble(toks[5]);
|
||||
double cCpu = Double.parseDouble(toks[2])
|
||||
+ Double.parseDouble(toks[3])
|
||||
+ Double.parseDouble(toks[4])
|
||||
+ Double.parseDouble(toks[6])
|
||||
+ Double.parseDouble(toks[8])
|
||||
+ Double.parseDouble(toks[7]);
|
||||
if (0 != ((cCpu + cIdle) - (oCpu + oIdle))) {
|
||||
usage = DoubleUtils.div((100.00 * ((cCpu - oCpu))),
|
||||
((cCpu + cIdle) - (oCpu + oIdle)), 1);
|
||||
if (usage < 0) {
|
||||
usage = 0;
|
||||
}
|
||||
else if (usage > 100)
|
||||
{
|
||||
usage = 100;
|
||||
}
|
||||
}
|
||||
oCpu = cCpu;
|
||||
oIdle = cIdle;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
FileUtil.closeRandomAccessFile(reader);
|
||||
}
|
||||
}
|
||||
return usage;
|
||||
}
|
||||
|
||||
private static String[] getCpuWithPid(int pid) {
|
||||
String cpuPath = "/proc/" + pid + "/stat";
|
||||
String cpu = "";
|
||||
String[] result = new String[3];
|
||||
|
||||
File f = new File(cpuPath);
|
||||
// 部分进程信息无法读取
|
||||
if (!f.exists() || !f.canRead())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
FileReader fr = null;
|
||||
BufferedReader localBufferedReader = null;
|
||||
|
||||
try {
|
||||
fr = new FileReader(f);
|
||||
localBufferedReader = new BufferedReader(fr, 8192);
|
||||
cpu = localBufferedReader.readLine();
|
||||
if (null != cpu) {
|
||||
String[] cpuSplit = cpu.split(" ");
|
||||
result[0] = cpuSplit[1];
|
||||
result[1] = cpuSplit[13];
|
||||
result[2] = cpuSplit[14];
|
||||
}
|
||||
}catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
FileUtil.closeReader(localBufferedReader);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static String[] getSystemCpu() {
|
||||
String cpuPath = "/proc/stat";
|
||||
String cpu = "";
|
||||
String[] result = new String[7];
|
||||
|
||||
File f = new File(cpuPath);
|
||||
if (!f.exists() || !f.canRead())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
FileReader fr = null;
|
||||
BufferedReader localBufferedReader = null;
|
||||
|
||||
try {
|
||||
fr = new FileReader(f);
|
||||
localBufferedReader = new BufferedReader(fr, 8192);
|
||||
cpu = localBufferedReader.readLine();
|
||||
if (null != cpu) {
|
||||
result = cpu.split(" ");
|
||||
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
FileUtil.closeReader(localBufferedReader);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String getCpuUsageByCmd() {
|
||||
java.lang.Process process;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line;
|
||||
String cmd = "dumpsys cpuinfo";
|
||||
try {
|
||||
process = Runtime.getRuntime().exec(
|
||||
new String[] { "sh", "-c", cmd });
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(
|
||||
process.getInputStream()));
|
||||
while (((line = br.readLine()) != null)) {
|
||||
// 去掉空白行数据
|
||||
line = line.trim();
|
||||
if (line.equals("")) {
|
||||
continue;
|
||||
}
|
||||
sb.append(line);
|
||||
sb.append("\r\n");
|
||||
}
|
||||
try {
|
||||
process.waitFor();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取进程的CPU使用时间片
|
||||
*
|
||||
* @return 进程的CPU使用时间片
|
||||
*/
|
||||
public long getJif() {
|
||||
return (long)pJif;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.zhjt.mogo_core_function_devatools.monitor.utils
|
||||
|
||||
import java.lang.Exception
|
||||
import java.math.BigDecimal
|
||||
|
||||
|
||||
object DoubleUtils {
|
||||
|
||||
fun mul(d1: Double, d2: Double): Double {
|
||||
val bd1 = BigDecimal(d1.toString())
|
||||
val bd2 = BigDecimal(d2.toString())
|
||||
return try {
|
||||
bd1.multiply(bd2).toDouble()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun div(d1: Double, d2: Double, scale: Int): Double {
|
||||
val bd1 = BigDecimal(d1.toString())
|
||||
val bd2 = BigDecimal(d2.toString())
|
||||
return try {
|
||||
// 直接向下取整
|
||||
bd1.divide(bd2, scale, BigDecimal.ROUND_DOWN).toDouble()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 四舍五入
|
||||
* scale:保留多少个小数位
|
||||
*/
|
||||
@JvmStatic
|
||||
fun keepDecimal(v: Double, scale: Int = 1): Double {
|
||||
if (scale < 0) {
|
||||
throw IllegalArgumentException("The scale must be a positive integer or zero")
|
||||
}
|
||||
return BigDecimal(v).setScale(scale, BigDecimal.ROUND_HALF_UP).toDouble()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
package com.zhjt.mogo_core_function_devatools.monitor.utils
|
||||
|
||||
import android.net.LocalSocket
|
||||
import android.util.Log
|
||||
import java.io.*
|
||||
import java.lang.Exception
|
||||
import java.net.Socket
|
||||
import java.nio.channels.FileChannel
|
||||
|
||||
object FileUtil {
|
||||
private const val TAG = "FileUtil"
|
||||
const val separator = "/"
|
||||
fun isPathStringValid(path: String?): Boolean {
|
||||
if (null == path || path.isEmpty()) {
|
||||
return false
|
||||
}
|
||||
if (path.contains(":") || path.contains("*") || path.contains("?")
|
||||
|| path.contains("\"") || path.contains("<")
|
||||
|| path.contains(">") || path.contains("|")
|
||||
) {
|
||||
Log.w(TAG, "filename can not contains:*:?\"<>|")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun isPath(path: String): Boolean {
|
||||
return path.contains(separator) || path.contains("\\")
|
||||
}
|
||||
|
||||
fun getPath(path: String): String {
|
||||
val la = path.lastIndexOf(separator)
|
||||
return path.substring(0, la)
|
||||
}
|
||||
|
||||
fun convertValidFilePath(path: String, defPosfix: String): String {
|
||||
var filePath = path
|
||||
if (path.contains(separator) || path.contains("\\")) {
|
||||
val la = filePath.lastIndexOf(".")
|
||||
if (la < 0) {
|
||||
filePath = path + defPosfix
|
||||
} else {
|
||||
val temp = filePath.substring(la)
|
||||
if (temp.contains(separator) || temp.contains("\\")) {
|
||||
// "."是目录名的一部分而不是后缀名的情况
|
||||
filePath = path + defPosfix
|
||||
}
|
||||
// else fileName = fileName
|
||||
}
|
||||
} else {
|
||||
if (!path.contains(".")) // 没有有后缀
|
||||
{
|
||||
filePath = filePath + defPosfix
|
||||
}
|
||||
}
|
||||
return filePath
|
||||
}
|
||||
|
||||
fun isFileExists(file: String?): Boolean {
|
||||
try {
|
||||
val f = File(file)
|
||||
if (!f.exists()) {
|
||||
return false
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun isFileValid(f: File): Boolean {
|
||||
if (!f.exists()) {
|
||||
try {
|
||||
f.createNewFile()
|
||||
} catch (e: IOException) {
|
||||
return false
|
||||
}
|
||||
f.delete()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun isFileValid(parent: File?, name: String?): Boolean {
|
||||
val f = File(parent, name)
|
||||
return isFileValid(f)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除存在的文件
|
||||
*
|
||||
* @param filePath
|
||||
*/
|
||||
fun delExistFile(filePath: String?) {
|
||||
val f = File(filePath)
|
||||
if (f.exists()) f.delete()
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭bufferReader
|
||||
*
|
||||
* @param br
|
||||
*/
|
||||
@JvmStatic
|
||||
fun closeReader(br: Reader?) {
|
||||
if (br != null) {
|
||||
try {
|
||||
br.close()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭Writer
|
||||
*
|
||||
* @param wr
|
||||
*/
|
||||
fun closeWriter(wr: Writer?) {
|
||||
if (wr != null) {
|
||||
try {
|
||||
wr.close()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* flush Writer
|
||||
*
|
||||
* @param wr
|
||||
*/
|
||||
fun flushWriter(wr: Writer?) {
|
||||
if (wr != null) {
|
||||
try {
|
||||
wr.flush()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 输入流的关闭
|
||||
*
|
||||
* @param in
|
||||
*/
|
||||
fun closeInputStream(`in`: InputStream?) {
|
||||
if (`in` != null) {
|
||||
try {
|
||||
`in`.close()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出流的关闭
|
||||
*
|
||||
* @param out
|
||||
*/
|
||||
fun closeOutputStream(out: OutputStream?) {
|
||||
if (out != null) {
|
||||
try {
|
||||
out.close()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件管道的关闭
|
||||
*
|
||||
* @param chl
|
||||
*/
|
||||
fun closeFileChannel(chl: FileChannel?) {
|
||||
if (chl != null) {
|
||||
try {
|
||||
chl.close()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* RandomAccessFile的关闭
|
||||
*
|
||||
* @param f RandomAccessFile对象
|
||||
*/
|
||||
@JvmStatic
|
||||
fun closeRandomAccessFile(f: RandomAccessFile?) {
|
||||
if (f != null) {
|
||||
try {
|
||||
f.close()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Socket的关闭
|
||||
*
|
||||
* @param s Socket对象
|
||||
*/
|
||||
fun colseSocket(s: Socket?) {
|
||||
if (s != null) {
|
||||
try {
|
||||
s.close()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* LocalSocket的关闭
|
||||
*
|
||||
* @param s Socket对象
|
||||
*/
|
||||
fun colseLocalSocket(s: LocalSocket?) {
|
||||
if (s != null) {
|
||||
try {
|
||||
s.close()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteFile(file: File) {
|
||||
if (file.exists()) { // 判断文件是否存在
|
||||
if (file.isFile) { // 判断是否是文件
|
||||
file.delete() // delete()方法 你应该知道 是删除的意思;
|
||||
} else if (file.isDirectory) { // 否则如果它是一个目录
|
||||
val files = file.listFiles() // 声明目录下所有的文件 files[];
|
||||
for (i in files.indices) { // 遍历目录下所有的文件
|
||||
deleteFile(files[i]) // 把每个文件 用这个方法进行迭代
|
||||
}
|
||||
}
|
||||
file.delete()
|
||||
} else {
|
||||
Log.e(TAG, "文件不存在!" + "n")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 拷贝文件
|
||||
*
|
||||
* @param s 源文件
|
||||
* @param t 目标文件
|
||||
*/
|
||||
fun copyFile(s: File?, t: File) {
|
||||
var fi: FileInputStream? = null
|
||||
var fo: FileOutputStream? = null
|
||||
var `in`: FileChannel? = null
|
||||
var out: FileChannel? = null
|
||||
try {
|
||||
if (!t.exists()) {
|
||||
t.createNewFile()
|
||||
}
|
||||
fi = FileInputStream(s)
|
||||
fo = FileOutputStream(t)
|
||||
`in` = fi.channel
|
||||
out = fo.channel
|
||||
// 连接两个通道,并且从in通道读取,然后写入out通道
|
||||
`in`.transferTo(0, `in`.size(), out)
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
} finally {
|
||||
closeOutputStream(fo)
|
||||
closeInputStream(fi)
|
||||
closeFileChannel(`in`)
|
||||
closeFileChannel(out)
|
||||
}
|
||||
}
|
||||
|
||||
fun copyInputToFile(`in`: InputStream?, path: String?) {
|
||||
var bis: BufferedInputStream? = null
|
||||
var fos: FileOutputStream? = null
|
||||
try {
|
||||
val buffer = ByteArray(10 * 1024)
|
||||
bis = BufferedInputStream(`in`)
|
||||
fos = FileOutputStream(path)
|
||||
var a = bis.read(buffer, 0, buffer.size)
|
||||
while (a != -1) {
|
||||
fos.write(buffer, 0, a)
|
||||
fos.flush()
|
||||
a = bis.read(buffer, 0, buffer.size)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
} finally {
|
||||
closeOutputStream(fos)
|
||||
closeInputStream(bis)
|
||||
closeInputStream(`in`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.zhjt.mogo_core_function_devatools.monitor.utils;
|
||||
|
||||
import android.os.Debug;
|
||||
import android.util.Log;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
public class HookUtils {
|
||||
private static final String TAG = "HookUtils";
|
||||
|
||||
/**
|
||||
* 单位为MB
|
||||
* 0: 总PSS,1: dalvik总Pss, 2: native总Pss, 3: other总Pss
|
||||
* @param memoryInfo
|
||||
* @return
|
||||
*/
|
||||
public static double[] getPssArray(Debug.MemoryInfo memoryInfo) {
|
||||
double[] pssArray = new double[4];
|
||||
if (memoryInfo != null) {
|
||||
double totalPss = DoubleUtils.keepDecimal(memoryInfo.getTotalPss() / 1024.0, 3);
|
||||
// Log.d(TAG, "===========Hook PSS 开始:===========\n");
|
||||
// Log.d(TAG, "总PSS内存为:" + totalPss);
|
||||
pssArray[0] = totalPss;
|
||||
try {
|
||||
Field dalvikField = memoryInfo.getClass().getField("dalvikSwappedOutPss");
|
||||
double dalvikPss = DoubleUtils.keepDecimal(memoryInfo.dalvikPss / 1024.0, 3);
|
||||
double dalvikSwappedOutPss = DoubleUtils.keepDecimal((int) dalvikField.get(memoryInfo) / 1024.0, 3);
|
||||
pssArray[1] = dalvikPss + dalvikSwappedOutPss;
|
||||
// Log.d(TAG, "dalvikPss is:" + dalvikSwappedOutPss);
|
||||
// Log.d(TAG, "dalvikSwappedOutPss is:" + dalvikPss + "\n\n");
|
||||
|
||||
Field nativeField = memoryInfo.getClass().getField("nativeSwappedOutPss");
|
||||
double nativePss = DoubleUtils.keepDecimal(memoryInfo.nativePss / 1024.0, 3);
|
||||
double nativeSwappedOutPss = DoubleUtils.keepDecimal((int) nativeField.get(memoryInfo) / 1024.0, 3);
|
||||
pssArray[2] = nativePss + nativeSwappedOutPss;
|
||||
// Log.d(TAG, "nativePss is:" + nativePss);
|
||||
// Log.d(TAG, "nativeSwappedOutPss is:" + nativeSwappedOutPss + "\n\n");
|
||||
|
||||
Field otherField = memoryInfo.getClass().getField("otherSwappedOutPss");
|
||||
double otherPss = DoubleUtils.keepDecimal(memoryInfo.otherPss / 1024.0, 3);
|
||||
double otherSwappedOutPss = DoubleUtils.keepDecimal((int) otherField.get(memoryInfo) / 1024.0, 3);
|
||||
pssArray[3] = otherPss + otherSwappedOutPss;
|
||||
// Log.d(TAG, "otherPss is:" + otherPss);
|
||||
// Log.d(TAG, "otherSwappedOutPss is:" + otherSwappedOutPss + "\n\n");
|
||||
//
|
||||
// Log.d(TAG, "===========Hook PSS 结束!===========\n");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, e.getMessage());
|
||||
}
|
||||
}
|
||||
return pssArray;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
package com.zhjt.mogo_core_function_devatools.monitor.utils;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.os.Debug;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 内存信息工具类。
|
||||
*/
|
||||
public class MemUtils {
|
||||
|
||||
public static double[] getPssArray(Context context, int[] pids) {
|
||||
Debug.MemoryInfo[] memoryArray = getMemoryInfo(context, pids);
|
||||
if (memoryArray.length > 0) {
|
||||
return HookUtils.getPssArray(memoryArray[0]);
|
||||
} else {
|
||||
return new double[4];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 单位MB
|
||||
*/
|
||||
public static double getPssMemValue(Context context, int[] pids) {
|
||||
double value = 0.0;
|
||||
Debug.MemoryInfo[] memoryArray = getMemoryInfo(context, pids);
|
||||
if (memoryArray.length > 0) {
|
||||
value = DoubleUtils.keepDecimal(memoryArray[0].getTotalPss() / 1024.0, 1);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 只能获取本应用的PSS内存占系统百分比:单位%,保留1位小数
|
||||
* @return
|
||||
*/
|
||||
public static double getPidMemPercent() {
|
||||
double percent = 0.0;
|
||||
Debug.MemoryInfo memInfo = new Debug.MemoryInfo();
|
||||
Debug.getMemoryInfo(memInfo);
|
||||
int pidTotalPss = memInfo.getTotalPss();
|
||||
long systemTotalMem = getTotalMem();
|
||||
percent = DoubleUtils.keepDecimal(pidTotalPss * 100.0 / systemTotalMem, 1);
|
||||
return percent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本应用进程总pss内存,单位MB
|
||||
*/
|
||||
public static double getPssMem() {
|
||||
Debug.MemoryInfo memInfo = new Debug.MemoryInfo();
|
||||
Debug.getMemoryInfo(memInfo);
|
||||
return DoubleUtils.keepDecimal(memInfo.getTotalPss() / 1024.0, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统空闲内存占用百分比: 单位%
|
||||
* @return
|
||||
*/
|
||||
public static double getSystemMemPercent() {
|
||||
double percent = 0.0;
|
||||
long[] memInfo = getMemInfo();
|
||||
long free = memInfo[1] + memInfo[2] + memInfo[3];
|
||||
long total = memInfo[0];
|
||||
percent = DoubleUtils.keepDecimal(free * 100.0 / total, 1);
|
||||
return percent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取空闲内存
|
||||
*
|
||||
* @return 空闲内存,单位KB
|
||||
*/
|
||||
public static long getFreeMem() {
|
||||
long[] memInfo = getMemInfo();
|
||||
return memInfo[1] + memInfo[2] + memInfo[3];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取总内存
|
||||
*
|
||||
* @return 总内存,单位KB
|
||||
*/
|
||||
public static long getTotalMem() {
|
||||
long[] memInfo = getMemInfo();
|
||||
return memInfo[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取内存信息:total、free、buffers、cached,单位KB
|
||||
*
|
||||
* @return 内存信息
|
||||
*/
|
||||
public static long[] getMemInfo() {
|
||||
long memInfo[] = new long[4];
|
||||
try {
|
||||
Class<?> procClazz = Class.forName("android.os.Process");
|
||||
Class<?> paramTypes[] = new Class[] { String.class, String[].class,
|
||||
long[].class };
|
||||
Method readProclines = procClazz.getMethod("readProcLines",
|
||||
paramTypes);
|
||||
Object args[] = new Object[3];
|
||||
final String[] memInfoFields = new String[] { "MemTotal:",
|
||||
"MemFree:", "Buffers:", "Cached:" };
|
||||
long[] memInfoSizes = new long[memInfoFields.length];
|
||||
memInfoSizes[0] = 30;
|
||||
memInfoSizes[1] = -30;
|
||||
args[0] = "/proc/meminfo";
|
||||
args[1] = memInfoFields;
|
||||
args[2] = memInfoSizes;
|
||||
if (null != readProclines) {
|
||||
readProclines.invoke(null, args);
|
||||
for (int i = 0; i < memInfoSizes.length; i++) {
|
||||
memInfo[i] = memInfoSizes[i];
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return memInfo;
|
||||
}
|
||||
|
||||
public static long[] getHeapNative() {
|
||||
int Native_HeapSize = 0;
|
||||
int Native_HeapAlloc = 1;
|
||||
long[] value = new long[2];
|
||||
value[Native_HeapSize] = Debug.getNativeHeapSize() >> 10;
|
||||
value[Native_HeapAlloc] = Debug.getNativeHeapAllocatedSize() >> 10;
|
||||
return value;
|
||||
}
|
||||
|
||||
public static long[] getHeapDalvik() {
|
||||
int Total_HeapSize = 0;
|
||||
int Total_HeapAlloc = 1;
|
||||
|
||||
long[] value_total = new long[2];
|
||||
value_total[Total_HeapSize] = Runtime.getRuntime().totalMemory() >> 10;
|
||||
value_total[Total_HeapAlloc] = (Runtime.getRuntime().totalMemory() - Runtime
|
||||
.getRuntime().freeMemory()) >> 10;
|
||||
|
||||
long[] value_native = getHeapNative();
|
||||
|
||||
int Dalvik_HeapSize = 0;
|
||||
int Dalvik_HeapAlloc = 1;
|
||||
long[] value_dalvik = new long[2];
|
||||
value_dalvik[Dalvik_HeapSize] = value_total[Total_HeapSize]
|
||||
- value_native[0];
|
||||
value_dalvik[Dalvik_HeapAlloc] = value_total[Total_HeapAlloc]
|
||||
- value_native[1];
|
||||
|
||||
return value_dalvik;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取堆内存数据,精确到KB Get VM Heap Size by calling:
|
||||
* Runtime.getRuntime().totalMemory(); Get Allocated VM Memory by calling:
|
||||
* Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
|
||||
* Get VM Heap Size Limit by calling: Runtime.getRuntime().maxMemory() Get
|
||||
* Native Allocated Memory by calling: Debug.getNativeHeapAllocatedSize();
|
||||
*/
|
||||
public static long[] getVM() {
|
||||
long[] value = new long[5];
|
||||
value[0] = (Runtime.getRuntime().totalMemory() - Runtime.getRuntime()
|
||||
.freeMemory()) >> 10;
|
||||
value[1] = Runtime.getRuntime().totalMemory() >> 10;
|
||||
|
||||
value[2] = Debug.getNativeHeapAllocatedSize() >> 10;
|
||||
value[3] = Debug.getNativeHeapSize() >> 10;
|
||||
value[4] = Debug.getGlobalAllocSize() >> 10;
|
||||
return value;
|
||||
}
|
||||
|
||||
private static Debug.MemoryInfo[] getMemoryInfo(Context context, int[] pids) {
|
||||
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
return am.getProcessMemoryInfo(pids);
|
||||
}
|
||||
}
|
||||
@@ -160,6 +160,8 @@ import java.util.*
|
||||
|
||||
private var busOperationStatus: IOchBusView? = null
|
||||
|
||||
private var isStarted = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
}
|
||||
@@ -182,10 +184,17 @@ import java.util.*
|
||||
}
|
||||
}
|
||||
|
||||
/*ivCameraIcon?.setOnLongClickListener {
|
||||
activity?.let { it1 -> CarcorderPreviewView.show(it1) }
|
||||
ivCameraIcon?.setOnLongClickListener {
|
||||
if (!isStarted) {
|
||||
CallerDevaToolsManager.startMonitor()
|
||||
isStarted = true
|
||||
} else {
|
||||
CallerDevaToolsManager.stopMonitor()
|
||||
isStarted = false
|
||||
}
|
||||
// activity?.let { it1 -> CarcorderPreviewView.show(it1) }
|
||||
true
|
||||
}*/
|
||||
}
|
||||
|
||||
ivToolsIcon?.setOnClickListener {
|
||||
if (toolsViewFloat == null) {
|
||||
|
||||
@@ -28,8 +28,12 @@ import com.mogo.module.common.MogoModule;
|
||||
import com.mogo.module.common.MogoModulePaths;
|
||||
import com.mogo.module.service.ServiceConst;
|
||||
import com.zhidao.support.obu.ami.AmiClientManager;
|
||||
import com.zhjt.mogo_core_function_devatools.monitor.db.CpuInfo;
|
||||
import com.zhjt.mogo_core_function_devatools.monitor.db.MemInfo;
|
||||
import com.zhjt.mogo_core_function_devatools.monitor.db.MonitorDb;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 默认初始化一些基础服务配置
|
||||
@@ -58,6 +62,7 @@ public abstract class MainMoGoApplication extends AbsMogoApplication {
|
||||
|
||||
//查询是否有版本的更新
|
||||
queryAppUpgrade();
|
||||
checkMonitorDb();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -75,6 +80,18 @@ public abstract class MainMoGoApplication extends AbsMogoApplication {
|
||||
},5000);
|
||||
}
|
||||
|
||||
private void checkMonitorDb() {
|
||||
new Thread(() -> {
|
||||
long limitId = 50001;
|
||||
List<CpuInfo> cpuList = MonitorDb.getDb(this).monitorDao().getAllCPUById(limitId);
|
||||
List<MemInfo> memList = MonitorDb.getDb(this).monitorDao().getAllMemById(limitId);
|
||||
// 大于5w条清除
|
||||
if (cpuList.size() > 0 || memList.size() > 0) {
|
||||
this.deleteDatabase(MonitorDb.INTERNAL_DB_NAME);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化异常采集配置
|
||||
*/
|
||||
|
||||
@@ -113,4 +113,8 @@ interface IDevaToolsProvider : IProvider {
|
||||
* 隐藏状态栏
|
||||
*/
|
||||
fun hideStatusBar()
|
||||
|
||||
fun startMonitor()
|
||||
|
||||
fun stopMonitor()
|
||||
}
|
||||
@@ -164,4 +164,12 @@ object CallerDevaToolsManager {
|
||||
devaToolsProviderApi?.hideStatusBar()
|
||||
}
|
||||
}
|
||||
|
||||
fun startMonitor() {
|
||||
devaToolsProviderApi?.startMonitor()
|
||||
}
|
||||
|
||||
fun stopMonitor() {
|
||||
devaToolsProviderApi?.stopMonitor()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user