fps帧率监控
项目地址:https://github.com/Peakmain/BasicUI/tree/kotlin/ui/src/main/java/com/peakmain/ui/utils/fps
如果大家觉得有点帮助,还请抬抬你的贵手,给我点个小星星。
如果我们跟Choreographer的postFrameCallback源码我们会发现最后会走到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被渲染的时候回调,因此我们可以通过这个原理实现应用的帧率监听
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("当前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
}
}
之后我们只需要调用FrameMonitor的start即可开启帧率检测。我这里对其进行了封装大家可以查看FpsMonitorUtils
java、native异常捕获
https://github.com/Peakmain/BasicUI/tree/kotlin/ui/src/main/java/com/peakmain/ui/utils/crash
- java捕获异常
java捕获异常其实很简单,只需要继承 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) {
//默认系统处理
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发生时间
sb.append("forground=${ActivityUtils.mInstance.isFront}\n")//应用处于前后台
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")//可用内存
sb.append("totalMemory=${Formatter.formatFileSize(context, memoryInfo.totalMem)}\n")//设备总内存
val file = Environment.getExternalStorageDirectory()
//手机内部可用空间
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的异常捕获实际调用的是breadkpad的方法,这里我已经编译成aar,大家可以直接去下载放到自己项目就可以了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
网友评论