Android——开发支撑工具

1.介绍

  开发过程中往往会用到一些开发的工具,仅仅在Debug模式下使用。并不将该工具打入release包中

比如:
  • 1.查看设备信息
  • 2.查看构建时间
  • 3.查看项目版本
    ...

2.开始设计

2.1.创建新的Android Library

1.添加我们buildConfigField,在我们debug环境使用

buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
        //buildConfigField添加属性
        debug {
            /**
             * 参数1:字段类型
             * 参数2:字段名称
             * 参数3:返回值
             */
            buildConfigField("String","BUILD_TIME","\""+ buildTime() + "\"")
        }
    }

def buildTime(){
    //EE表示星期几
    //TimeZone表示时区
    return new Date().format("EE HH:mm:ss",TimeZone.getTimeZone("GMT+08:00"))
}

2.项目Rebuild后BuildConfig中就会出现我们添加的属性BUILD_TIME

public final class BuildConfig {
  ...
  // Fields from build type: debug
  public static final String BUILD_TIME = "星期二 16:10:26";
}
2.2.创建DebugTools类

作用:该类主要就是工具需要显示的功能,最终会通过反射获取该类方法的实现,并执行。
好处:每次需要新增一个功能,我们只需要添加一个方法即可。

/**
 * TODO:创建DebugTools工具类
 *   1.该类提供我们工具需要的一些功能方法。
 *   2.最终通过反射来获取该类中的所有方法,并执行方法内的方法体。
 *     注意:如果方法没有返回值,那么我们可以通过注解的方式来实现Tools上显示方法名称
 */
class DebugTools {
    /**
     * TODO:显示项目的构建版本号
     *   1.0.1
     */
    fun buildVersion():String{
        return "构建版本:${BuildConfig.VERSION_CODE}.${BuildConfig.VERSION_NAME}"
    }

    /**
     * TODO:显示项目的构建时间
     *  星期二 22:14:54
     */
    fun buildTime():String{
        //new date() 当我们在运行时拿到的时间。也就是这个包打出来的时间
        return "构建时间:${BuildConfig.BUILD_TIME}"
    }

    /**
     * TODO:显示项目的构建环境
     *   通过BuildConfig.Debug
     */
    fun buildEnvironment():String{
        return "构建环境:${if (BuildConfig.DEBUG) "测试环境" else "正式环境"}"
    }

    /**
     * TODO:降级Https为Http
     *   此时是需要具体的实现,并非是一个String类型的返回值,所以我们此时通过HiDebug注解来实现Tools上显示的名称。
     */
    @HiDebug(name = "一键开启Https降级",desc = "将继承Http,可以使用抓包工具明文抓包")
    fun degradeHttp(){
        //此时我们可以通过MMKV(https://github.com/Tencent/MMKV)来存储我们的baseUrl,将其修改为http的。如果需要重启应用生效,那么我们此时直接重启应用
        //...

        val content = AppGlobals.get()?.applicationContext?:return
        //获取需要启动应用的Intent的意图
        val intent = content.packageManager.getLaunchIntentForPackage(content.packageName)
        intent?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        content.startActivity(intent)
        //杀死当前进程,并主动启动新的启动页,完成重启动作。
        Process.killProcess(Process.myPid())
    }
}


/**
 * TODO:注解的作用
 *  @Target:表示作用于哪里(类,方法,属性等等)FUNCTION方法
 *  @Retention:作用于什么时候,RUNTIME运行时期
 */
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class HiDebug(val name:String,val desc:String = "")
2.3.创建实现类

作用:该类主要就是弹窗显示上方定义的DebugTools中的每一个方法,通过反射获取该类方法的实现,并执行。

class DebugToolDialogFragment :AppCompatDialogFragment(){

    //通过数据类进行遍历获取类中所有方法
    private val debugTools = arrayOf(DebugTools::class.java)

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        //获取我们试图的跟布局android.R.id.content,我们可以进行修改宽高大小。
        val parent = dialog?.window?.findViewById<ViewGroup>(android.R.id.content)?:container
        val view = inflater.inflate(R.layout.hi_debug_tool,parent,false)
        //指定弹窗的宽高
        dialog?.window?.setLayout(
            (HiDisplayUtil.getDisplayWidthInPx(view.context) * 0.7f).toInt(),
            WindowManager.LayoutParams.WRAP_CONTENT)
        //设置背景圆角
        dialog?.window?.setBackgroundDrawableResource(R.drawable.shape_hi_debug_tool)
        return view
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        //设置列表项分割线
        val itemDecoration = DividerItemDecoration(view.context,DividerItemDecoration.VERTICAL)
        itemDecoration.setDrawable(
            ContextCompat.getDrawable(
                view.context,
                R.drawable.shape_hi_debug_divider)!!)


        var functions = mutableListOf<DebugFunction>()
        //获取数组元素个数
        val size = debugTools.size  
        //遍历
        for (index in 0 until size){
            //获取所有类对象
            val claz = debugTools[index]
            //实例化数组每一个元素对象
            val target = claz.getConstructor().newInstance()
            //获取类中所有方法
            val declaredMethods= target.javaClass.declaredMethods
            for (method in declaredMethods){
                var title = ""
                var desc = ""
                var enable = false
                val annotation = method.getAnnotation(HiDebug::class.java)
                //包含@HiDebug注解的方法都可以被点击
                if (annotation!=null){
                    title = annotation.name
                    desc = annotation.desc
                    enable = true
                }else{
                    method.isAccessible = true   //开启访问权限
                    title = method.invoke(target) as String
                }
                val func = DebugFunction(title,desc,method,enable,target)
                functions.add(func)
            }
        }

        recycler_view.addItemDecoration(itemDecoration)
        recycler_view.layoutManager = LinearLayoutManager(context,LinearLayoutManager.VERTICAL,false)
        recycler_view.adapter = DebugToolAdapter(functions)
    }

    inner class DebugToolAdapter(val list:List<DebugFunction>): RecyclerView.Adapter<RecyclerView.ViewHolder>() {
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
            //创建item
            var itemView = layoutInflater.inflate(R.layout.hi_debug_tool_item,parent,false)
            return object :RecyclerView.ViewHolder(itemView){

            }
        }


        override fun getItemCount(): Int {
            return list.size
        }

        //绑定数据
        override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
            val debugFunction = list[position]
            val itemTitle = holder.itemView.findViewById<TextView>(R.id.item_title)
            val itemDesc = holder.itemView.findViewById<TextView>(R.id.item_desc)
            itemTitle.text = debugFunction.name
            if (TextUtils.isEmpty(debugFunction.desc)){
                itemDesc.visibility = View.GONE
            }else{
                itemDesc.visibility = View.VISIBLE
                itemDesc.text = debugFunction.desc
            }

            //是否可以点击
            if (debugFunction.enable){
                holder.itemView.setOnClickListener {
                    dismiss()
                    debugFunction.invoke()
                }
            }
        }
    }

    /**
     * name:方法名称
     * desc:方法描述
     * method:方法的实体
     * enable:是否可以点击,有些返回字符串不需要点击
     * target:方法所在类的对象
     */
    data class DebugFunction(
        val name:String,
        val desc:String,
        val method:Method,
        val enable:Boolean,
        val target:Any) {

        //点击事件,通过反射就执行该方法
        fun invoke() {
            method.invoke(target)
        }
    }
}

3.使用

1.我们在主模块中添加依赖

//只有在debug下才会打入apk当中(只用于debug中)
debugImplementation project(path: ":hidebugtools" )

2.可以在任意地方调用(我是通过手机音量键)

/**
     * 监听音量键按下
     */
    override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
        if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN){
            //显示弹窗,只有在Debug的情况下显示
            if (BuildConfig.DEBUG){
                //通过反射获取类
                try {
                    val clazz = Class.forName("com.yc.hidebugtool.DebugToolDialogFragment")
                    val target:DialogFragment = clazz.getConstructor().newInstance() as DialogFragment
                    target.show(supportFragmentManager,"debug_tool")
                }catch (e:ClassNotFoundException){
                    e.printStackTrace()
                }
                //正常使用
                try {
                    val target: DialogFragment = DebugToolDialogFragment()
                    target.show(supportFragmentManager, "debug_tool")
                }catch (e: ClassNotFoundException){
                    e.printStackTrace()
                }
            }
        }
        return super.onKeyDown(keyCode, event)
    }

3.总结

好处:

1.我们通过debugImplementation 只有在debug模式才会打入包中。
2.完全耦合,我们需要任意一种工具功能,我们只需要在DebugTools类中添加对应的方法,并实现。
github地址:https://github.com/HuiZaierr/DebugTools

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容