1. Andorid TV 开发之连接机顶盒及常用 adb 命令
国内的 Android TV 应用虽然是在 TV 上显示,但是实际上大都运行在各类机顶盒中(当然也可以运行在智能电视本身定制的 Android 系统中),这些机顶盒运行的都是各个厂商基于 Android 各个版本系统定制的 ROM,并不是 Google 原生的Android TV 系统。
开发手机 app 应用,手机开启调试模式,用数据线连上手机,就能对手机进行 debug 开发。然而,机顶盒并没有手机的 micro usb 接口,并不能连数据线进行直接调试(据说,用 USB to USB 数据线连接机顶盒,也无法识别机顶盒),这时需要使用 adb 命令连接和调试机顶盒。打开 Android Studio 的 Terminal 或者 cmd( windows )、终端( mac ),输入 adb 出现如下结果,证明 adb命令可以正常使用,否则参照 adb 错误“ 'adb' 不是内部或外部命令,也不是可运行的程序或批处理文件 ( windows ) 或 在 MAC OS X 安装 ADB (Android 调试桥)
( mac )开启 adb 命令。
如果没有提示任何错误信息,表示连接机顶盒成功,接着输入
adb devices 查看连接的机顶盒的状态信息。
如果提示 xxxxx refused connect 证明还未获取机顶盒的调试权限,需要和机顶盒厂家获取调试工具、开启调试功能的方法、调试端口等(许多机顶盒的厂商都留有调试后门),也可以 root 机顶盒的系统。
adb connect [ip]:[port] 连接机顶盒(默认端口为薯则雀5555)
adb devices 查看所有连接设备 名称、ip、端口已经数早状态( device 或 offline )
adb install [apk 安装包所在路径(如:d:\a.apk)] 将对应路径的 apk 安装包安装到机顶盒
adb install -r [apk 安装包所在路径(如:d:\a.apk)] 将对应路径的apk 安装包强制(覆盖)安装到机顶盒
adb -s [设备名称或设备IP:端口] install [apk 安装包所在路径(如:d:/a.apk)] 当 adb 连接多个设备时,将 apk 安装到指定设备中
adb uninstall [应用包名,例如:com.example.tmall] 卸载应用(通过包名指定)
adb shell 进入系统目录(通过 exit 退出)
adb shell am start -n [包名]/盯碧[包名+类名] 启动指定类名的 Activity
adb shell input text **** 在已经获取焦点的EditText中输入内容
adb shell ps 查看当前终端中的进程信息
adb shell mpsys activity activites 查看当前终端所以前后台Acitivity的堆栈
adb shell logcat > d:\log.txt 打印日志到本地文件(会打印缓存的日志,可能会有昨天等之前日志。windows下,按 ctrl+c 停止日志打印)
adb logcat *:D > d:\log.txt 过滤出 D 级别以上的日志,打印日志到本地文件( 在 macOS 下需要给 *:W 这样以 * 作为 tag 的参数加双引号,如 adb logcat "*:W" )
awesome-adb
Android ADB 命令大全(通过 ADB 命令查看 wifi 密码、MAC 地址、设备信息、操作文件、查看文件、日志信息、卸载、启动和安装 APK 等)
连接上机顶盒的一小步,是机顶盒开发的一大步。
2. Android移动应用中的焦点分析
简单一点理解,在移动应用中,焦点就是当前正在处理事件的位置。在手机应用中,最有可能用到焦点的就是EditText,如果同一个界面中有多个EditText,通常情况下同一时间只有一个能够输入内容,此时,这个EditText就获取了焦点。
在Android中,对焦点的设置分为两种情况,TouchMode和非TouchMode。现在的手机基本都是触摸屏,我们用手指触摸屏幕来操作Android应用时,处于TouchMode。除了TouchMode之外,还有非TouchMode,利用外接设备来操作早衫应用。比如键盘。使用Genymotion模拟器的时候,一个陆兄腔界面上有多个控件时,可以用电脑tab键来进行移动,被选中的控件会高亮显示,这时候就是非TouchMode,被选中的控件获得了焦点。
在手机应用中,用到焦点的时候并不多,但是TV应用中,需要用遥控器来操作选中控件,这时候就需要对焦点进行处理了。关于焦点,常用方法如下:
在View类中, isFocusable() 和 isFocusableInTouchMode() 获取到的结果都是false,也就是说,直接继承自View的控件是不能获取焦点的。我们常用控件中对这两个方法进行了改写,比如EditText,这两个方法都是true,而Button则只有 isFocusable() 返回true。这也就是为什么我们用tab键选取Button的时候能够高亮显示,而鼠标点击(模拟触控)的时候不能高亮显示的原因了。如果想在点击的时候也能高亮显示Button,需要手动设置 setFocusableInTouchMode(true) ,就可以了。
如果想对控件的焦点状态进行监听,需要设置 setOnFocusChangeListener() ,只要控件的焦点状态发生变化(获得或者失去焦点),都会调用 onFocusChange 方法
关于焦点的移动,默认的算法会寻找指定方向上最近的可以获取焦点的元素(非TouchMode)。另外在创建控件的时候,也可以指定寻找焦点的方向,设置nextFocusDown、nextFocusLeft、nextFocusRight 和 nextFocusUp的值为指定元素就可以了。看以下例子:
这里指定了上面的button向上寻找焦点时,下一个元素是id为bottom的元素,也就是说,上面的Button在获取了焦点之后,继续按向上键,系统会将焦点移动到id为bottom的元素上,而不是继续向上。
在开发手机应用的过程中,对焦点的处理并不多,它与事件是两个不同的体系,通常情况下焦点和事件是相互独立并不冲突。但是在Button的点击事件中会有一点问题。如果我们队一个button设置了 setFocusableInTouchMode(true) ,使他可以获取焦点,那么我们点击这个button的时候,第一次点击并不会执行 onClick() 方法,而是执行 onFocusChange() 。第二次点击的时候才会执行 onClick() 方法。看起来好像 onFocusChange() 消耗了点击事件,实际上并不是的。
这个问题我们看一下源码就清楚了:
onClick() 方法是在onTouchEvent的ACTION_UP里调用的,看一下View的onTouchEvent方法:
可以看到,只有当focusTaken为false的时候才会执行onClick,focusTaken的值默认是false的,但是在 isFocusable() && isFocusableInTouchMode() && !isFocused() 为true的时候,会去 requestFocus 获取焦点,并将值赋给focusTaken。
关键在于 isFocused() ,如果当前Button没尘码有获取焦点, isFocused() 返回false, !isFocused() 值为ture,Button就会去获取焦点,从而导致 focusTaken 为true, onClick 方法就不会执行了,只有Button已经获取了焦点的时候才会执行onClick方法。
3. Android TV 焦点原理源码解析
相信很多刚接触AndroidTV开发的开发者,都会被各种焦点问题给折磨的不行。不管是学技术还是学习其他知识,都要学习和理解其中原理,碰到问题我们才能得心应手。下面就来探一探Android的焦点分发的过程。
Android焦点事件的分发是从ViewRootImpl的processKeyEvent开始的,源码如下:
源码比较长,下面我就慢慢来讲解一下具体的每一个细节。
dispatchKeyEvent方法返回true代表焦点事件被消费了。
ViewGroup的dispatchKeyEvent()方法的源码如下:
(2)ViewGroup的dispatchKeyEvent执行流程
(3)下面再来瞧瞧view的dispatchKeyEvent方法的具体的执行过程
惊奇的发现执行了onKeyListener中的onKey方法,如果onKey方法返回true,那么dispatchKeyEvent方法也会返回true
可以得出结论:如果想要修改ViewGroup焦点事件的分发,可以这么干:
注意:实际开发中,理论上所有焦点问题都可以通过给dispatchKeyEvent方法增加监听来来拦截来控制。
(1)dispatchKeyEvent方法返回false后,先得到按键的方向direction值,这个值是一个int类型参数。这个direction值是后面来进行焦点查找的。
(2)接着会调用DecorView的findFocus()方法一层一层往下查找已经获取焦点的子View。
ViewGroup的findFocus方法如下:
View的findFocus方法
说明:判断view是否获取焦点的isFocused()方法, (mPrivateFlags & PFLAG_FOCUSED) != 0 和view 的isFocused()方法是一致的。
其中isFocused()方法的作用是判断view是否已经获取焦点,如果viewGroup已经获取到了焦点,那么返回本身即可,否则通过mFocused的findFocus()方法来找焦点。mFocused其实就是ViewGroup中获取焦点的子view,如果mView不是ViewGourp的话,findFocus其实就是判断本身是否已经获取焦点,如果已经获取焦点了,返回本身。
(3)回到processKeyEvent方法中,如果findFocus方法返回的mFocused不为空,说明找到了当前获取焦点的view(mFocused),接着focusSearch会把direction(遥控器按键按下的方向)作为参数,找到特定方向下一个将要获取焦点的view,最后如果该view不为空,那么就让该view获取焦点。
(4)focusSearch方法的具体实现。
focusSearch方法的源码如下:
可以看出focusSearch其实是一层一层地网上调用父View的focusSearch方法,直到当前view是根布局(isRootNamespace()方法),通过注释可以知道focusSearch最终会调用DecorView的focusSearch方法。而DecorView的focusSearch方法找到的焦点view是通过FocusFinder来找到的。
(5)FocusFinder是什么?
它其实是一个实现 根据给定的按键方向,通过当前的获取焦点的View,查找下一个获取焦点的view这样算法的类。焦点没有被拦截的情况下,Android框架焦点的查找最终都是通过FocusFinder类来实现的。
(6)FocusFinder是如何通过findNextFocus方法寻找焦点的。
下面就来看看FocusFinder类是如何通过findNextFocus来找焦点的。一层一层往下看,后面会执行findNextUserSpecifiedFocus()方法,这个方法会执行focused(即当前获取焦点的View)的findUserSetNextFocus方法,如果该方法返回的View不为空,且isFocusable = true && isInTouchMode() = true的话,FocusFinder找到的焦点就是findNextUserSpecifiedFocus()返回的View。
(7)findNextFocus会优先根据XML里设置的下一个将获取焦点的View ID值来寻找将要获取焦点的View。
看看View的findUserSetNextFocus方法内部都干了些什么,OMG不就是通过我们xml布局里设置的nextFocusLeft,nextFocusRight的viewId来找焦点吗,如果按下Left键,那么便会通过nextFocusLeft值里的View Id值去找下一个获取焦点的View。
可以得出以下结论:
1. 如果一个View在XML布局中设置了focusable = true && isInTouchMode = true,那么这个View会优先获取焦点。
2. 通过设置nextFocusLeft,nextFocusRight,nextFocusUp,nextFocusDown值可以控制View的下一个焦点。
Android焦点的原理实现就这些。总结一下:
为了方便同志们学习,我这做了张导图,方便大家理解~