文章目录
- 1 TV项目开发步骤
- 1.1 普通apk开发流程
- 1.2 系统apk开发流程
- 2 焦点处理
- 2.1 TV开发中常见的遥控器事件KeyEvent
- 2.2 使用adb命令模拟遥控器按键调试TV模拟器
- 2.3 焦点拦截事件分发
- 2.3.1 事件传递说明
- 2.3.2 常见的焦点处理方式
- 2.3.3 View的焦点处理
- 2.3.4 遥控器长按事件监听
- 2.3.5 焦点丢失问题
- 3 adb参考
- 3.1 adb命令大全
- 3.2 apk正式签名步骤
- 3.3 使用adb命令将apk写入机顶盒相关命令
1 TV项目开发步骤
1.1 普通apk开发流程
- 连接机顶盒
// 10.118.4.144为机顶盒的ip,默认为5555端口
// 机顶盒ip一般在电视中的Network Setting中查看
// 使用adb需要连接网线通过网口连接
>>adb connect 10.118.3.56.5555
- 将apk写入机顶盒
// 使用adb install将写入data/data目录
>>adb install D:\android\test.apk
// 强制覆盖安装apk,两种安装apk方式都是写入data/data目录,不适用于系统apk
>>adb install -r D:\android\test.apk
// 当连接多台机顶盒时,将apk写入指定ip的机顶盒
>>adb -s 10.118.3.56:5555 install D:\android\test.apk
- 卸载apk
// 通过指定包名卸载apk
>>adb uninstall com.example.test
- 查看打印日志
// 查看输出为TAG关键字的log,这里的TAG相当于Log.i(TAG, "xxx")中的TAG
>>logcat -s TAG
// 查看输出为TAG关键字的log,附带上时间
>>logcat -v time -s TAG
// 查看输出为TAG关键字的log,这里的TAG可以为任意值
>>logcat | grep TAG
1.2 系统apk开发流程
/system分区默认挂载为只读,但有些操作比如给Android系统添加命令、删除自带应用等需要对/system进行写操作,所以需要重新挂载它为可读写。
写入的apk需要使用系统签名文件进行签名。
- 申请root权限
>>adb root
>>su
- 进入shell
>>adb shell
- 关闭内核打印
echo 0 > /proc/sys/kernel/printk
- 重新挂载
// mount命令是进入shell才可以使用
// 重新挂载vendor目录为可读写
>>mount -o rw,remount /vendor;
// 重新挂载system目录为可读写
>>mount -o rw,remount /system;
// 如果上面的方式不能成功挂载,退出shell,通过adb remount挂载
>>exit
>>adb remount
// 挂载对应的设备
>>adb -s 10.118.3.41:5555 remount
- 将apk push到系统目录下
// adb push可以替换原来的apk或者写入新的apk
>>adb push D:\android\demo\test.apk /system/app/test.apk
- 重启让apk生效
>>adb shell reboot
>>adb -s 10.118.3.41:5555 shell reboot
- 测试应用
// 启动包名com.example.test下的MainActivity
>>adb shell am start -n com.example.test/.MainActivity
// 主动发送广播
>>adb shell am broadcast -a android.intent.action.xxxx
2 焦点处理
2.1 TV开发中常见的遥控器事件KeyEvent
KeyEvent.KEYCODE_DPAD_UP:遥控器上键
KeyEvent.KEYCODE_DPAD_DOWN:遥控器下键
keyEvent.KEYCODE_DPAD_LEFT:遥控器左键
keyEvent.KEYCODE_DPAD_RIGHT:遥控器右键
keyEvent.KEYCODE_DPAD_CENTER:遥控器ok确认键
keyEvent.KEYCODE_DPAD_x:x表示数字0-9,遥控器数字键
KeyEvent.KEYCODE_BACK:遥控器返回键
KeyEvent.KEYCODE_HOME:遥控器主页键
KeyEvent.KEYCODE_x:x表示字母A-Z,遥控器字母键
KeyEvent.KEYCODE_MENU:遥控器菜单键
2.2 使用adb命令模拟遥控器按键调试TV模拟器
adb shell
input keyevent 1 // 遥控器menu键
input keyevent 3 // 遥控器Home键
input keyevent 4 // 遥控器Back键
input keyevent 19 // 遥控器上键
input keyevent 20 // 遥控器下键
input keyevent 21 // 遥控器左键
input keyevent 22 // 遥控器右键
input keyevent 23 // 遥控器ok确认键
2.3 焦点拦截事件分发
TV端是通过遥控器按键控制焦点事件,与手机端有所不同,所以在事件拦截处理上也不同。
手机端拦截事件分发:
- dispatchTouchEvent()
- onInterceptTouchEvent()
- onTouchEvent()
TV端拦截事件分发:
- dispatchKeyEvent()
- onKeyDown()/onKeyUp()
- onKeyListener()
2.3.1 事件传递说明
(PhoneWindow)$DecorView.dispatchKeyEvent()->
Activity.dispatchKeyEvent()->
View.dispatchKeyEvent()->
// setonKeyListener != null则回调监听器
View.setOnKeyListener.onKey()->
// setOnKeyListener == null或onKey()返回false则回调onKeyDown()/onKeyUp()
View.onKeyDown()/onKeyUp()->
// setOnClickListener != null && 遥控器ok按键的ACTION_UP则回调监听器
View.setOnClickListener.onClick()->
// View.setOnClickListener == null则返回到Activity回调onKeyDown()/onKeyUp()
Activity.OnKeyDown()/OnKeyUp()
KeyEvent的事件处理只有两个地方,一个是Activity,一个是具体的View,ViewGroup只负责分发事件给子View不消耗事件。其实Activity和具体View的事件处理都是交由DecorView处理。
2.3.2 常见的焦点处理方式
- 在Activity重写dispatchKeyEvent()
// 遥控器按下和松开会分别调用两次dispatchKeyEvent(),dispatchKeyEvent->onKeyDown->dispatchKeyEvent->onKeyUp
// 返回true或false都会拦截事件,即子View将不会接收到分发事件,Activity的onKeyDown()/onKeyUp()也不会接收到
// return true:拦截事件,不让焦点再移动
// return false:拦截事件,焦点会移动
// return super.dispatchKeyEvent:往下分发事件不拦截
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
int keyCode = event.getKeyCode();
int action = event.getAction();
switch(keyCode) {
case KeyEvent.KEYCODE_DPAD_DOWN:
if (action == KeyEvent.ACTION_DOWN) {
Log.i(TAG, "dispatchKeyEvent-ACTION_DOWN");
} else if (action == KeyEvent.ACTION_UP) {
Log.i(TAG, "dispatchKeyEvent-ACTION_UP");
}
return true;
case KeyEvent.KEYCODE_DPAD_UP:
if (action == KeyEvent.ACTION_DOWN) {
Log.i(TAG, "dispatchKeyEvent-ACTION_DOWN");
} else if (action == KeyEvent.ACTION_UP) {
Log.i(TAG, "dispatchKeyEvent-ACTION_UP");
}
return false;
}
return super.dispatchKeyEvent(event);
}
- 在Activity重写onKeyDown()/onKeyUp()
事件走到这两个回调表示子View没有消耗事件抛回给上层,在这里做最后的事件处理。
- 具体View实现setOnKeyListener()
一般实现onKeyListener对具体的遥控器事件做特殊处理
- 具体View实现OnClickListener()
一般实现onClickListener都是响应遥控器的ok确认键
2.3.3 View的焦点处理
- requestFocus():强制获取焦点
- android:focusable:设置一个控件是否能获取焦点
- android:nextFocusUp/view.setNextFocusUpId(id):设置遥控器按上键时哪个view会获取到焦点,提供一个id
- android:nextFocusDown/view.setNextFocusDownId(id):设置遥控器按下键时哪个view会获取到焦点,提供一个id
- android:nextFocusLeft/view.setNextFocusLeftId(id):设置遥控器按左键时哪个view会获取到焦点,提供一个id
- android:nextFocusRight/view.setNextFocusRightId(id):设置遥控器按右键时哪个view会获取到焦点,提供一个id
- view.setOnFocusChangeListener():监听view获取到焦点,一般在该方法处理view背景修改等
2.3.4 遥控器长按事件监听
private boolean isLongPress;
@Override
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
isLongPress = true;
return true;
}
return super.onKeyLongPress(keyCode, event);
}
// dispatchKeyEvent统一处理长按时按下和松开时的处理
// 或者分别在onKeyDown监听长按按下,onKeyUp监听长按松开
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
int keyCode = event.getKeyCode();
int action = event.getAction();
switch(keyCode) {
case KeyEvent.KEYCODE_DPAD_DOWN:
if (action == KeyEvent.ACTION_DOWN) {
// 按下时会执行一次,进行event长按跟踪,不断回调dispatchKeyEvent,第二次开始会走else
if (event.getRepeatCount() == 0) {
event.startTracking();
isLongPress = false;
} else {
isLongPress = true;
// 遥控器向下长按按下处理
.....
}
} else if (action == KeyEvent.ACTION_UP) {
if (isLongPress) {
isLongPress = false;
// 遥控器向下长按松开处理
....
}
}
return true;
}
return super.dispatckKeyEvent(event);
}
或者
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
// 按下时会执行一次,进行event长按跟踪
if (event.getRepeatCount() == 0) {
isLongPress = false;
event.startTracking();
}
return true;
}
}
return super.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
if (isLongPress) {
isLongPress = false;
// 遥控器向下长按松开处理
....
}
return true;
}
return super.onKeyUp(keyCode, event);
}
2.3.5 焦点丢失问题
android:descendantFocusability="afterDescendants"
在组合控件(即自定义ViewGroup inflate有多个控件的xml布局)焦点处理中,在使用ListView、GridView、RecyclerView时有时会出现焦点丢失问题,需要添加该属性
// ViewGroup会优先其子类控件而获取焦点
android:descendantsFocusability="beforeDescendants"
// ViewGroup只有当其子类控件不需要获取焦点时才获取焦点
android:descendantsFocusability="afterDescendants"
// ViewGroup会覆盖子类控件而直接获得焦点
android:descandantsFocusability="blockDescendants"
3 adb参考
3.1 adb命令大全
https://github.com/mzlogin/awesome-adb
3.2 apk正式签名步骤
https://jingyan.baidu.com/article/59703552aeccbf8fc1074075.html
3.3 使用adb命令将apk写入机顶盒相关命令
https://www.jianshu.com/p/225fef8c6e67