fps幀率監(jiān)控
項(xiàng)目地址:https://github.com/Peakmain/BasicUI/tree/kotlin/ui/src/main/java/com/peakmain/ui/utils/fps
如果大家覺得有點(diǎn)幫助奥帘,還請?zhí)愕馁F手,給我點(diǎn)個小星星即硼。
如果我們跟Choreographer的postFrameCallback源碼我們會發(fā)現(xiàn)最后會走到CallbackRecord 的run方法
private static final class CallbackRecord {
public CallbackRecord next;
public long dueTime;
public Object action; // Runnable or FrameCallback
public Object token;
public void run(long frameTimeNanos) {
if (token == FRAME_CALLBACK_TOKEN) {
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
((Runnable)action).run();
}
}
}
也就是說自定義的FrameCallback會在下一個frame被渲染的時候回調(diào)竭宰,因此我們可以通過這個原理實(shí)現(xiàn)應(yīng)用的幀率監(jiān)聽
internal class FrameMonitor : Choreographer.FrameCallback {
private val mChoreographer = Choreographer.getInstance()
private var mFrameStartTime: Long = 0
//1s繪制了多少幀
private var mFrameCount = 0
private var mCallbackLists = arrayListOf<FpsMonitorUtils.FpsCallback>()
private var isPrintMessage = false
override fun doFrame(frameTimeNanos: Long) {
val currentTimeMills = TimeUnit.NANOSECONDS.toMillis(frameTimeNanos)
if (mFrameStartTime > 0) {
val time = currentTimeMills - mFrameStartTime
mFrameCount++
if (time > 1000) {
val fps = mFrameCount * 1000 / time.toDouble()
val topActivity = ActivityUtils.mInstance.getTopActivity(true)
if (isPrintMessage) {
LogUtils.d("當(dāng)前Activity是:$topActivity,它的fps是:$fps")
}
mCallbackLists.forEach {
it.onFrame(fps)
}
mFrameCount = 0
mFrameStartTime = currentTimeMills
}
} else {
mFrameStartTime = currentTimeMills
}
start()
}
fun start() {
mChoreographer.postFrameCallback(this)
}
fun stop() {
mFrameStartTime = 0
mChoreographer.removeFrameCallback(this)
}
fun addCallback(callback: FpsMonitorUtils.FpsCallback) {
mCallbackLists.add(callback)
}
fun reset() {
mFrameStartTime = 0
mChoreographer.removeFrameCallback(this)
mFrameCount = 0
mCallbackLists.clear()
}
fun printMessage(print: Boolean) {
this.isPrintMessage = print
}
}
之后我們只需要調(diào)用FrameMonitor的start即可開啟幀率檢測。我這里對其進(jìn)行了封裝大家可以查看FpsMonitorUtils
動畫.gif
java宫莱、native異常捕獲
https://github.com/Peakmain/BasicUI/tree/kotlin/ui/src/main/java/com/peakmain/ui/utils/crash
- java捕獲異常
java捕獲異常其實(shí)很簡單丈攒,只需要繼承 Thread.UncaughtExceptionHandler,然后重寫uncaughtException方法
var CRASH_DIR = "crash_dir"
fun init(crashDir: String) {
Thread.setDefaultUncaughtExceptionHandler(CaughtExceptionHandler())
this.CRASH_DIR = crashDir
}
class CaughtExceptionHandler : Thread.UncaughtExceptionHandler {
private val context = BasicUIUtils.application!!
private val formatter = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.CHINA)
private val LAUNCH_TIME = formatter.format(Date())
private val mDefaultExceptionHandler = Thread.getDefaultUncaughtExceptionHandler()
override fun uncaughtException(thread: Thread?, e: Throwable?) {
if (!handleException(e) && mDefaultExceptionHandler != null) {
//默認(rèn)系統(tǒng)處理
mDefaultExceptionHandler.uncaughtException(thread, e)
}
//重啟app
restartApp()
}
private fun restartApp() {
val intent: Intent? =
context.packageManager?.getLaunchIntentForPackage(context.packageName)
intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
context.startActivity(intent)
Process.killProcess(Process.myPid())
exitProcess(10)
}
//是否有異常
private fun handleException(e: Throwable?): Boolean {
if (e == null) return false
val logInfo = createLogInfo(e)
if (BuildConfig.DEBUG) {
LogUtils.e(logInfo)
}
val file = saveCrashInfoToFile(logInfo)
if(CrashUtils.mListener!=null){
CrashUtils.mListener!!.onFileUploadListener(file)
}
return true
}
private fun saveCrashInfoToFile(logInfo: String): File {
val crashDir = File(CRASH_DIR)
if (!crashDir.exists()) {
crashDir.mkdirs()
}
val file = File(crashDir, formatter.format(Date()) + "-crash.txt")
file.createNewFile()
LogUtils.e(file.absolutePath)
val fos = FileOutputStream(file)
try {
fos.write(logInfo.toByteArray())
fos.flush()
} catch (e: Exception) {
e.printStackTrace()
} finally {
fos.close()
}
return file
}
private fun createLogInfo(e: Throwable): String {
val sb = StringBuilder()
sb.append("brand=${Build.BRAND}\n")
sb.append("rom=${Build.MODEL}\n")
sb.append("os=${Build.VERSION.RELEASE}\n")
sb.append("sdk=${Build.VERSION.SDK_INT}\n")
sb.append("launch_time=${LAUNCH_TIME}\n")//啟動app的時間
sb.append("crash_time=${formatter.format(Date())}\n")//crash發(fā)生時間
sb.append("forground=${ActivityUtils.mInstance.isFront}\n")//應(yīng)用處于前后臺
sb.append("thread=${Thread.currentThread().name}\n")//異常線程名
sb.append("cpu_arch=${Build.CPU_ABI}\n")
//app 信息
val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
sb.append("versionCode=${packageInfo.versionCode}\n")//版本號
sb.append("versionName=${packageInfo.versionName}\n")
sb.append("packageName=${packageInfo.packageName}\n")
sb.append("requestedPermission=${Arrays.toString(packageInfo.requestedPermissions)}\n")
val memoryInfo = ActivityManager.MemoryInfo()
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
activityManager.getMemoryInfo(memoryInfo)
sb.append("availMemory=${Formatter.formatFileSize(context, memoryInfo.availMem)}\n")//可用內(nèi)存
sb.append("totalMemory=${Formatter.formatFileSize(context, memoryInfo.totalMem)}\n")//設(shè)備總內(nèi)存
val file = Environment.getExternalStorageDirectory()
//手機(jī)內(nèi)部可用空間
val statFs = StatFs(file.path)
val availableSize = statFs.availableBlocks * statFs.blockSize
sb.append(
"availStorage=${Formatter.formatFileSize(
context,
availableSize.toLong()
)}\n"
)
val write: Writer = StringWriter()
val printWriter = PrintWriter(write)
e.printStackTrace(printWriter)
var cause = e.cause
while (cause != null) {
cause.printStackTrace(printWriter)
cause = cause.cause
}
printWriter.close()
sb.append(write.toString())
return sb.toString()
}
}
- native異常捕獲
native的異常捕獲實(shí)際調(diào)用的是breadkpad的方法授霸,這里我已經(jīng)編譯成aar巡验,大家可以直接去下載放到自己項(xiàng)目就可以了aar目錄,使用也很簡單
NativeCrashHandler.init(nativeCrashDir.absolutePath)
Java和native的異常我封裝成了一個CrashUtils工具類
object CrashUtils {
private const val CRASH_DIR_JAVA = "javaCrash"
private const val CRASH_DIR_NATIVE = "nativeCrash"
fun init() {
val javaCrashDir = getJavaCrashDir()
val nativeCrashDir = getNativeCrashDir()
CrashHelper.init(javaCrashDir.absolutePath)
NativeCrashHandler.init(nativeCrashDir.absolutePath)
}
private fun getNativeCrashDir(): File {
val file = File(BasicUIUtils.application!!.cacheDir, CRASH_DIR_NATIVE)
if (!file.exists()) {
file.mkdirs()
}
return file
}
private fun getJavaCrashDir(): File {
val file = File(BasicUIUtils.application!!.cacheDir, CRASH_DIR_JAVA)
if (!file.exists()) {
file.mkdirs()
}
return file
}
var mListener: OnFileUploadListener? = null
interface OnFileUploadListener {
fun onFileUploadListener(file: File)
}
//文件上傳
fun setOnFileUploadListener(listener: OnFileUploadListener?) {
this.mListener = listener
}
}
只需要CrashUtils.init()初始化一下就可以了
我的開源庫生成給的目錄在data/user/com.peakmain.basicui/cache中
image.png