未验证 提交 4b83c86a 编写于 作者: 金台 提交者: GitHub

Merge pull request #614 from willpyshan13/kotlin

[kotlin]-[健康体检]-[实现健康体检的相关功能并完成自测]
......@@ -47,7 +47,7 @@ internal object LeakCanaryManager {
val leakBean = LeakBean()
leakBean.page = ActivityUtils.getTopActivity().javaClass.canonicalName
leakBean.detail = message
//AppHealthInfoUtil.getInstance().addLeakInfo(leakBean)
AppHealthInfoUtil.instance.addLeakInfo(leakBean)
}
} catch (e: Exception) {
e.printStackTrace()
......
......@@ -61,7 +61,7 @@ dependencies {
implementation rootProject.ext.dependencies["recyclerview"]
implementation rootProject.ext.dependencies["kotlin"]
implementation rootProject.ext.dependencies["core-ktx"]
// implementation rootProject.ext.dependencies["kotlin-coroutines"]
implementation rootProject.ext.dependencies["kotlin-coroutines"]
implementation rootProject.ext.dependencies["gson"]
implementation rootProject.ext.dependencies["zxing"]
......
......@@ -9,6 +9,8 @@ import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import com.didichuxing.doraemonkit.constant.DokitConstant
import com.didichuxing.doraemonkit.kit.core.DokitViewManager
import com.didichuxing.doraemonkit.kit.health.AppHealthInfoUtil
import com.didichuxing.doraemonkit.kit.health.model.AppHealthInfo
import com.didichuxing.doraemonkit.kit.uiperformance.UIPerformanceUtil
import com.didichuxing.doraemonkit.model.ActivityLifecycleInfo
import com.didichuxing.doraemonkit.model.ViewInfo
......@@ -216,11 +218,11 @@ internal class DokitActivityLifecycleCallbacks : Application.ActivityLifecycleCa
""".trimIndent()
LogHelper.d(TAG,detail)
//todo 需要完成上传数据到健康数据
// val uiLevelBean = UiLevelBean()
// uiLevelBean.page = activity.javaClass.canonicalName
// uiLevelBean.level = "" + maxLevel
// uiLevelBean.detail = detail
// AppHealthInfoUtil.getInstance().addUiLevelInfo(uiLevelBean)
val uiLevelBean = AppHealthInfo.DataBean.UiLevelBean()
uiLevelBean.page = activity.javaClass.canonicalName
uiLevelBean.level = "" + maxLevel
uiLevelBean.detail = detail
AppHealthInfoUtil.instance.addUiLevelInfo(uiLevelBean)
} catch (e: Exception) {
e.printStackTrace()
}
......
......@@ -28,7 +28,9 @@ import com.didichuxing.doraemonkit.kit.dataclean.DataCleanKit
import com.didichuxing.doraemonkit.kit.dbdebug.DbDebugKit
import com.didichuxing.doraemonkit.kit.fileexplorer.FileExplorerKit
import com.didichuxing.doraemonkit.kit.gpsmock.GpsMockKit
import com.didichuxing.doraemonkit.kit.health.AppHealthInfoUtil
import com.didichuxing.doraemonkit.kit.health.HealthKit
import com.didichuxing.doraemonkit.kit.health.model.AppHealthInfo
import com.didichuxing.doraemonkit.kit.largepicture.LargePictureKit
import com.didichuxing.doraemonkit.kit.layoutborder.LayoutBorderKit
import com.didichuxing.doraemonkit.kit.loginfo.LogInfoKit
......@@ -359,12 +361,11 @@ object DoraemonKitReal {
//若是文件,直接打印 byte
val fileLength = FileUtils.getLength(file)
if (fileLength > FILE_LENGTH_THRESHOLD) {
//TODO("健康体检功能")
// val fileBean = BigFileBean()
// fileBean.fileName = FileUtils.getFileName(file)
// fileBean.filePath = file.absolutePath
// fileBean.fileSize = "" + fileLength
// AppHealthInfoUtil.getInstance().addBigFilrInfo(fileBean)
val fileBean = AppHealthInfo.DataBean.BigFileBean()
fileBean.fileName = FileUtils.getFileName(file)
fileBean.filePath = file.absolutePath
fileBean.fileSize = "" + fileLength
AppHealthInfoUtil.instance.addBigFilrInfo(fileBean)
}
//LogHelper.i(TAG, "文件==>" + file.getAbsolutePath() + " fileName===>" + FileUtils.getFileName(file) + " fileLength===>" + fileLength);
}
......
......@@ -6,6 +6,7 @@ import android.content.Intent
import android.os.Debug
import android.os.Looper
import android.text.TextUtils
import com.blankj.utilcode.util.ActivityUtils
import com.didichuxing.doraemonkit.DoraemonKit
import com.didichuxing.doraemonkit.R
import com.didichuxing.doraemonkit.constant.BundleKey
......@@ -15,6 +16,8 @@ import com.didichuxing.doraemonkit.kit.blockmonitor.BlockMonitorFragment
import com.didichuxing.doraemonkit.kit.blockmonitor.bean.BlockInfo
import com.didichuxing.doraemonkit.kit.blockmonitor.core.BlockCanaryUtils.concernStackString
import com.didichuxing.doraemonkit.kit.core.UniversalActivity
import com.didichuxing.doraemonkit.kit.health.AppHealthInfoUtil
import com.didichuxing.doraemonkit.kit.health.model.AppHealthInfo.DataBean.BlockBean
import com.didichuxing.doraemonkit.util.LogHelper.e
import com.didichuxing.doraemonkit.util.LogHelper.i
import com.didichuxing.doraemonkit.util.NotificationUtils
......@@ -78,7 +81,16 @@ class BlockMonitorManager private constructor() {
* @param blockInfo
*/
private fun addBlockInfoInAppHealth(blockInfo: BlockInfo) {
//todo addBlockInfoInAppHealth
try {
val activityName = ActivityUtils.getTopActivity().javaClass.canonicalName
val blockBean = BlockBean()
blockBean.page = activityName
blockBean.blockTime = blockInfo.timeCost
blockBean.detail = blockInfo.toString()
AppHealthInfoUtil.instance.addBlockInfo(blockBean)
} catch (e: Exception) {
e.printStackTrace()
}
}
/**
......
......@@ -11,6 +11,7 @@ import com.didichuxing.doraemonkit.kit.crash.CrashCaptureMainFragment
import com.didichuxing.doraemonkit.kit.dataclean.DataCleanFragment
import com.didichuxing.doraemonkit.kit.fileexplorer.FileExplorerFragment
import com.didichuxing.doraemonkit.kit.gpsmock.GpsMockFragment
import com.didichuxing.doraemonkit.kit.health.HealthFragment
import com.didichuxing.doraemonkit.kit.largepicture.LargePictureFragment
import com.didichuxing.doraemonkit.kit.network.ui.NetWorkMockFragment
import com.didichuxing.doraemonkit.kit.network.ui.NetWorkMonitorFragment
......@@ -66,6 +67,7 @@ open class UniversalActivity : BaseActivity() {
FragmentIndex.FRAGMENT_FILE_EXPLORER -> FileExplorerFragment::class.java
FragmentIndex.FRAGMENT_GPS_MOCK -> GpsMockFragment::class.java
FragmentIndex.FRAGMENT_CRASH -> CrashCaptureMainFragment::class.java
FragmentIndex.FRAGMENT_HEALTH -> HealthFragment::class.java
else -> null
}
if (fragmentClass == null) {
......
......@@ -2,14 +2,24 @@ package com.didichuxing.doraemonkit.kit.health
import com.blankj.utilcode.util.AppUtils
import com.blankj.utilcode.util.DeviceUtils
import com.blankj.utilcode.util.GsonUtils
import com.blankj.utilcode.util.TimeUtils
import com.didichuxing.doraemonkit.BuildConfig
import com.didichuxing.doraemonkit.config.CrashCaptureConfig.isCrashCaptureOpen
import com.didichuxing.doraemonkit.constant.DokitConstant
import com.didichuxing.doraemonkit.constant.NetWorkConstant
import com.didichuxing.doraemonkit.kit.blockmonitor.core.BlockMonitorManager
import com.didichuxing.doraemonkit.kit.crash.CrashCaptureManager
import com.didichuxing.doraemonkit.kit.health.model.AppHealthInfo
import com.didichuxing.doraemonkit.kit.health.model.AppHealthInfo.BaseInfoBean
import com.didichuxing.doraemonkit.kit.health.model.AppHealthInfo.DataBean.*
import com.didichuxing.doraemonkit.kit.health.model.AppHealthInfo.DataBean.AppStartBean.LoadFuncBean
import com.didichuxing.doraemonkit.kit.health.model.AppHealthInfo.DataBean.PerformanceBean.ValuesBean
import com.didichuxing.doraemonkit.kit.network.NetworkManager
import com.didichuxing.doraemonkit.kit.performance.manager.PerformanceDataManager
import com.didichuxing.doraemonkit.okgo.DokitOkGo
import com.didichuxing.doraemonkit.okgo.callback.StringCallback
import com.didichuxing.doraemonkit.okgo.model.Response
import java.util.*
/**
......@@ -31,6 +41,30 @@ class AppHealthInfoUtil {
val INSTANCE = AppHealthInfoUtil()
}
/**
* 上传健康体检数据到服务器
*/
fun post(uploadAppHealthCallBack: UploadAppHealthCallback?) {
if (mAppHealthInfo == null) {
return
}
//线上地址:https://www.dokit.cn/healthCheck/addCheckData
//测试环境地址:http://dokit-test.intra.xiaojukeji.com/healthCheck/addCheckData
DokitOkGo.post<String>(NetWorkConstant.APP_HEALTH_URL)
.upJson(GsonUtils.toJson(mAppHealthInfo))
.execute(object : StringCallback() {
override fun onSuccess(response: Response<String>) {
uploadAppHealthCallBack?.onSuccess(response)
}
override fun onError(response: Response<String>) {
super.onError(response)
uploadAppHealthCallBack?.onError(response)
}
})
}
/**
* 设置基本信息
*
......@@ -251,7 +285,7 @@ class AppHealthInfoUtil {
* @return
*/
private val data: AppHealthInfo.DataBean?
private get() {
get() {
if (mAppHealthInfo!!.data == null) {
val dataBean = AppHealthInfo.DataBean()
dataBean.cpu = ArrayList()
......@@ -272,19 +306,48 @@ class AppHealthInfoUtil {
/**
* 开启健康体检监控
*/
fun start() {}
fun start() {
PerformanceDataManager.instance.init()
//帧率
PerformanceDataManager.instance.startMonitorFrameInfo()
//cpu
PerformanceDataManager.instance.startMonitorCPUInfo()
//内存
PerformanceDataManager.instance.startMonitorMemoryInfo()
//卡顿
BlockMonitorManager.instance.start()
//网络
NetworkManager.instance.startMonitor()
//crash 开关
isCrashCaptureOpen = true
CrashCaptureManager.instance.start()
}
/**
* 结束健康体检监控
*/
fun stop() {}
fun stop() {
//帧率
PerformanceDataManager.instance.stopMonitorFrameInfo()
//cpu
PerformanceDataManager.instance.stopMonitorCPUInfo()
//内存
PerformanceDataManager.instance.stopMonitorMemoryInfo()
//卡顿
BlockMonitorManager.instance.stop()
//网络
NetworkManager.instance.stopMonitor()
//crash 开关
isCrashCaptureOpen = false
CrashCaptureManager.instance.stop()
}
/**
* list 去掉最大值和最小值 并重新 排序
*/
private fun sortValue(valuesBeans: List<ValuesBean>): List<ValuesBean> {
val newValuesBeans: MutableList<ValuesBean> = ArrayList(valuesBeans)
Collections.sort(newValuesBeans) { pre, next ->
newValuesBeans.sortWith(Comparator { pre, next ->
val preValue = pre.value.toFloat()
val nextValue = next.value.toFloat()
if (preValue < nextValue) {
......@@ -292,10 +355,10 @@ class AppHealthInfoUtil {
} else {
1
}
}
})
newValuesBeans.removeAt(0)
newValuesBeans.removeAt(newValuesBeans.size - 1)
Collections.sort(newValuesBeans) { pre, next ->
newValuesBeans.sortWith(Comparator { pre, next ->
val preValue = pre.time.toLong()
val nextValue = next.time.toLong()
if (preValue < nextValue) {
......@@ -303,7 +366,7 @@ class AppHealthInfoUtil {
} else {
1
}
}
})
return newValuesBeans
}
......@@ -316,6 +379,52 @@ class AppHealthInfoUtil {
}
}
/**
* 获取当前最后一个PerformanceInfo信息
*
* @return PerformanceBean
*/
fun getLastPerformanceInfo(performanceType: Int): PerformanceBean? {
var performanceBeans: List<PerformanceBean?>? = null
when (performanceType) {
PerformanceDataManager.PERFORMANCE_TYPE_CPU -> {
performanceBeans = data?.cpu
}
PerformanceDataManager.PERFORMANCE_TYPE_MEMORY -> {
performanceBeans = data?.memory
}
PerformanceDataManager.PERFORMANCE_TYPE_FPS -> {
performanceBeans = data?.fps
}
}
return if (performanceBeans == null || performanceBeans.isEmpty()) {
null
} else performanceBeans[performanceBeans.size - 1]
}
/**
* 移除满足条件的最后一个PerformanceInfo信息
*
* @return PerformanceBean
*/
fun removeLastPerformanceInfo(performanceType: Int) {
var performanceBeans: MutableList<PerformanceBean>? = null
when (performanceType) {
PerformanceDataManager.PERFORMANCE_TYPE_CPU -> {
performanceBeans = data?.cpu
}
PerformanceDataManager.PERFORMANCE_TYPE_MEMORY -> {
performanceBeans = data?.memory
}
PerformanceDataManager.PERFORMANCE_TYPE_FPS -> {
performanceBeans = data?.fps
}
}
if (performanceBeans != null && performanceBeans.isNotEmpty()) {
performanceBeans.removeAt(performanceBeans.size - 1)
}
}
companion object {
private const val TAG = "AppHealthInfoUtil"
val instance: AppHealthInfoUtil
......
package com.didichuxing.doraemonkit.kit.health
import android.content.Context
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import android.widget.TextView
import com.blankj.utilcode.util.ConvertUtils
import com.didichuxing.doraemonkit.R
import com.didichuxing.doraemonkit.kit.core.AbsDokitView
import com.didichuxing.doraemonkit.kit.core.DokitViewLayoutParams
import com.didichuxing.doraemonkit.kit.core.DokitViewManager
import kotlinx.coroutines.*
/**
* ================================================
* 作 者:jint(金台)
* 版 本:1.0
* 创建日期:2019-12-27-17:59
* 描 述:页面倒计时浮标 功能待实现
* 修订历史:
* 描 述:页面倒计时浮标
* 修订历史:update by pengyushan 2020-07-07
* ================================================
*/
class CountDownDokitView : AbsDokitView() {
private val TAG = "CountDownDokitView"
private var mNum: TextView? = null
private var countdownJob: Job? = null
private val COUNT_DOWN_TOTAL = 10
private val COUNT_DOWN_INTERVAL = 1700L
private var countTime = COUNT_DOWN_TOTAL
override fun onCreate(context: Context) {
}
override fun onCreateView(context: Context, rootView: FrameLayout): View {
return rootView
return LayoutInflater.from(context).inflate(R.layout.dk_float_count_down,rootView,false)
}
override fun onViewCreated(rootView: FrameLayout) {
mNum = findViewById(R.id.tv_number)
countdownJob?.cancel()
countdownJob = GlobalScope.launch(Dispatchers.Main) {
while (true){
delay(COUNT_DOWN_INTERVAL)
countTime--
mNum!!.text = "$countTime"
if (countTime ==0){
if (isNormalMode) {
DokitViewManager.instance.detach(activity, this@CountDownDokitView)
} else {
DokitViewManager.instance.detach(this@CountDownDokitView)
}
}
}
}
}
override fun initDokitViewLayoutParams(params: DokitViewLayoutParams?) {
params?.height = DokitViewLayoutParams.WRAP_CONTENT
params?.width = DokitViewLayoutParams.WRAP_CONTENT
params?.gravity = Gravity.TOP or Gravity.LEFT
params?.x = ConvertUtils.dp2px(280f)
params?.y = ConvertUtils.dp2px(25f)
}
/**
......@@ -35,4 +69,9 @@ class CountDownDokitView : AbsDokitView() {
fun resetTime() {
}
override fun onDestroy() {
super.onDestroy()
countdownJob?.cancel()
}
}
\ No newline at end of file
package com.didichuxing.doraemonkit.kit.health
import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentPagerAdapter
import com.didichuxing.doraemonkit.R
import com.didichuxing.doraemonkit.kit.core.BaseFragment
import com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar
import com.didichuxing.doraemonkit.widget.viewpager.VerticalViewPager
import java.util.*
/**
* 健康体检fragment
* @author pengyushan
*/
class HealthFragment : BaseFragment() {
var mVerticalViewPager: VerticalViewPager? = null
private var mHomeTitleBar: HomeTitleBar? = null
var mFragments: MutableList<Fragment> = ArrayList()
private var mFragmentPagerAdapter: FragmentPagerAdapter? = null
override fun onRequestLayout(): Int {
return R.layout.dk_fragment_health
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (activity == null) {
return
}
initView()
}
private fun initView() {
mFragments.clear()
mFragments.add(HealthFragmentFirst())
mFragments.add(HealthFragmentSecond())
mHomeTitleBar = findViewById(R.id.title_bar)
mHomeTitleBar!!.setListener(object : HomeTitleBar.OnTitleBarClickListener {
override fun onRightClick() {
finish()
}
})
mVerticalViewPager = findViewById(R.id.view_pager)
mFragmentPagerAdapter = object : FragmentPagerAdapter(childFragmentManager) {
override fun getItem(position: Int): Fragment {
return mFragments[position]
}
override fun getCount(): Int {
return mFragments.size
}
}
mVerticalViewPager!!.adapter = mFragmentPagerAdapter
}
/**
* 滑动到顶部
*/
fun scroll2theTop() {
if (mVerticalViewPager != null && mFragmentPagerAdapter != null) {
mVerticalViewPager!!.setCurrentItem(0, true)
}
}
}
\ No newline at end of file
package com.didichuxing.doraemonkit.kit.health
import android.os.Bundle
import android.os.Process
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import com.blankj.utilcode.util.AppUtils
import com.blankj.utilcode.util.ToastUtils
import com.didichuxing.doraemonkit.R
import com.didichuxing.doraemonkit.config.GlobalConfig.appHealth
import com.didichuxing.doraemonkit.constant.DokitConstant
import com.didichuxing.doraemonkit.kit.core.BaseFragment
import com.didichuxing.doraemonkit.kit.health.AppHealthInfoUtil
import com.didichuxing.doraemonkit.okgo.model.Response
import com.didichuxing.doraemonkit.util.DokitUtil
import com.didichuxing.doraemonkit.util.LogHelper.e
import com.didichuxing.doraemonkit.util.LogHelper.i
import com.didichuxing.doraemonkit.widget.dialog.DialogListener
import com.didichuxing.doraemonkit.widget.dialog.DialogProvider
import com.didichuxing.doraemonkit.widget.dialog.UniversalDialogFragment
import kotlin.system.exitProcess
/**
* 健康体检 第一页
* @author pengyushan
*/
class HealthFragmentFirst : BaseFragment() {
var mTitle: TextView? = null
var mController: ImageView? = null
var mUserInfoDialogProvider: UserInfoDialogProvider? = null
override fun onRequestLayout(): Int {
return R.layout.dk_fragment_health_child_one
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (activity == null) {
return
}
mTitle = findViewById(R.id.tv_title)
mController = findViewById(R.id.iv_btn)
if (DokitConstant.APP_HEALTH_RUNNING) {
mTitle!!.visibility = View.VISIBLE
mController!!.setImageResource(R.mipmap.dk_health_stop)
} else {
mTitle!!.visibility = View.INVISIBLE
mController!!.setImageResource(R.mipmap.dk_health_start)
}
mUserInfoDialogProvider = UserInfoDialogProvider(null, object : DialogListener {
override fun onPositive(): Boolean {
return if (mUserInfoDialogProvider != null) {
//上传健康体检数据
mUserInfoDialogProvider!!.uploadAppHealthInfo(object : UploadAppHealthCallback {
override fun onSuccess(response: Response<String>?) {
i(TAG, "上传成功===>" + response!!.body())
ToastUtils.showShort(DokitUtil.getString(R.string.dk_health_upload_successed))
//重置状态
appHealth = false
DokitConstant.APP_HEALTH_RUNNING = false
mTitle!!.visibility = View.INVISIBLE
mController!!.setImageResource(R.mipmap.dk_health_start)
//关闭健康体检监控
AppHealthInfoUtil.instance.stop()
AppHealthInfoUtil.instance.release()
}
override fun onError(response: Response<String>?) {
e(TAG, "error response===>" + response!!.message())
ToastUtils.showShort(DokitUtil.getString(R.string.dk_health_upload_failed))
}
})
} else true
}
override fun onNegative(): Boolean {
return true
}
override fun onCancel(): Boolean {
ToastUtils.showShort(DokitUtil.getString(R.string.dk_health_upload_droped))
//重置状态
appHealth = false
DokitConstant.APP_HEALTH_RUNNING = false
mTitle!!.visibility = View.INVISIBLE
mController!!.setImageResource(R.mipmap.dk_health_start)
//关闭健康体检监控
AppHealthInfoUtil.instance.stop()
AppHealthInfoUtil.instance.release()
return true
}
})
mController!!.setOnClickListener(View.OnClickListener {
if (activity == null) {
return@OnClickListener
}
//当前处于健康体检状态
if (DokitConstant.APP_HEALTH_RUNNING) {
if (mUserInfoDialogProvider != null) {
showDialog(mUserInfoDialogProvider!!)
}
} else {
AlertDialog.Builder(activity!!)
.setTitle(DokitUtil.getString(R.string.dk_health_upload_title))
.setMessage(DokitUtil.getString(R.string.dk_health_upload_message))
.setCancelable(false)
.setPositiveButton("ok") { dialog, which ->
dialog.dismiss()
if (mController != null) {
ToastUtils.showShort(DokitUtil.getString(R.string.dk_health_funcation_start))
appHealth = true
DokitConstant.APP_HEALTH_RUNNING = true
//重启app
mController!!.postDelayed({
AppUtils.relaunchApp()
Process.killProcess(Process.myPid())
exitProcess(1)
}, 2000)
}
}
.setNegativeButton("cancel") { dialog, _ -> dialog.dismiss() }.show()
}
})
}
override fun showDialog(provider: DialogProvider<*>) {
val dialog = UniversalDialogFragment(provider)
provider.host = dialog
provider.show(childFragmentManager)
}
}
\ No newline at end of file
package com.didichuxing.doraemonkit.kit.health
import android.os.Bundle
import android.view.View
import android.widget.LinearLayout
import com.didichuxing.doraemonkit.R
import com.didichuxing.doraemonkit.kit.core.BaseFragment
/**
* 健康体检 第二页
* @author pengyushan
*/
class HealthFragmentSecond : BaseFragment() {
var mLlBackTop: LinearLayout? = null
override fun onRequestLayout(): Int {
return R.layout.dk_fragment_health_child_two
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mLlBackTop = findViewById(R.id.ll_back_top)
mLlBackTop!!.setOnClickListener {
val parentFragment = parentFragment
if (parentFragment is HealthFragment) {
parentFragment.scroll2theTop()
}
}
}
}
\ No newline at end of file
......@@ -8,11 +8,12 @@ import com.didichuxing.doraemonkit.aop.DokitPluginConfig
import com.didichuxing.doraemonkit.constant.DokitConstant
import com.didichuxing.doraemonkit.constant.FragmentIndex
import com.didichuxing.doraemonkit.kit.AbstractKit
import com.didichuxing.doraemonkit.util.DokitUtil
import com.didichuxing.doraemonkit.util.DokitUtil.getString
/**
* @author jintai
* @desc: 一键体检kit
* update by pengyushan 2020-07-07
*/
class HealthKit : AbstractKit() {
override val name: Int
......@@ -22,12 +23,30 @@ class HealthKit : AbstractKit() {
get() = R.mipmap.dk_health
override fun onClick(context: Context?) {
kotlinTip()
if (!DokitPluginConfig.SWITCH_DOKIT_PLUGIN) {
ToastUtils.showShort(getString(R.string.dk_plugin_close_tip))
return
}
if (!DokitPluginConfig.SWITCH_NETWORK) {
ToastUtils.showShort(getString(R.string.dk_plugin_network_close_tip))
return
}
if (!DokitPluginConfig.SWITCH_METHOD) {
ToastUtils.showShort(getString(R.string.dk_plugin_method_close_tip))
return
}
if (TextUtils.isEmpty(DokitConstant.PRODUCT_ID)) {
ToastUtils.showShort(getString(R.string.dk_platform_tip))
return
}
startUniversalActivity(context, FragmentIndex.FRAGMENT_HEALTH)
}
override fun onAppInit(context: Context?) {}
override val isInnerKit: Boolean
get() = true
override val isInnerKit = true
override fun innerKitId(): String {
return "dokit_sdk_platform_ck_health"
......
package com.didichuxing.doraemonkit.kit.health
import com.didichuxing.doraemonkit.okgo.model.Response
/**
* ================================================
* 作 者:jint(金台)
* 版 本:1.0
* 创建日期:2020-01-07-15:42
* 描 述:
* 修订历史:update pengyushan 2020-07-06 转化为kotlin
* ================================================
*/
interface UploadAppHealthCallback {
/**
* @param response
*/
fun onSuccess(response: Response<String>?)
/**
* @param response
*/
fun onError(response: Response<String>?)
}
\ No newline at end of file
package com.didichuxing.doraemonkit.kit.health
import android.text.TextUtils
import android.view.View
import android.widget.EditText
import android.widget.TextView
import com.blankj.utilcode.util.ToastUtils
import com.didichuxing.doraemonkit.R
import com.didichuxing.doraemonkit.widget.dialog.DialogListener
import com.didichuxing.doraemonkit.widget.dialog.DialogProvider
/**
* Created by jint on 2019/4/12
* update by pengyushan 2020-07-06
* 完善健康体检用户信息dialog
* @author jintai
*/
class UserInfoDialogProvider internal constructor(data: Any?, listener: DialogListener?) : DialogProvider<Any?>(data, listener) {
private var mPositive: TextView? = null
private var mNegative: TextView? = null
private var mClose: TextView? = null
private var mCaseName: EditText? = null
private var mUserName: EditText? = null
override val layoutId: Int
get() = R.layout.dk_dialog_userinfo
override fun findViews(view: View?) {
view?.let {
mPositive = view.findViewById(R.id.positive)
mNegative = view.findViewById(R.id.negative)
mClose = view.findViewById(R.id.close)
mCaseName = view.findViewById(R.id.edit_case_name)
mUserName = view.findViewById(R.id.edit_user_name)
}
}
override fun bindData(data: Any?) {}
override val positiveView
get() = mPositive
override val negativeView
get() = mNegative
override val cancelView
get() = mClose
/**
* 上传健康体检数据
*/
fun uploadAppHealthInfo(uploadAppHealthCallBack: UploadAppHealthCallback?): Boolean {
if (!userInfoCheck()) {
ToastUtils.showShort("请填写测试用例和测试人")
return false
}
val caseName = mCaseName!!.text.toString()
val userName = mUserName!!.text.toString()
AppHealthInfoUtil.instance.setBaseInfo(caseName, userName)
//上传数据
AppHealthInfoUtil.instance.post(uploadAppHealthCallBack)
return true
}
/**
* 检查用户数据
*/
private fun userInfoCheck(): Boolean {
if (mCaseName == null || TextUtils.isEmpty(mCaseName!!.text.toString())) {
return false
}
return !(mUserName == null || TextUtils.isEmpty(mUserName!!.text.toString()))
}
override val isCancellable: Boolean
get() = false
}
\ No newline at end of file
......@@ -109,7 +109,7 @@ class AppHealthInfo {
@Expose
var pageKey: String? = null
var page: String? = null
var values: List<ValuesBean>? = null
var values: MutableList<ValuesBean>? = null
/**
* cpu、内存、fps 共享的ValueBean
......
......@@ -6,10 +6,15 @@ import android.os.*
import android.text.TextUtils
import android.view.Choreographer
import androidx.annotation.RequiresApi
import com.blankj.utilcode.util.ActivityUtils
import com.blankj.utilcode.util.AppUtils
import com.blankj.utilcode.util.TimeUtils
import com.didichuxing.doraemonkit.DoraemonKit
import com.didichuxing.doraemonkit.config.DokitMemoryConfig
import com.didichuxing.doraemonkit.constant.DokitConstant
import com.didichuxing.doraemonkit.kit.health.AppHealthInfoUtil
import com.didichuxing.doraemonkit.kit.health.model.AppHealthInfo.DataBean.PerformanceBean
import com.didichuxing.doraemonkit.kit.health.model.AppHealthInfo.DataBean.PerformanceBean.ValuesBean
import java.io.*
import java.lang.Process
......@@ -200,7 +205,7 @@ class PerformanceDataManager private constructor() {
fun startMonitorCPUInfo() {
DokitMemoryConfig.CPU_STATUS = true
mNormalHandler!!.sendEmptyMessageDelayed(MSG_CPU, NORMAL_SAMPLING_TIME.toLong())
mNormalHandler?.sendEmptyMessageDelayed(MSG_CPU, NORMAL_SAMPLING_TIME.toLong())
}
fun destroy() {
......@@ -216,7 +221,7 @@ class PerformanceDataManager private constructor() {
fun stopMonitorCPUInfo() {
DokitMemoryConfig.CPU_STATUS = false
mNormalHandler!!.removeMessages(MSG_CPU)
mNormalHandler?.removeMessages(MSG_CPU)
}
fun startMonitorMemoryInfo() {
......@@ -224,12 +229,12 @@ class PerformanceDataManager private constructor() {
if (maxMemory == 0f) {
maxMemory = mActivityManager.memoryClass.toFloat()
}
mNormalHandler!!.sendEmptyMessageDelayed(MSG_MEMORY, NORMAL_SAMPLING_TIME.toLong())
mNormalHandler?.sendEmptyMessageDelayed(MSG_MEMORY, NORMAL_SAMPLING_TIME.toLong())
}
fun stopMonitorMemoryInfo() {
DokitMemoryConfig.RAM_STATUS = false
mNormalHandler!!.removeMessages(MSG_MEMORY)
mNormalHandler?.removeMessages(MSG_MEMORY)
}
/**
......@@ -421,7 +426,72 @@ class PerformanceDataManager private constructor() {
*/
@Synchronized
private fun addPerformanceDataInAppHealth(performanceValue: Float, performanceType: Int) {
//todo 需要完善保存到监控体检
try {
val lastPerformanceInfo: PerformanceBean? = AppHealthInfoUtil.instance.getLastPerformanceInfo(performanceType)
//第一次启动
if (lastPerformanceInfo == null) {
val performanceBean = PerformanceBean()
val valuesBeans: MutableList<ValuesBean> = ArrayList()
valuesBeans.add(ValuesBean("" + TimeUtils.getNowMills(), "" + performanceValue))
performanceBean.page = ActivityUtils.getTopActivity().javaClass.canonicalName
performanceBean.pageKey = ActivityUtils.getTopActivity().toString()
performanceBean.values = valuesBeans
when (performanceType) {
PERFORMANCE_TYPE_CPU -> {
AppHealthInfoUtil.instance.addCPUInfo(performanceBean)
}
PERFORMANCE_TYPE_MEMORY -> {
AppHealthInfoUtil.instance.addMemoryInfo(performanceBean)
}
else -> {
AppHealthInfoUtil.instance.addFPSInfo(performanceBean)
}
}
} else { //不是第一次启动
val lastPageKey = lastPerformanceInfo.pageKey
//同一个页面
if (lastPageKey == ActivityUtils.getTopActivity().toString()) {
val valuesBeans = lastPerformanceInfo.values
val valueSize = valuesBeans!!.size
//判断是否需要上传数据
//采集的点数必须在10~40之间 其中cpu 、 内存必须在20~40 因为fps 1s中采集一次
if (valueSize < 40) {
valuesBeans.add(ValuesBean("" + TimeUtils.getNowMills(), "" + performanceValue))
}
} else { //页面已发生变化
val lastValuesBeans = lastPerformanceInfo.values
val valueSize = lastValuesBeans!!.size
//先丢弃上一个页面的数据
if (performanceType == PERFORMANCE_TYPE_CPU && valueSize < 20) {
AppHealthInfoUtil.instance.removeLastPerformanceInfo(performanceType)
} else if (performanceType == PERFORMANCE_TYPE_MEMORY && valueSize < 20) {
AppHealthInfoUtil.instance.removeLastPerformanceInfo(performanceType)
} else if (performanceType == PERFORMANCE_TYPE_FPS && valueSize < 10) {
AppHealthInfoUtil.instance.removeLastPerformanceInfo(performanceType)
}
val performanceBean = PerformanceBean()
val newValuesBeans: MutableList<ValuesBean> = ArrayList()
newValuesBeans.add(ValuesBean("" + TimeUtils.getNowMills(), "" + performanceValue))
performanceBean.page = ActivityUtils.getTopActivity().javaClass.canonicalName
performanceBean.pageKey = ActivityUtils.getTopActivity().toString()
performanceBean.values = newValuesBeans
when (performanceType) {
PERFORMANCE_TYPE_CPU -> {
AppHealthInfoUtil.instance.addCPUInfo(performanceBean)
}
PERFORMANCE_TYPE_MEMORY -> {
AppHealthInfoUtil.instance.addMemoryInfo(performanceBean)
}
else -> {
AppHealthInfoUtil.instance.addFPSInfo(performanceBean)
}
}
}
}
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
}
companion object {
......
......@@ -3,7 +3,11 @@ package com.didichuxing.doraemonkit.kit.timecounter.couter
import android.app.Activity
import android.os.SystemClock
import com.blankj.utilcode.util.ActivityUtils
import com.didichuxing.doraemonkit.constant.DokitConstant
import com.didichuxing.doraemonkit.kit.core.DokitViewManager
import com.didichuxing.doraemonkit.kit.core.UniversalActivity
import com.didichuxing.doraemonkit.kit.health.AppHealthInfoUtil
import com.didichuxing.doraemonkit.kit.health.model.AppHealthInfo
import com.didichuxing.doraemonkit.kit.timecounter.TimeCounterView
import com.didichuxing.doraemonkit.kit.timecounter.bean.ActivityTimeCounterRecord
......@@ -94,16 +98,15 @@ internal class ActivityCounter {
private fun addToAppHealth(activityRecord: ActivityTimeCounterRecord) {
try {
//TODO 将Activity 打开耗时 添加到AppHealth 中
//if (DokitConstant.APP_HEALTH_RUNNING) {
// if (ActivityUtils.getTopActivity().javaClass.canonicalName != UniversalActivity::class.java.canonicalName) {
// val pageLoadBean = AppHealthInfo.DataBean.PageLoadBean()
// pageLoadBean.page = ActivityUtils.getTopActivity().javaClass.canonicalName
// pageLoadBean.time = activityRecord.totalCost().toString()
// pageLoadBean.trace = activityRecord.title()
// AppHealthInfoUtil.instance.addPageLoadInfo(pageLoadBean)
// }
//}
if (DokitConstant.APP_HEALTH_RUNNING) {
if (ActivityUtils.getTopActivity().javaClass.canonicalName != UniversalActivity::class.java.canonicalName) {
val pageLoadBean = AppHealthInfo.DataBean.PageLoadBean()
pageLoadBean.page = ActivityUtils.getTopActivity().javaClass.canonicalName
pageLoadBean.time = activityRecord.totalCost().toString()
pageLoadBean.trace = activityRecord.title()
AppHealthInfoUtil.instance.addPageLoadInfo(pageLoadBean)
}
}
} catch (e: Exception) {
e.printStackTrace()
}
......
......@@ -95,8 +95,6 @@ abstract class DialogProvider<T>(protected val mData: T, private var mDialogList
open val negativeView: View?
get() = null
val cancelView: View?
open val cancelView: View?
get() = null
}
\ No newline at end of file
package com.didichuxing.doraemonkit.widget.viewpager
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import androidx.viewpager.widget.ViewPager
import com.didichuxing.doraemonkit.widget.viewpager.transfer.DefaultTransformer
/**
* ================================================
* 作 者:jint(金台)
* 版 本:1.0
* 创建日期:2019-12-27-14:53
* 描 述:
* 修订历史:update by pengyushan 2020-07-06
* ================================================
*/
class VerticalViewPager @JvmOverloads constructor(context: Context?, attrs: AttributeSet? = null) : ViewPager(context!!, attrs) {
private fun swapTouchEvent(event: MotionEvent): MotionEvent {
val width = width.toFloat()
val height = height.toFloat()
val swappedX = event.y / height * width
val swappedY = event.x / width * height
event.setLocation(swappedX, swappedY)
return event
}
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
val intercept = super.onInterceptTouchEvent(swapTouchEvent(event))
swapTouchEvent(event)
return intercept
}
override fun onTouchEvent(ev: MotionEvent): Boolean {
return super.onTouchEvent(swapTouchEvent(ev))
}
init {
setPageTransformer(false, DefaultTransformer())
}
}
\ No newline at end of file
package com.didichuxing.doraemonkit.widget.viewpager.transfer
import android.view.View
import androidx.viewpager.widget.ViewPager.PageTransformer
/**
* ================================================
* 作 者:jint(金台)
* 版 本:1.0
* 创建日期:2019-12-27-14:55
* 描 述:
* 修订历史:update by pengyushan 2020-07-06
* ================================================
*/
class DefaultTransformer : PageTransformer {
override fun transformPage(view: View, position: Float) {
var alpha = 0f
if (position in 0.0..1.0) {
alpha = 1 - position
} else if (-1 < position && position < 0) {
alpha = position + 1
}
view.alpha = alpha
view.translationX = view.width * -position
val yPosition = position * view.height
view.translationY = yPosition
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="280dp"
android:layout_height="wrap_content"
android:background="@color/dk_color_FFFFFF"
android:orientation="vertical">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="30dp"
android:text="@string/dk_health_dialog_title"
android:textColor="@color/dk_color_333333"
android:textSize="21sp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:orientation="vertical"
android:paddingLeft="20dp"
android:paddingRight="20dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dk_health_dialog_test_name"
android:textColor="@color/dk_color_BEBEBE"
android:textSize="14sp" />
<EditText
android:id="@+id/edit_case_name"
android:layout_width="match_parent"
android:layout_height="@dimen/dk_dp_40"
android:layout_marginTop="@dimen/dk_dp_5"
android:background="@drawable/dk_health_edittext_style"
android:hint="@string/dk_health_dialog_test_name_hint"
android:paddingLeft="6dp"
android:textColor="@color/dk_color_333333"
android:textSize="14sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dk_dp_16"
android:text="@string/dk_health_dialog_test_person"
android:textColor="@color/dk_color_BEBEBE"
android:textSize="14sp" />
<EditText
android:id="@+id/edit_user_name"
android:layout_width="match_parent"
android:layout_height="@dimen/dk_dp_40"
android:layout_marginTop="@dimen/dk_dp_5"
android:background="@drawable/dk_health_edittext_style"
android:hint="@string/dk_health_dialog_test_person_hint"
android:paddingLeft="6dp"
android:textColor="@color/dk_color_333333"
android:textSize="14sp" />
</LinearLayout>
<View
style="@style/DK.Divider"
android:layout_marginTop="20dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:orientation="horizontal">
<TextView
android:id="@+id/negative"
style="@style/DK.Text"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:background="@drawable/dk_dialog_button_background"
android:text="@string/dk_cancel" />
<View style="@style/DK.Divider.Vertical" />
<TextView
android:id="@+id/close"
style="@style/DK.Text"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:background="@drawable/dk_dialog_button_background"
android:text="@string/dk_discard" />
<View style="@style/DK.Divider.Vertical" />
<TextView
android:id="@+id/positive"
style="@style/DK.Text.Blue"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:background="@drawable/dk_dialog_button_background"
android:text="@string/dk_post" />
</LinearLayout>
</LinearLayout>
</FrameLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ProgressBar
android:id="@+id/circle_progress_bar"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_gravity="center_horizontal"
android:indeterminateDrawable="@drawable/dk_progressbar_style" />
<TextView
android:id="@+id/tv_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="10"
android:textColor="#3CBCA3"
android:textSize="28sp"
android:textStyle="bold" />
</FrameLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/dk_color_FFFFFF"
android:orientation="vertical">
<com.didichuxing.doraemonkit.widget.titlebar.HomeTitleBar
android:id="@+id/title_bar"
android:layout_width="match_parent"
android:layout_height="89dp"
app:dkIcon="@mipmap/dk_close_icon_big"
app:dkTitle="@string/dk_kit_health" />
<com.didichuxing.doraemonkit.widget.viewpager.VerticalViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/title_bar" />
</RelativeLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/dk_color_FFFFFF"
android:orientation="vertical">
<FrameLayout
android:layout_width="260dp"
android:layout_height="435dp"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dp">
<ImageView
android:layout_width="260dp"
android:layout_height="match_parent"
android:src="@mipmap/dk_health_bg" />
<ImageView
android:id="@+id/iv_btn"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center_horizontal|bottom"
android:layout_marginBottom="80dp"
android:layout_marginLeft="3dp"
android:src="@mipmap/dk_health_start" />
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="80dp"
android:text="@string/dk_health_funcation_running"
android:textColor="#27BCB7"
android:textSize="18sp" />
</FrameLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="20dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:text="@string/dk_health_step_index"
android:textColor="#27BCB7"
android:textSize="16sp" />
<ImageView
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center_horizontal"
android:src="@mipmap/dk_health_next_page" />
</LinearLayout>
</RelativeLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/dk_color_FFFFFF"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:orientation="vertical"
android:paddingLeft="16dp"
android:paddingRight="16dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:orientation="vertical">
<TextView
android:layout_width="80dp"
android:layout_height="36dp"
android:background="@mipmap/dk_health_title_bg"
android:gravity="center"
android:text="@string/dk_health_title_step1"
android:textColor="@color/dk_color_FFFFFF"
android:textSize="16sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/dk_health_step1"
android:textColor="@color/dk_color_333333"
android:textSize="14sp" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:orientation="vertical">
<TextView
android:layout_width="80dp"
android:layout_height="36dp"
android:background="@mipmap/dk_health_title_bg"
android:gravity="center"
android:text="@string/dk_health_title_step2"
android:textColor="@color/dk_color_FFFFFF"
android:textSize="16sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/dk_health_step2"
android:textColor="@color/dk_color_333333"
android:textSize="14sp" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:orientation="vertical">
<TextView
android:layout_width="80dp"
android:layout_height="36dp"
android:background="@mipmap/dk_health_title_bg"
android:gravity="center"
android:text="@string/dk_health_title_step3"
android:textColor="@color/dk_color_FFFFFF"
android:textSize="16sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/dk_health_step2"
android:textColor="@color/dk_color_333333"
android:textSize="14sp" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:orientation="vertical">
<TextView
android:layout_width="80dp"
android:layout_height="36dp"
android:background="@mipmap/dk_health_title_bg"
android:gravity="center"
android:text="@string/dk_health_title_step4"
android:textColor="@color/dk_color_FFFFFF"
android:textSize="16sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:textColor="@color/dk_color_333333"
android:text="@string/dk_health_step4"
android:textSize="14sp" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/ll_back_top"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="20dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:text="@string/dk_health_back_top"
android:textColor="#27BCB7"
android:textSize="16sp" />
<ImageView
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center_horizontal"
android:rotation="180"
android:src="@mipmap/dk_health_next_page" />
</LinearLayout>
</RelativeLayout>
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册