1. 程式人生 > >Android電視機(機頂盒)初次開發的一些經驗分享

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;
	}