天天看點

Android電視機(機頂盒)初次開發的一些經驗分享

從之前的web背景開發轉到Android應用開發,做的第一個正式項目就是公司一個App的電視機(機頂盒)版本Demo開發。經過四個人近兩周加班加點的開發和測試,總算按時傳遞了任務。在後續開發還沒有開始之前,決定把這次開發當中學習到的一些知識和遇到的一些問題和大家的一起分享一下,由于也是剛開始做Android開發,并且是初次做電視機(機頂盒)上的開發,有什麼不正确的地方,歡迎大家批評指正。另外,這也是我本人的第一篇原創部落格,希望以後能堅持寫下去!好,廢話不多說,我們進入正題。

    一、運作和調試

    在做手機版Android開發時,要運作我們的程式,可以選擇使用模拟器或者USB連接配接手機來運作。但是在做電視機(機頂盒)版本開發時,由于現在電視機(機頂盒)普遍的輸出分辨率都在720P以上,用模拟器模拟并不是很友善,而采用資料線連接配接的方式,同樣也不友善,客觀上受到資料線長度和電視機(機頂盒)上接口的限制,而且這次在我們的開發之前進行調研的時候,發現使用資料線連接配接無法識别裝置(也許和驅動有關)。總的來說,之前在手機上開發所用的方法,在電視(機頂盒)上并不好用。那該如何調試運作呢,總不能每次運作都打包,再通過其它方式把安裝包裝上去吧?答案當然是:no。

     這裡就要為大家介紹幾個在電視機(機頂盒)我在本次開發中發現非常實用的幾個adb 指令了:

     1.adb connect [ip]

    使用這個指令,可以連接配接指定ip的裝置。有了這個指令,之前講的問題就迎刃而解了,隻要将我們的電腦和電視機(機頂盒)連入同一個區域網路,就可以連接配接到相應的裝置了,然後就可以在eclipse裡直接運作或調試程式了。相應的有

      adb disconnect [ip]

功能相信大家都懂得,就不解釋了。

    2.adb uninstall [package]

    看名字相信大家也知道,這個指令可以用來解除安裝應用。在電視機(機頂盒)上解除安裝應用并不如我們在手機上友善,全部都要用遙控器來操作,使用這個指令就友善多了。而且這次開發過程中,由于我們裝置有限,幾個人使用一台電視機,經常需要解除安裝别人安裝的不同簽名的程式,這個指令省去了我們不少時間。

    3.adb shell input text ****

    這個指令的功能看字面兒相信也能猜得出來,就是用來輸入文字的。我們在調試程式的時候,很多情況下要鍵入文字,這在手機上可能并不是個問題,可到了電視上,用遙控器按鍵盤真的會讓你崩潰的,這個指令簡直就是福音!

    上面三個就是我在這次開發中使用最頻繁的三個指令,其中第二和第三個的使用,都必須在使用第一個進行connect之後才有意義。當然,adb的指令還有很多,這裡就不詳細介紹了,有興趣的話大家可以自行搜尋。

    二、焦點控制

    關于焦點,在手機開發中,我們可能更多關心的是某個控件在焦點變化時的邏輯處理。而在電視機(機頂盒)上做開發時,每個控件隻有獲得了焦點,才能對其進行操作,是以,確定需要操作的控件能夠獲得焦點、控制焦點的前後順序是一個非常重要的問題。例如,在一個布局中有多個控件,在點選遙控器的上下左右方向鍵時,焦點會移到哪一個控件,哪些控件僅僅做顯示,但不需要獲得焦點等等問題。

    為了確定上述問題的可控性,我們可以通過以下方式來實作:

    1.控制控件是否可獲得焦點

android:focusable="true/false"
           

當這個屬性置為true時,表示目前控件可以獲得焦點,false則表示不可獲得焦點。相應的,我們也可以在程式中通過以下代碼來設定:

v.setFocusable(true/false);
           

    2.控制按遙控器上下左右時下一個獲得焦點的控件

android:nextFocusUp="@+id/..."
android:nextFocusDown="@+id/..."
android:nextFocusLeft="@+id/..."
android:nextFocusRight="@+id/..."
           

這個相信不用多做解釋,從字面意思就能很清楚的明白這四個屬性的含義。相應的,在代碼中也可以實作:

v.setNextFocusUp(id);
v.setNextFocusDown(id);
v.setNextFocusLeft(id);
v.setNextFocusRight(id);
           

    三、UI适配

    UI适配在Android開發中是一件既麻煩又無法避免的事情,在本次開發中同樣也碰到了這方面的問題。我們這次開發,主要适配1080p和720p兩種分辨率。最開始的想法是,既然适配這兩種分辨率,就指定這兩種分辨率的資源,即

    drawable-1920×1080

    drawable-1280×720

相應地,提供對應分辨率下的尺寸:

    values-1920×1080

    values-1280×720

我們開發的時候,有一台42寸的電視盒兩個機頂盒,電視最高支援4k分辨率,盒子隻能輸出720p分辨率。按照我們的适配政策,在電視和機頂盒上測試,都證明是正确的。然而,開發結束,送到測試那邊時,他們使用的是32寸的1080p電視,就出現問題了,程式直接崩潰,無法運作,通過檢視日志,發現是OOM問題。經過同僚的分析,覺得是因為測試的電視雖然是1080p,但尺寸小,dpi高,是的所有圖檔都會被壓縮,導緻OOM。最後,更改了适配政策,采用了

    drawable-sw1080dp

    drawable-sw720dp

相應地,提供對應分辨率下的尺寸:

    values-sw1080dp

    values-sw720dp

解決了程式崩潰的問題。

    雖然問題解決了,但我覺得原因其實是我們之前雖然采用了分辨率來适配,但是在values檔案裡,使用的卻是dp機關,隻是當時開發用的電視,恰好dpi是160,才使得UI顯示正常。是以,采用最初的适配方案,将values中尺寸的機關改為px,應該也是可以的,當然這個還沒有驗證過,後面我會驗證一下。

    四、陰影的程式實作

    為了界面的美觀與動感,在電視APP設計中,往往會用到倒影和陰影的效果,我們這次的demo設計也不例外。開發中,我負責子產品需要實作倒影和陰影,本來是想讓美工來切圖,但是美工不願意,讓我們用程式來實作,交涉許久無果之下,隻得自己來了。倒影的生成,由另外一個同僚寫了一個公用方法實作了,這裡就不和大家分享了,其原理基本上就是将原圖倒置,畫在畫布上,然後加上一個半透明的蒙版,就搞定了。

    陰影效果是我自己實作的,其實網上有很多講陰影實作的教程,但是對我這個都不太适用,我需要的是在一個圓角矩形的圖檔四周加上陰影效果。經過很長時間的考慮,最後的辦法是在原圖四邊加上矩形的陰影,然後在四個圓角的地方畫扇形陰影來實作。不多說,直接上代碼:

/** 
	 * @author: sunnybaby
	 * @Title: createShadowBitmap 
	 * @Description: 生成帶陰影圖檔
	 * @param orignalBitmap:原圖
	 * @param shadowMargin:陰影邊寬
	 * @param iconCornerRadius:原圖圓角半徑
	 * @return :生成的帶陰影圖檔
	 */
	public static Bitmap createShadowBitmap(Bitmap orignalBitmap,
			int shadowMargin, int iconCornerRadius) {
		int w = orignalBitmap.getWidth();
		int h = orignalBitmap.getHeight();
		Bitmap shadowBitmap = Bitmap.createBitmap(w + shadowMargin * 2, h
				+ shadowMargin * 2, Config.ARGB_8888);
		int width = shadowBitmap.getWidth();
		int height = shadowBitmap.getHeight();
		Canvas canvas = new Canvas(shadowBitmap);
		canvas.drawBitmap(orignalBitmap, shadowMargin, shadowMargin, null);
		Paint paint = new Paint();
		paint.setXfermode(new PorterDuffXfermode(Mode.DST_OVER));
		int radius = shadowMargin + iconCornerRadius;
		// 四個邊的陰影效果,采用線性陰影,寬度等于陰影邊距+圓角半徑
		LinearGradient leftGradient = new LinearGradient(radius, 0, 0, 0,
				0x7F000000, 0x00000000, TileMode.CLAMP);
		LinearGradient rightGradient = new LinearGradient(width - radius, 0,
				width, 0, 0x7F000000, 0x00000000, TileMode.CLAMP);
		LinearGradient topGradient = new LinearGradient(0, radius, 0, 0,
				0x7F000000, 0x00000000, TileMode.CLAMP);
		LinearGradient bottomGradient = new LinearGradient(0, height - radius,
				0, height, 0x7F000000, 0x00000000, TileMode.CLAMP);
		paint.setShader(leftGradient);
		canvas.drawRect(0, radius, radius, height - radius, paint);
		paint.setShader(rightGradient);
		canvas.drawRect(width - radius, radius, width, height - radius, paint);
		paint.setShader(topGradient);
		canvas.drawRect(radius, 0, width - radius, radius, paint);
		paint.setShader(bottomGradient);
		canvas.drawRect(radius, height - radius, width - radius, height, paint);
		// 四個角的陰影效果,采用圓形陰影,半徑等于陰影邊距+圓角半徑
		RadialGradient topLeftCornerGradient = new RadialGradient(radius,
				radius, radius, 0x7F000000, 0x00000000, TileMode.CLAMP);
		RadialGradient topRightCornerGradient = new RadialGradient(width
				- radius, radius, radius, 0x7F000000, 0x00000000,
				TileMode.CLAMP);
		RadialGradient bottomLeftCornerGradient = new RadialGradient(radius,
				height - radius, radius, 0x7F000000, 0x00000000, TileMode.CLAMP);
		RadialGradient bottomRightCornerGradient = new RadialGradient(width
				- radius, height - radius, radius, 0x7F000000, 0x00000000,
				TileMode.CLAMP);
		// 畫四個角,就是畫四個圓心角為90度的扇形,drawArc函數第一個參數為圓弧所在圓的的外接矩形,第二個參數為起始角度,第三個參數為扇形順時針滑過的角度,第四個參數如果為true時,在繪制圓弧時将圓心包括在内(用來畫扇形),第五個參數為畫筆
		paint.setShader(topLeftCornerGradient);
		canvas.drawArc(new RectF(0, 0, radius * 2, radius * 2), 180, 90, true,
				paint);
		paint.setShader(topRightCornerGradient);
		canvas.drawArc(new RectF(width - radius * 2, 0, width, radius * 2),
				270, 90, true, paint);
		paint.setShader(bottomLeftCornerGradient);
		canvas.drawArc(new RectF(0, height - radius * 2, radius * 2, height),
				90, 90, true, paint);
		paint.setShader(bottomRightCornerGradient);
		canvas.drawArc(new RectF(width - radius * 2, height - radius * 2,
				width, height), 0, 90, true, paint);
		return shadowBitmap;
	}
           

繼續閱讀