Android 机顶盒TV app开发

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模拟器

https://blog.csdn.net/u013347784/article/details/78329221

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 焦点拦截事件分发

https://blog.csdn.net/jun5753/article/details/78684049

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

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值