當(dāng)前位置:首頁(yè) > 嵌入式 > 嵌入式軟件
[導(dǎo)讀]Android系統(tǒng)開發(fā)全攻略(二)

一、Android智能手機(jī)遠(yuǎn)程視頻監(jiān)控的設(shè)計(jì)

  摘要:為了實(shí)現(xiàn)移動(dòng)視頻監(jiān)控,提出了一種基于智能手機(jī)的遠(yuǎn)程視頻監(jiān)控系統(tǒng)。介紹了監(jiān)控系統(tǒng)的體系結(jié)構(gòu)和硬件平臺(tái),闡述了嵌入式操作系統(tǒng)Android 應(yīng)用程序的開發(fā)方法,并結(jié)合實(shí)際的應(yīng)用系統(tǒng),重點(diǎn)論述了Android 平臺(tái)上視頻監(jiān)控客戶端的設(shè)計(jì)思路。移植了音視頻解碼庫(kù)FFmpeg 進(jìn)行H. 264 視頻解碼,并采用OpenGL ES 實(shí)現(xiàn)實(shí)時(shí)視頻顯示。在無(wú)線局域網(wǎng)絡(luò)的環(huán)境下對(duì)視頻監(jiān)控終端進(jìn)行測(cè)試,達(dá)到了利用手機(jī)進(jìn)行移動(dòng)視頻監(jiān)控的目的。

  隨著多媒體技術(shù)、視頻壓縮技術(shù)以及網(wǎng)絡(luò)傳輸技術(shù)的發(fā)展,視頻監(jiān)控正朝著數(shù)字化、網(wǎng)絡(luò)化、智能化方向持續(xù)發(fā)展,并越來(lái)越廣泛地滲透到政府、教育、娛樂(lè)、醫(yī)療等領(lǐng)域。目前大部分的網(wǎng)絡(luò)視頻監(jiān)控系統(tǒng)是基于WEB 服務(wù)器的, 監(jiān)控終端為PC機(jī),用戶使用瀏覽器獲取監(jiān)控服務(wù)。由于互聯(lián)網(wǎng)接入地點(diǎn)的限制,普通的網(wǎng)絡(luò)視頻監(jiān)控?zé)o法滿足用戶在任何時(shí)間、任何地點(diǎn)獲取監(jiān)控信息的需求。

參閱相關(guān)系列文章

Android系統(tǒng)開發(fā)全攻略(一)

  本文介紹了一種以Android 智能手機(jī)為終端的視頻監(jiān)控系統(tǒng),該系統(tǒng)將傳統(tǒng)的視頻監(jiān)控與移動(dòng)多媒體技術(shù)相結(jié)合,真正實(shí)現(xiàn)了移動(dòng)視頻監(jiān)控。

  1系統(tǒng)的結(jié)構(gòu)

  本文中的視頻監(jiān)控系統(tǒng)采用C/ S 體系結(jié)構(gòu)。

  如圖1 所示,該系統(tǒng)由視頻采集端( 攝像頭),視頻服務(wù)器以及監(jiān)控客戶端等構(gòu)成。

  

  圖1視頻監(jiān)控系統(tǒng)總體結(jié)構(gòu)

  視頻服務(wù)器是整個(gè)系統(tǒng)的核心部分,它將攝像頭采集到的原始模擬信號(hào)轉(zhuǎn)換為數(shù)字信號(hào),并對(duì)視頻數(shù)據(jù)進(jìn)行編碼壓縮,最后通過(guò)Internet 將壓縮后的數(shù)據(jù)傳送至客戶端??蛻舳送ㄟ^(guò)TCP/ IP 協(xié)議訪問(wèn)服務(wù)器,通過(guò)對(duì)視頻數(shù)據(jù)的接收、解碼以及顯示,實(shí)現(xiàn)實(shí)時(shí)預(yù)覽功能??蛻舳艘部梢愿鶕?jù)用戶需求發(fā)送控制命令,實(shí)現(xiàn)對(duì)前端設(shè)備的控制操作,如云臺(tái)控制等。

  服務(wù)器部分采用Hi3515 處理器芯片為硬件平臺(tái),并移植了嵌入式操作系統(tǒng)Linux 作為整個(gè)系統(tǒng)運(yùn)行的軟件環(huán)境。Hi3515 是一款基于ARM9 處理器內(nèi)核以及視頻硬件加速引擎的高性能通信媒體處理器,具有H. 264 和MJPEG 多協(xié)議編解碼能力。

  本文以基于Hi3515 的遠(yuǎn)程視頻監(jiān)控系統(tǒng)為例,重點(diǎn)介紹了Android 平臺(tái)上監(jiān)控客戶端的設(shè)計(jì)過(guò)程。

  2Android 開發(fā)介紹

  Android 是基于Linux 開放性內(nèi)核的操作系統(tǒng),是Google 公司在2007 年11 月5 日公布的手機(jī)操作系統(tǒng)。Android 采用軟件堆層的架構(gòu),主要分為三部分:底層以Linux 核心為基礎(chǔ),提供基本功能;中間層包括函數(shù)庫(kù)和虛擬機(jī);最上層是各種應(yīng)用軟件。

  Android 平臺(tái)顯著的開放性使其擁有眾多的開發(fā)者,應(yīng)用日益豐富,不僅應(yīng)用于智能手機(jī),也向平板電腦、智能MP4 方面急速擴(kuò)張。

  Android 應(yīng)用程序用Java 語(yǔ)言編寫,每個(gè)應(yīng)用程序都擁有一個(gè)獨(dú)立的Dalvik 虛擬機(jī)實(shí)例,這個(gè)實(shí)例駐留在一個(gè)由Linux 內(nèi)核管理的進(jìn)程中。Dalvik支持Java Native Interface(JNI)編程方式,Android 應(yīng)用程序可以通過(guò)JNI 調(diào)用C/ C++開發(fā)的共享庫(kù),實(shí)現(xiàn)“Java+C冶的編程方式。開發(fā)Android 應(yīng)用程序最簡(jiǎn)捷的方式是安裝Android SDK 和Eclipse IDE.

  Eclipse 提供了一個(gè)豐富的Java 環(huán)境,Java 代碼通過(guò)編譯后,Android Developer Tools 會(huì)將它打包,用于安裝。

  3 監(jiān)控客戶端的設(shè)計(jì)與實(shí)現(xiàn)

  基于Android 平臺(tái)的監(jiān)控客戶端的總體框架如圖2 所示,分別由網(wǎng)絡(luò)通訊模塊、視頻解碼模塊以及視頻顯示模塊等構(gòu)成。其中網(wǎng)絡(luò)通訊模塊接收來(lái)自服務(wù)器的所有數(shù)據(jù),對(duì)數(shù)據(jù)進(jìn)行解析,并將視頻數(shù)據(jù)存入到視頻緩沖區(qū)。視頻解碼模塊負(fù)責(zé)從視頻緩沖區(qū)中讀取數(shù)據(jù)并送入H. 264 解碼器進(jìn)行解碼。最后,采用OpenGL 圖形庫(kù)將解碼后圖像繪制到屏幕上實(shí)現(xiàn)視頻播放。

  

  圖2客戶端總體框架。

  3. 1 H. 264 視頻解碼器的實(shí)現(xiàn)

  在網(wǎng)絡(luò)視頻監(jiān)控系統(tǒng)中,視頻的編碼壓縮是非常必要和關(guān)鍵的工作,沒有經(jīng)過(guò)壓縮的海量數(shù)據(jù)對(duì)網(wǎng)絡(luò)傳輸系統(tǒng)來(lái)說(shuō)是無(wú)法承受的[7] .H.264 是目前最先進(jìn)的視頻壓縮算法,它由視頻編碼層VCL 和網(wǎng)絡(luò)提取層NAL 兩部分組成。其中,VCL 進(jìn)行視頻編解碼,包括運(yùn)動(dòng)補(bǔ)償預(yù)測(cè)、變換編碼和熵編碼等;NAL 采用適當(dāng)?shù)母袷綄?duì)VCL 視頻數(shù)據(jù)進(jìn)行封裝打包。H.264 標(biāo)準(zhǔn)對(duì)編碼效率和圖像質(zhì)量進(jìn)行了諸多改進(jìn),且抗丟包性能和抗誤碼性能好,適應(yīng)各種網(wǎng)絡(luò)環(huán)境,非常適合于對(duì)壓縮率要求高,網(wǎng)絡(luò)環(huán)境復(fù)雜的移動(dòng)視頻監(jiān)控。

  客戶端接收的數(shù)據(jù)是經(jīng)過(guò)H.264 編碼壓縮后的數(shù)據(jù),需要經(jīng)過(guò)H.264 解碼還原視頻圖像后才能夠顯示,因此,H.264 解碼器是客戶端的關(guān)鍵部分。這里移植了開源的音視頻解碼庫(kù)FFmpeg 進(jìn)行H.264 解碼。在Android 應(yīng)用程序中使用FFmpeg 的步驟如下:

 ?。?)在Linux 環(huán)境下安裝Android 原生開發(fā)工具包NDK.

 ?。?) 創(chuàng)建jni 文件夾,將FFmpeg 工程復(fù)制到文件夾下。創(chuàng)建H264Decoder. c 源文件,提供Android程序使用的接口函數(shù),文件需要包括JNI 的操作頭文件《jni. h 》, 且函數(shù)名有固定的形式, 如com_ipcamera_PreView_H264Decoder 表示com_ipcamera包下面PreView 類中H264Decoder 函數(shù)。

  (3)創(chuàng)建Android. mk 文件,該文件包含正確構(gòu)建和命名庫(kù)的MakeFile 說(shuō)明。分別在LOCAL_SRC_FILES 和LOCAL_C_INCLUDES 項(xiàng)中添加編譯模塊所需源文件和頭文件目錄。

  (4)執(zhí)行NDK 開發(fā)包中的ndk鄄build 腳本,生成對(duì)應(yīng)的。 so 共享庫(kù),并復(fù)制到Android 工程下的libs/armeabi 目錄下。

 ?。?) 在Android 程序中通過(guò)System. loadLibrary(”庫(kù)名稱冶)加載所需要的庫(kù),加載成功后,應(yīng)用程序就可以使用H264Decoder 函數(shù)進(jìn)行H.264 的解碼。

  3. 2 OpenGL ES 繪圖

  為了提高繪圖的效率,客戶端使用OpenGL ES實(shí)現(xiàn)視頻圖像的顯示。OpenGL ES 是一個(gè)2D/3D輕量圖形庫(kù),是跨平臺(tái)圖形庫(kù)OpenGL 的簡(jiǎn)化版。

  OpenGL ES 專門針對(duì)手機(jī)、PDA 和游戲主機(jī)等嵌入式設(shè)備而設(shè)計(jì),目的是為了充分利用硬件加速,適合復(fù)雜的、圖形密集的程序。

  Android 中使用GLSurfaceView 來(lái)顯示OpenGL視圖,該類繼承至SurfaceView 并包含了一個(gè)專門用于渲染3D 的接口Renderer,主要通過(guò)實(shí)現(xiàn)ON鄄DrawFrame、onSurfaceChanged 以及onSurfaceCreated等方法構(gòu)建所需的Renderer.解碼器解碼一幀圖像后,調(diào)用GLSurfaceView 的requeSTRender 方法通知OpenGL ES 完成視頻圖像的顯示。使用OpenGL 繪圖的核心代碼如下:

  

  3. 3多線程設(shè)計(jì)

  視頻數(shù)據(jù)的接收和解碼都是復(fù)雜、持續(xù)的過(guò)程,如果其中一個(gè)過(guò)程出現(xiàn)阻塞會(huì)影響整個(gè)程序的運(yùn)行,因此,客戶端使用多線程實(shí)現(xiàn)數(shù)據(jù)接收和視頻解碼的并行處理。在整個(gè)程序運(yùn)行過(guò)程中,主線程響應(yīng)用戶操作,負(fù)責(zé)屏幕刷新工作,并創(chuàng)建兩個(gè)子線程:數(shù)據(jù)接收和視頻解碼子線程,處理過(guò)程如圖3 所示。

  

  圖3子線程處理流程。

  在Java 中, 多線程的實(shí)現(xiàn)有兩種方式: 擴(kuò)展java. lang. Thread 類或?qū)崿F(xiàn)java. lang. Runnable 接口。這里通過(guò)繼承Thread 類并覆寫run()方法實(shí)現(xiàn)兩個(gè)子線程。在多線程的應(yīng)用中關(guān)鍵是處理好線程之間的同步問(wèn)題,以解決對(duì)共享存儲(chǔ)區(qū)的訪問(wèn)沖突,避免引起線程甚至整個(gè)系統(tǒng)的死鎖。Java 多線程主要利用synchronized 關(guān)鍵字和wait( )、notify( ) 等方法實(shí)現(xiàn)線程間的同步。

  4 結(jié)束語(yǔ)

  目前,該系統(tǒng)已經(jīng)在實(shí)驗(yàn)室進(jìn)行測(cè)試,服務(wù)器輸出15fps CIF 格式的H. 264 視頻數(shù)據(jù),客戶端安裝在Android 手機(jī)上,通過(guò)WIFI 接入無(wú)線局域網(wǎng)中與服務(wù)器建立連接,用戶界面如圖4 所示,可實(shí)現(xiàn)遠(yuǎn)程視頻預(yù)覽、云臺(tái)控制等操作。

  

  圖4 監(jiān)控客戶端

  隨著3G 時(shí)代的到來(lái),數(shù)據(jù)傳輸速度有了大幅提升,為移動(dòng)實(shí)時(shí)視頻業(yè)務(wù)的實(shí)現(xiàn)創(chuàng)造更好的條件。

  手機(jī)用戶可以直接接入3G 網(wǎng)絡(luò)訪問(wèn)視頻監(jiān)控服務(wù)器,實(shí)現(xiàn)移動(dòng)在線的實(shí)時(shí)視頻監(jiān)控。由此可見,手機(jī)視頻監(jiān)控市場(chǎng)潛力巨大,具有很好的發(fā)展前景。

 

二、可動(dòng)態(tài)布局的Android抽屜之完整篇

  以前曾經(jīng)介紹過(guò)《Android提高第十九篇之“多方向”抽屜》,當(dāng)這個(gè)抽屜組件不與周圍組件發(fā)生壓擠的情況下(周圍組件布局不變),是比較好使的,但是如果需要對(duì)周圍組件擠壓,則用起來(lái)欠缺美觀了。

  如下圖。在對(duì)周圍壓擠的情況下,抽屜是先把周圍的組件一次性壓擠,再通過(guò)動(dòng)畫效果展開/收縮的,這種做法的好處是快速簡(jiǎn)單,壞處是如果擠壓范圍過(guò)大,則效果生硬。

  

  本文實(shí)現(xiàn)的自定義抽屜組件,主要針對(duì)這種壓擠效果做出改良,漸進(jìn)式壓擠周圍組件,使得過(guò)渡效果更加美觀。如下圖。

  

  本文實(shí)現(xiàn)的抽屜原理是醬紫:

  1.抽屜組件主要在屏幕不可視區(qū)域,手柄在屏幕邊緣的可視區(qū)域。即 抽屜.rightMargin=-XXX + 手柄.width

  2.指定一個(gè)周圍組件為可壓擠,即LayoutParams.weight=1;當(dāng)然用戶也可以指定多個(gè)View.

  3.使用AsyncTask來(lái)實(shí)現(xiàn)彈出/收縮的動(dòng)畫,彈出:抽屜.rightMargin+=XX,收縮:抽屜.rightMargin-=XX

  總結(jié),本文的自定義抽屜雖然對(duì)壓擠周圍組件有過(guò)渡效果,但是比較耗資源,讀者可以針對(duì)不同的情況考慮使用。

  本文的源碼可以到http://download.csdn.net/detail/hellogv/3615686 下載。

  接下來(lái)貼出本文全部源代碼:

  main.xml的源碼:

  [html] view plaincopyprint?

  《span style=“font-family:Comic Sans MS;font-size:18px;”》《?xml version=“1.0” encoding=“utf-8”?》

  《LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”

  android:layout_width=“fill_parent” android:layout_height=“fill_parent”

  android:id=“@+id/container”》

  《GridView android:id=“@+id/gridview” android:layout_width=“fill_parent”

  android:layout_height=“fill_parent” android:numColumns=“auto_fit”

  android:verticalSpacing=“10dp” android:gravity=“center”

  android:columnWidth=“50dip” android:horizontalSpacing=“10dip” /》

  《/LinearLayout》《/span》

  《span style=“font-family:Comic Sans MS;font-size:18px;”》《?xml version=“1.0” encoding=“utf-8”?》

  《LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”

  android:layout_width=“fill_parent” android:layout_height=“fill_parent”

  android:id=“@+id/container”》

  《GridView android:id=“@+id/gridview” android:layout_width=“fill_parent”

  android:layout_height=“fill_parent” android:numColumns=“auto_fit”

  android:verticalSpacing=“10dp” android:gravity=“center”

  android:columnWidth=“50dip” android:horizontalSpacing=“10dip” /》

  《/LinearLayout》《/span》

  GridView的Item.xml的源碼:

  [html] view plaincopyprint?

  《span style=“font-family:Comic Sans MS;font-size:18px;”》《?xml version=“1.0” encoding=“utf-8”?》

  《RelativeLayout xmlns:android=“http://schemas.android.com/apk/res/android”

  android:layout_height=“wrap_content” android:paddingBottom=“4dip”

  android:layout_width=“fill_parent”》

  《ImageView android:layout_height=“wrap_content” android:id=“@+id/ItemImage”

  android:layout_width=“wrap_content” android:layout_centerHorizontal=“true”》

  《/ImageView》

  《TextView android:layout_width=“wrap_content”

  android:layout_below=“@+id/ItemImage” android:layout_height=“wrap_content”

  android:text=“TextView01” android:layout_centerHorizontal=“true”

  android:id=“@+id/ItemText”》

  《/TextView》

  《/RelativeLayout》 《/span》

  《span style=“font-family:Comic Sans MS;font-size:18px;”》《?xml version=“1.0” encoding=“utf-8”?》

  《RelativeLayout xmlns:android=“http://schemas.android.com/apk/res/android”

  android:layout_height=“wrap_content” android:paddingBottom=“4dip”

  android:layout_width=“fill_parent”》

  《ImageView android:layout_height=“wrap_content” android:id=“@+id/ItemImage”

  android:layout_width=“wrap_content” android:layout_centerHorizontal=“true”》

  《/ImageView》

  《TextView android:layout_width=“wrap_content”

  android:layout_below=“@+id/ItemImage” android:layout_height=“wrap_content”

  android:text=“TextView01” android:layout_centerHorizontal=“true”

  android:id=“@+id/ItemText”》

  《/TextView》

  《/RelativeLayout》 《/span》

  Panel.java是本文核心,抽屜組件的源碼,這個(gè)抽屜只實(shí)現(xiàn)了從右往左的彈出/從左往右的收縮,讀者可以根據(jù)自己的需要修改源碼來(lái)改變抽屜動(dòng)作的方向:

 ?。踛ava] view plaincopyprint?

  《span style=“font-family:Comic Sans MS;font-size:18px;”》public class Panel extends LinearLayout{

  public interface PanelClosedEvent {

  void onPanelClosed(View panel);

  }

  public interface PanelOpenedEvent {

  void onPanelOpened(View panel);

  }

  /**Handle的寬度,與Panel等高*/

  private final static int HANDLE_WIDTH=30;

  /**每次自動(dòng)展開/收縮的范圍*/

  private final static int MOVE_WIDTH=20;

  private Button btnHandle;

  private LinearLayout panelContainer;

  private int mRightMargin=0;

  private Context mContext;

  private PanelClosedEvent panelClosedEvent=null;

  private PanelOpenedEvent panelOpenedEvent=null;

  /**

  * otherView自動(dòng)布局以適應(yīng)Panel展開/收縮的空間變化

  * @author GV

  *

  */[!--empirenews.page--]

  public Panel(Context context,View otherView,int width,int height) {

  super(context);

  this.mContext=context;

  //改變Panel附近組件的屬性

  LayoutParams otherLP=(LayoutParams) otherView.getLayoutParams();

  otherLP.weight=1;//支持壓擠

  otherView.setLayoutParams(otherLP);

  //設(shè)置Panel本身的屬性

  LayoutParams lp=new LayoutParams(width, height);

  lp.rightMargin=-lp.width+HANDLE_WIDTH;//Panel的Container在屏幕不可視區(qū)域,Handle在可視區(qū)域

  mRightMargin=Math.abs(lp.rightMargin);

  this.setLayoutParams(lp);

  this.setOrientation(LinearLayout.HORIZONTAL);

  //設(shè)置Handle的屬性

  btnHandle=new Button(context);

  btnHandle.setLayoutParams(new LayoutParams(HANDLE_WIDTH,height));

  btnHandle.setOnClickListener(new OnClickListener(){

  @Override

  public void onClick(View arg0) {

  LayoutParams lp = (LayoutParams) Panel.this.getLayoutParams();

  if (lp.rightMargin 《 0)// CLOSE的狀態(tài)

  new AsynMove().execute(new Integer[] { MOVE_WIDTH });// 正數(shù)展開

  else if (lp.rightMargin 》= 0)// OPEN的狀態(tài)

  new AsynMove().execute(new Integer[] { -MOVE_WIDTH });// 負(fù)數(shù)收縮

  }

  });

  //btnHandle.setOnTouchListener(HandleTouchEvent);

  this.addView(btnHandle);

  //設(shè)置Container的屬性

  panelContainer=new LinearLayout(context);

  panelContainer.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,

  LayoutParams.FILL_PARENT));

  this.addView(panelContainer);

  }

  /**
  * 定義收縮時(shí)的回調(diào)函數(shù)

  * @param event

  */

  public void setPanelClosedEvent(PanelClosedEvent event)

  {

  this.panelClosedEvent=event;

  }

  /**

  * 定義展開時(shí)的回調(diào)函數(shù)

  * @param event

  */

  public void setPanelOpenedEvent(PanelOpenedEvent event)

  {

  this.panelOpenedEvent=event;

  }

  /**

  * 把View放在Panel的Container

  * @param v

  */

  public void fillPanelContainer(View v)

  {

  panelContainer.addView(v);

  }

/**

  * 異步移動(dòng)Panel

  * @author hellogv

  */

  class AsynMove extends AsyncTask《Integer, Integer, Void》 {

  @Override

  protected Void doInBackground(Integer.。. params) {

  int times;

  if (mRightMargin % Math.abs(params[0]) == 0)// 整除

  times = mRightMargin / Math.abs(params[0]);

  else

  // 有余數(shù)

  times = mRightMargin / Math.abs(params[0]) + 1;

  for (int i = 0; i 《 times; i++) {

  publishProgress(params);

  try {

  Thread.sleep(Math.abs(params[0]));

  } catch (InterruptedException e) {

  // TODO Auto-generated catch block

  e.printStackTrace();

  }

  }

  return null;

  }

  @Override

  protected void onProgressUpdate(Integer.。. params) {

  LayoutParams lp = (LayoutParams) Panel.this.getLayoutParams();

  if (params[0] 《 0)

  lp.rightMargin = Math.max(lp.rightMargin + params[0],

 ?。?mRightMargin));

  else

  lp.rightMargin = Math.min(lp.rightMargin + params[0], 0);

  if(lp.rightMargin==0 && panelOpenedEvent!=null){//展開之后

  panelOpenedEvent.onPanelOpened(Panel.this);//調(diào)用OPEN回調(diào)函數(shù)

  }

  else if(lp.rightMargin==-(mRightMargin) && panelClosedEvent!=null){//收縮之后

  panelClosedEvent.onPanelClosed(Panel.this);//調(diào)用CLOSE回調(diào)函數(shù)

  }

  Panel.this.setLayoutParams(lp);

  }

  }

  }

  《/span》

  《span style=“font-family:Comic Sans MS;font-size:18px;”》public class Panel extends LinearLayout{

  public interface PanelClosedEvent {

  void onPanelClosed(View panel);

  }

  public interface PanelOpenedEvent {

  void onPanelOpened(View panel);

  }

  /**Handle的寬度,與Panel等高*/

  private final static int HANDLE_WIDTH=30;

  /**每次自動(dòng)展開/收縮的范圍*/

  private final static int MOVE_WIDTH=20;

  private Button btnHandle;

  private LinearLayout panelContainer;

  private int mRightMargin=0;

  private Context mContext;

  private PanelClosedEvent panelClosedEvent=null;

  private PanelOpenedEvent panelOpenedEvent=null;

  /**

  * otherView自動(dòng)布局以適應(yīng)Panel展開/收縮的空間變化

  * @author GV

  *

  */

  public Panel(Context context,View otherView,int width,int height) {

  super(context);

  this.mContext=context;

  //改變Panel附近組件的屬性

  LayoutParams otherLP=(LayoutParams) otherView.getLayoutParams();

  otherLP.weight=1;//支持壓擠

  otherView.setLayoutParams(otherLP);

  //設(shè)置Panel本身的屬性

  LayoutParams lp=new LayoutParams(width, height);

  lp.rightMargin=-lp.width+HANDLE_WIDTH;//Panel的Container在屏幕不可視區(qū)域,Handle在可視區(qū)域

  mRightMargin=Math.abs(lp.rightMargin);

  this.setLayoutParams(lp);

  this.setOrientation(LinearLayout.HORIZONTAL);

  //設(shè)置Handle的屬性

  btnHandle=new Button(context);

  btnHandle.setLayoutParams(new LayoutParams(HANDLE_WIDTH,height));

  btnHandle.setOnClickListener(new OnClickListener(){

  @Override

  public void onClick(View arg0) {

  LayoutParams lp = (LayoutParams) Panel.this.getLayoutParams();

  if (lp.rightMargin 《 0)// CLOSE的狀態(tài)

  new AsynMove().execute(new Integer[] { MOVE_WIDTH });// 正數(shù)展開

  else if (lp.rightMargin 》= 0)// OPEN的狀態(tài)

  new AsynMove().execute(new Integer[] { -MOVE_WIDTH });// 負(fù)數(shù)收縮

  }

  });

  //btnHandle.setOnTouchListener(HandleTouchEvent);

  this.addView(btnHandle);

  //設(shè)置Container的屬性

  panelContainer=new LinearLayout(context);

  panelContainer.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,

  LayoutParams.FILL_PARENT));

  this.addView(panelContainer);

  }

  /**

  * 定義收縮時(shí)的回調(diào)函數(shù)

  * @param event

  */

  public void setPanelClosedEvent(PanelClosedEvent event)

  {

  this.panelClosedEvent=event;

  }

  /**

  * 定義展開時(shí)的回調(diào)函數(shù)

  * @param event

  */

  public void setPanelOpenedEvent(PanelOpenedEvent event)

  {

  this.panelOpenedEvent=event;

  }

  /**

  * 把View放在Panel的Container

  * @param v

  */

  public void fillPanelContainer(View v)

  {

  panelContainer.addView(v);

  }

  /**

  * 異步移動(dòng)Panel

  * @author hellogv

  */

  class AsynMove extends AsyncTask《Integer, Integer, Void》 {

  @Override

  protected Void doInBackground(Integer.。. params) {

  int times;

  if (mRightMargin % Math.abs(params[0]) == 0)// 整除

  times = mRightMargin / Math.abs(params[0]);

  else

  // 有余數(shù)

  times = mRightMargin / Math.abs(params[0]) + 1;

  for (int i = 0; i 《 times; i++) {

  publishProgress(params);

  try {

  Thread.sleep(Math.abs(params[0]));

  } catch (InterruptedException e) {

  // TODO Auto-generated catch block

  e.printStackTrace();

  }

  }

  return null;

  }

  @Override

  protected void onProgressUpdate(Integer.。. params) {

  LayoutParams lp = (LayoutParams) Panel.this.getLayoutParams();

  if (params[0] 《 0)

  lp.rightMargin = Math.max(lp.rightMargin + params[0],

 ?。?mRightMargin));

  else

  lp.rightMargin = Math.min(lp.rightMargin + params[0], 0);

  if(lp.rightMargin==0 && panelOpenedEvent!=null){//展開之后

  panelOpenedEvent.onPanelOpened(Panel.this);//調(diào)用OPEN回調(diào)函數(shù)

  }

  else if(lp.rightMargin==-(mRightMargin) && panelClosedEvent!=null){//收縮之后

  panelClosedEvent.onPanelClosed(Panel.this);//調(diào)用CLOSE回調(diào)函數(shù)

  }

  Panel.this.setLayoutParams(lp);

  }

  }

  }

  《/span》

  main.java是主控部分,演示了Panel的使用:

 ?。踛ava] view plaincopyprint?

  《span style=“font-family:Comic Sans MS;font-size:18px;”》public class main extends Activity {

  public Panel panel;

  public LinearLayout container;

  public GridView gridview;

  public void onCreate(Bundle savedInstanceState) {

  super.onCreate(savedInstanceState);

  setContentView(R.layout.main);

  this.setTitle(““可動(dòng)態(tài)布局”的抽屜組件之構(gòu)建基礎(chǔ)-----hellogv”);

  gridview = (GridView) findViewById(R.id.gridview);

  container=(LinearLayout)findViewById(R.id.container);

  panel=new Panel(this,gridview,200,LayoutParams.FILL_PARENT);

  container.addView(panel);//加入Panel控件

  //新建測(cè)試組件

  TextView tvTest=new TextView(this);

  tvTest.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,LayoutParams.FILL_PARENT));

  tvTest.setText(“測(cè)試組件,紅字白底”);

  tvTest.setTextColor(Color.RED);

  tvTest.setBackgroundColor(Color.WHITE);

  //加入到Panel里面

  panel.fillPanelContainer(tvTest);

  panel.setPanelClosedEvent(panelClosedEvent);

  panel.setPanelOpenedEvent(panelOpenedEvent);

  //往GridView填充測(cè)試數(shù)據(jù)

  ArrayList《HashMap《String, Object》》 lstImageItem = new ArrayList《HashMap《String, Object》》();

  for (int i = 0; i 《 100; i++) {

  HashMap《String, Object》 map = new HashMap《String, Object》();

  map.put(“ItemImage”, R.drawable.icon);

  map.put(“ItemText”, “NO.” + String.valueOf(i));

  lstImageItem.add(map);

  }

  SimpleAdapter saImageItems = new SimpleAdapter(this,

  lstImageItem,

  R.layout.item,

  new String[] { “ItemImage”, “ItemText” },

  new int[] { R.id.ItemImage, R.id.ItemText });

  gridview.setAdapter(saImageItems);

  gridview.setOnItemClickListener(new ItemClickListener());

  }

  PanelClosedEvent panelClosedEvent =new PanelClosedEvent(){

  @Override

  public void onPanelClosed(View panel) {

  Log.e(“panelClosedEvent”,“panelClosedEvent”);

  }

  };

  PanelOpenedEvent panelOpenedEvent =new PanelOpenedEvent(){

  @Override

  public void onPanelOpened(View panel) {

  Log.e(“panelOpenedEvent”,“panelOpenedEvent”);

  }

  };

  class ItemClickListener implements OnItemClickListener {

  @Override

  public void onItemClick(AdapterView《?》 arg0,View arg1, int arg2, long arg3) {

  @SuppressWarnings(“unchecked”)

  HashMap《String, Object》 item = (HashMap《String, Object》) arg0

  .getItemAtPosition(arg2);

  setTitle((String) item.get(“ItemText”));

  }

  }《/span》

  這次就在基礎(chǔ)篇的基礎(chǔ)上加入拖拉功能。拖拉功能基于GestureDetector,GestureDetector的基本使用方式不是本文介紹的重點(diǎn),有興趣的童鞋可以上網(wǎng)查詢相關(guān)的教程。

本文的抽屜控件相對(duì)于基礎(chǔ)篇的抽屜控件多了以下功能:

  1.支持手勢(shì)拖拉

  2.拖拉到一半時(shí),可以自動(dòng)展開或者收縮。

  具體如下圖:

  

  本文的源碼可以到這里下載:http://download.csdn.net/detail/hellogv/3642418[!--empirenews.page--]

  只貼出抽屜組件的源碼,其他源文件與基礎(chǔ)篇的一樣:

 ?。踛ava] view plaincopyprint?

  《span style=“font-family:Comic Sans MS;font-size:18px;”》public class Panel extends LinearLayout implements GestureDetector.OnGestureListener{

  public interface PanelClosedEvent {

  void onPanelClosed(View panel);

  }

  public interface PanelOpenedEvent {

  void onPanelOpened(View panel);

  }

  private final static int HANDLE_WIDTH=30;

  private final static int MOVE_WIDTH=20;

  private Button btnHandler;

  private LinearLayout panelContainer;

  private int mRightMargin=0;

  private Context mContext;

  private GestureDetector mGestureDetector;

  private boolean mIsScrolling=false;

  private float mScrollX;

  private PanelClosedEvent panelClosedEvent=null;

  private PanelOpenedEvent panelOpenedEvent=null;

  public Panel(Context context,View otherView,int width,int height) {

  super(context);

  this.mContext=context;

  //定義手勢(shì)識(shí)別

  mGestureDetector = new GestureDetector(mContext,this);

  mGestureDetector.setIsLongpressEnabled(false);

  //改變Panel附近組件的屬性

  LayoutParams otherLP=(LayoutParams) otherView.getLayoutParams();

  otherLP.weight=1;

  otherView.setLayoutParams(otherLP);

  //設(shè)置Panel本身的屬性

  LayoutParams lp=new LayoutParams(width, height);

  lp.rightMargin=-lp.width+HANDLE_WIDTH;

  mRightMargin=Math.abs(lp.rightMargin);

  this.setLayoutParams(lp);

  this.setOrientation(LinearLayout.HORIZONTAL);

  //設(shè)置Handler的屬性

  btnHandler=new Button(context);

  btnHandler.setLayoutParams(new LayoutParams(HANDLE_WIDTH,height));

  //btnHandler.setOnClickListener(handlerClickEvent);

  btnHandler.setOnTouchListener(handlerTouchEvent);

  this.addView(btnHandler);

  //設(shè)置Container的屬性

  panelContainer=new LinearLayout(context);

  panelContainer.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,

  LayoutParams.FILL_PARENT));

  this.addView(panelContainer);

  }

  private View.OnTouchListener handlerTouchEvent=new View.OnTouchListener() {

  @Override

  public boolean onTouch(View v, MotionEvent event) {

  if(event.getAction()==MotionEvent.ACTION_UP && //onScroll時(shí)的ACTION_UP

  mIsScrolling==true)

  {

  LayoutParams lp=(LayoutParams) Panel.this.getLayoutParams();

  if (lp.rightMargin 》= (-mRightMargin/2)) {//往左超過(guò)一半

  new AsynMove().execute(new Integer[] { MOVE_WIDTH });// 正數(shù)展開

  }

  else if (lp.rightMargin 《 (-mRightMargin/2)) {//往右拖拉

  new AsynMove().execute(new Integer[] { -MOVE_WIDTH });// 負(fù)數(shù)收縮

  }

  }

  return mGestureDetector.onTouchEvent(event);

  }

  };

  /**

  * 定義收縮時(shí)的回調(diào)函數(shù)

  * @param event

  */

  public void setPanelClosedEvent(PanelClosedEvent event)

  {

  this.panelClosedEvent=event;

  }

  /**

  * 定義展開時(shí)的回調(diào)函數(shù)

  * @param event

  */

  public void setPanelOpenedEvent(PanelOpenedEvent event)

  {

  this.panelOpenedEvent=event;

  }

  /**

  * 把View放在Panel的Container

  * @param v

  */

  public void fillPanelContainer(View v)

  {

  panelContainer.addView(v);

  }

  /**

  * 異步移動(dòng)Panel

  * @author hellogv

  */

  class AsynMove extends AsyncTask《Integer, Integer, Void》 {

  @Override

  protected Void doInBackground(Integer.。。 params) {

  int times;

  if (mRightMargin % Math.abs(params[0]) == 0)// 整除

  times = mRightMargin / Math.abs(params[0]);

  else

  // 有余數(shù)

  times = mRightMargin / Math.abs(params[0]) + 1;

  for (int i = 0; i 《 times; i++) {

  publishProgress(params);

  try {

  Thread.sleep(Math.abs(params[0]));

  } catch (InterruptedException e) {

  // TODO Auto-generated catch block

  e.printStackTrace();

  }

  }

  return null;

  }

  @Override

  protected void onProgressUpdate(Integer.。。 params) {

  LayoutParams lp = (LayoutParams) Panel.this.getLayoutParams();

  if (params[0] 《 0)

  lp.rightMargin = Math.max(lp.rightMargin + params[0],

 ?。?mRightMargin));

  else

  lp.rightMargin = Math.min(lp.rightMargin + params[0], 0);

  if(lp.rightMargin==0 && panelOpenedEvent!=null){//展開之后

  panelOpenedEvent.onPanelOpened(Panel.this);//調(diào)用OPEN回調(diào)函數(shù)

  }

  else if(lp.rightMargin==-(mRightMargin) && panelClosedEvent!=null){//收縮之后

  panelClosedEvent.onPanelClosed(Panel.this);//調(diào)用CLOSE回調(diào)函數(shù)

  }

  Panel.this.setLayoutParams(lp);

  }

  }

  @Override

  public boolean onDown(MotionEvent e) {

  mScrollX=0;

  mIsScrolling=false;

  return false;

  }

  @Override

  public boolean onSingleTapUp(MotionEvent e) {

  LayoutParams lp = (LayoutParams) Panel.this.getLayoutParams();

  if (lp.rightMargin 《 0)// CLOSE的狀態(tài)

  new AsynMove().execute(new Integer[] { MOVE_WIDTH });// 正數(shù)展開

  else if (lp.rightMargin 》= 0)// OPEN的狀態(tài)

  new AsynMove().execute(new Integer[] { -MOVE_WIDTH });// 負(fù)數(shù)收縮

  return false;

  }

  @Override

  public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,

  float distanceY) {

  mIsScrolling=true;

  mScrollX+=distanceX;

  LayoutParams lp=(LayoutParams) Panel.this.getLayoutParams();

  if (lp.rightMargin 《 -1 && mScrollX 》 0) {//往左拖拉

  lp.rightMargin = Math.min((lp.rightMargin + (int) mScrollX),0);

  Panel.this.setLayoutParams(lp);

  Log.e(“onScroll”,lp.rightMargin+“”);

  }

  else if (lp.rightMargin 》 -(mRightMargin) && mScrollX 《 0) {//往右拖拉

  lp.rightMargin = Math.max((lp.rightMargin + (int) mScrollX),-mRightMargin);

  Panel.this.setLayoutParams(lp);

  }

  if(lp.rightMargin==0 && panelOpenedEvent!=null){//展開之后

  panelOpenedEvent.onPanelOpened(Panel.this);//調(diào)用OPEN回調(diào)函數(shù)

  }

  else if(lp.rightMargin==-(mRightMargin) && panelClosedEvent!=null){//收縮之后

  panelClosedEvent.onPanelClosed(Panel.this);//調(diào)用CLOSE回調(diào)函數(shù)

  }

  Log.e(“onScroll”,lp.rightMargin+“”);

  return false;

  }

  @Override

  public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,

  float velocityY) {return false;}

  @Override

  public void onLongPress(MotionEvent e) {}

  @Override

  public void onShowPress(MotionEvent e) {}

  }

  《/span》

 

三、Android智能手機(jī)藍(lán)牙通信功能開發(fā):BluetoothChat例程分析

  1. 概述

  Bluetooth 是幾乎現(xiàn)在每部手機(jī)標(biāo)準(zhǔn)配備的功能,多用于耳機(jī) mic 等設(shè)備與手機(jī)的連接,除此之外,還可以多部手機(jī)之間建立 bluetooth 通信,本文就通過(guò) SDK 中帶的一個(gè)聊天室的例程,來(lái)介紹一下 Android 上的 Bluetooth 的開發(fā)。

  在 Android1.x 的時(shí)候,相關(guān) API 非常不完善,還不能簡(jiǎn)單的使用 Bluetooth 開發(fā),有一個(gè)開源項(xiàng)目可以幫助程序員使用、開發(fā)藍(lán)牙,支持直接方法 bluetooth 協(xié)議棧。在 Android2 以后,框架提供了一些官方 API 來(lái)進(jìn)行藍(lán)牙的通信,但目前的程序也比較不完善。本文主要討論 Android2 后的 Bluetooth 通信的 API 使用方法。

  首先看聊天室的效果圖:

  

  2. Bluetooth 通信 API 介紹

  2.1. Bluetooth 通信過(guò)程

  

  2.2. Bluetooth API 的主要方法

  BluetoothAdapter 類

  BluetoothAdapter.getDefaultAdapter() :得到本地默認(rèn)的 BluetoothAdapter ,若返回為 null 則表示本地不支持藍(lán)牙;

  isDiscovering() :返回設(shè)備是否正在發(fā)現(xiàn)周圍藍(lán)牙設(shè)備;

  cancelDiscovery() :取消正在發(fā)現(xiàn)遠(yuǎn)程藍(lán)牙設(shè)備的過(guò)程;

  startDiscovery() :開始發(fā)現(xiàn)過(guò)程;

  getScanMode() :得到本地藍(lán)牙設(shè)備的 Scan Mode ;

  getBondedDevices() :得到已配對(duì)的設(shè)備;

  isEnabled() :藍(lán)牙功能是否啟用。[!--empirenews.page--]

  當(dāng)發(fā)現(xiàn)藍(lán)牙功能未啟用時(shí),如下調(diào)用設(shè)置啟用藍(lán)牙:

  

  如果發(fā)現(xiàn)當(dāng)前設(shè)備沒有打開對(duì)外可見模式,則傳遞 Intent 來(lái)調(diào)用打開可發(fā)現(xiàn)模式,代碼如下:

  

  BluetoothDevice 類,此為對(duì)應(yīng)的遠(yuǎn)程藍(lán)牙 Device

  createRfcommSocketToServiceRecord() :創(chuàng)建該 Device 的 socket 。

  BluetoothSocket 類

  connect() :請(qǐng)求連接藍(lán)牙。

  getInputStream() :得到輸入流,用于接收遠(yuǎn)程方信息。

  getOutputStream() :得到輸出流,發(fā)送給遠(yuǎn)程方的信息。

  close() :關(guān)閉藍(lán)牙連接。

  InputStream 類:

  read(byte[]) :以阻塞方式讀取輸入流。

  OutputStream 類:

  write(byte[]) :將信息寫入該輸出流,發(fā)送給遠(yuǎn)程。

  3. BluetoothChat 例程分析

  Google 提供的關(guān)于 Bluetooth 開發(fā)的例程為 Bluetoothchat ,使用截圖可見本文一開始。除去配置及 ui 定義等文件,主程序文件共三個(gè): BluetoothChat.java 、 BluetoothChatService.java 以及 DeviceListActivity.java ,詳細(xì)功能可見下面的描述。

  3.1. 整體調(diào)用關(guān)系序列圖

  

  3.2. BluetoothChat.java

  例程的主 Activity 。 onCreate() 得到本地 BluetoothAdapter 設(shè)備,檢查是否支持。 onStart() 中檢查是否啟用藍(lán)牙,并請(qǐng)求啟用,然后執(zhí)行 setupChat() 。 setupChat() 中先對(duì)界面中的控件進(jìn)行初始化增加點(diǎn)擊監(jiān)聽器等,然創(chuàng)建 BluetoothChatService 對(duì)象,該對(duì)象在整個(gè)應(yīng)用過(guò)程中存在,并執(zhí)行藍(lán)牙連接建立、消息發(fā)送接受等實(shí)際的行為。

  3.3. BluetoothChatService.java

  public synchronized void start() :

  開啟 mAcceptThread 線程,由于樣例程序是僅 2 人的聊天過(guò)程,故之前先檢測(cè) mConnectThread 和 mConnectedThread 是否運(yùn)行,運(yùn)行則先退出這些線程。

  public synchronized void connect(BluetoothDevice device) :

  取消 CONNECTING 和 CONNECTED 狀態(tài)下的相關(guān)線程,然后運(yùn)行新的 mConnectThread 線程。

  public synchronized void connected(BluetoothSocket socket, BluetoothDevice device) :

  開啟一個(gè) ConnectedThread 來(lái)管理對(duì)應(yīng)的當(dāng)前連接。之前先取消任意現(xiàn)存的 mConnectThread 、 mConnectedThread 、 mAcceptThread 線程,然后開啟新 mConnectedThread ,傳入當(dāng)前剛剛接受的 socket 連接。最后通過(guò) Handler 來(lái)通知 UI 連接 OK 。

  public synchronized void stop() :

  停止所有相關(guān)線程,設(shè)當(dāng)前狀態(tài)為 NONE 。

  public void write(byte[] out) :

  在 STATE_CONNECTED 狀態(tài)下,調(diào)用 mConnectedThread 里的 write 方法,寫入 byte 。

  private void connectionFailed() :

  連接失敗的時(shí)候處理,通知 ui ,并設(shè)為 STATE_LISTEN 狀態(tài)。

  private void connectionLost() :

  當(dāng)連接失去的時(shí)候,設(shè)為 STATE_LISTEN 狀態(tài)并通知 ui 。

  內(nèi)部類:

  private class AcceptThread extends Thread :

  創(chuàng)建監(jiān)聽線程,準(zhǔn)備接受新連接。使用阻塞方式,調(diào)用 BluetoothServerSocket.accept() 。提供 cancel 方法關(guān)閉 socket 。

  private class ConnectThread extends Thread :

  這是定義的連接線程,專門用來(lái)對(duì)外發(fā)出連接對(duì)方藍(lán)牙的請(qǐng)求和處理流程。構(gòu)造函數(shù)里通過(guò) BluetoothDevice.createRfcommSocketToServiceRecord() ,從待連接的 device 產(chǎn)生 BluetoothSocket. 然后在 run 方法中 connect ,成功后調(diào)用 BluetoothChatSevice 的 connected() 方法。定義 cancel() 在關(guān)閉線程時(shí)能夠關(guān)閉相關(guān) socket 。

  private class ConnectedThread extends Thread :

  這個(gè)是雙方藍(lán)牙連接后一直運(yùn)行的線程。構(gòu)造函數(shù)中設(shè)置輸入輸出流。 Run 方法中使用阻塞模式的 InputStream.read() 循環(huán)讀取輸入流, 然后 post 到 UI 線程中更新聊天消息。也提供了 write() 將聊天消息寫入輸出流傳輸至對(duì)方,傳輸成功后回寫入 UI 線程。最后 cancel() 關(guān)閉連接的 socket 。

  3.4. DeviceListActivity.java

  該類包含 UI 和操作的 Activity 類,作用是得到系統(tǒng)默認(rèn)藍(lán)牙設(shè)備的已配對(duì)設(shè)備列表,以及搜索出的未配對(duì)的新設(shè)備的列表。然后提供點(diǎn)擊后發(fā)出連接設(shè)備請(qǐng)求的功能。

  除了 RFCOMM 通信外, Android 上關(guān)于 Bluetooth 的還有 SDP 、 GAP 、耳機(jī)設(shè)備連接等內(nèi)容,本文還未涉及,將會(huì)隨著藍(lán)牙相關(guān) API 在新版本中的進(jìn)一步完善來(lái)學(xué)習(xí)使用。

 

四、Android智能手機(jī)平臺(tái)多分辨率解決方案詳解

  摘 要:近年來(lái),智能手機(jī)的功能越來(lái)越強(qiáng)大,移動(dòng)終端應(yīng)用程序?qū)映霾桓F,移動(dòng)互聯(lián)網(wǎng)改變?nèi)藗兊纳?。Android 系統(tǒng)是開放手機(jī)聯(lián)盟推出的一款開源的手機(jī)操作系統(tǒng),正是由于其開放性,沒有采用Windows PhONe7 類似的硬件限定標(biāo)準(zhǔn),目前基于Android系統(tǒng)的機(jī)型越來(lái)越多,一些硬件指標(biāo)出現(xiàn)了混亂的局面,其中最明顯的就是屏幕分辨率的問(wèn)題。如何使開發(fā)者的應(yīng)用程序盡可能多地適應(yīng)多種分辨率,正是本文要講述的問(wèn)題。文章首先介紹Android 的系統(tǒng)架構(gòu),然后介紹Android 平臺(tái)中分辨率的相關(guān)術(shù)語(yǔ),之后重點(diǎn)講述在開發(fā)過(guò)程中如何部署資源以及所應(yīng)遵循的原則,最后給出測(cè)試多分辨率兼容性的方法。

  0 引 言

  2007 年11 月,Google 公司發(fā)布基于Linux2.6 內(nèi)核的移動(dòng)終端操作系統(tǒng)- Android, 由于其開源性, 得到很多手機(jī)廠商的追捧和應(yīng)用開發(fā)者的青睞。近年來(lái)智能手機(jī)發(fā)展迅速,運(yùn)行速度、存儲(chǔ)容量和可靠性等指標(biāo)有了顯著提高[1],當(dāng)今的智能手機(jī)用戶對(duì)應(yīng)用軟件的舒適性和美觀性有了更大的期望,應(yīng)用程序界面友好性已經(jīng)越來(lái)越重要。但是由于Android 的開源性,硬件廠商屏幕分辨率不統(tǒng)一,據(jù)統(tǒng)計(jì)目前市場(chǎng)上Android系統(tǒng)手機(jī)的分辨率有10 余種,分辨率分布如此廣泛使得開發(fā)者在處理多分辨率適應(yīng)方面遇到了不少難題。文章首先介紹Android 平臺(tái)的系統(tǒng)架構(gòu)及資源管理方法,之后介紹目前開發(fā)者在處理多分辨率時(shí)采用的方法,而后重點(diǎn)分析Android 平臺(tái)資源加載機(jī)制并且結(jié)合實(shí)例給出多分辨率的處理步驟及技巧,最后介紹測(cè)試多分辨率效果的方法。

  1 Android 平臺(tái)簡(jiǎn)介

  Android 是一個(gè)包括操作系統(tǒng)、中間件和關(guān)鍵應(yīng)用的移動(dòng)設(shè)備軟件堆[2],Android 系統(tǒng)和其他系統(tǒng)一樣,采用分層的架構(gòu)。由下至上依此為L(zhǎng)inux 操作系統(tǒng)和驅(qū)動(dòng)、程序庫(kù)及Android 運(yùn)行時(shí)環(huán)境、應(yīng)用程序框架層、應(yīng)用層。 Android 應(yīng)用程序的基本組件有Activity、Intent、BroadcaSTReceiver、Service 四種,各個(gè)組件的配置信息以及權(quán)限管理、版本管理等配置信息都保存在AndroidManifest.xml 中。

  1.1 Android 應(yīng)用程序資源管理

  手機(jī)界面上加載的圖片是Android 資源的一種,除此之外還有XML 資源(anim.xml layout.xml 等) 以及原數(shù)據(jù)文件( 音視頻文件等)[3]。新建一個(gè)HelloAndroid 的Android 應(yīng)用程序,默認(rèn)生成的文件架構(gòu)包含src,gen,assets,res 等文件夾,以及AndroidManifest.xml 配置文件。src 文件夾中保存的是Android 源代碼,res 文件夾代表應(yīng)用程序需要使用到的資源文件,gen 包中包含R.java 文件。Res 文件夾中包含的所有資源文件都對(duì)應(yīng)在R.java 中。

  當(dāng)開發(fā)者在res/ 目錄中任何一個(gè)子目錄中添加相應(yīng)類型的文件之后,ADT 會(huì)在R.java 文件中相應(yīng)的匿名內(nèi)部類中國(guó)自動(dòng)生成一條靜態(tài)int 類型的常量,對(duì)添加的文件進(jìn)行索引。

  Android 系統(tǒng)采取這種架構(gòu)使視圖等資源文件與控制代碼分離,實(shí)現(xiàn)松耦合。然而可以使用R.java 文件在代碼中對(duì)相應(yīng)的資源文件進(jìn)行存取,靈活操作。

  1.2 一般多分辨率處理方法及其缺點(diǎn)

  1.2.1 圖片縮放

  基于當(dāng)前屏幕的精度,平臺(tái)自動(dòng)加載任何未經(jīng)縮放的限定尺寸和精度的圖片。如果圖片不匹配,平臺(tái)會(huì)加載默認(rèn)資源并且在放大或者縮小之后可以滿足當(dāng)前界面的顯示要求。例如,當(dāng)前為高精度屏幕,平臺(tái)會(huì)加載高精度資源(如HelloAndroid中drawable-hdpi 中的位圖資源),如果沒有,平臺(tái)會(huì)將中精度資源縮放至高精度,導(dǎo)致圖片顯示不清晰。

  1.2.2 自動(dòng)定義像素尺寸和位置

  如果程序不支持多種精度屏幕,平臺(tái)會(huì)自動(dòng)定義像素絕對(duì)位置和尺寸值等,這樣就能保證元素能和精度160 的屏幕上一樣能顯示出同樣尺寸的效果。例如,要讓W(xué)VGA 高精度屏幕和傳統(tǒng)的HVGA 屏幕一樣顯示同樣尺寸的圖片,當(dāng)程序不支持時(shí),系統(tǒng)會(huì)對(duì)程序慌稱屏幕分辨率為320×480,在(10,10)到(100,100)的區(qū)域內(nèi)繪制圖形完成之后,系統(tǒng)會(huì)將圖形放大到(15,15)到(150,150)的屏幕顯示區(qū)域。

  1.2.3 兼容更大尺寸的屏幕

  當(dāng)前屏幕超過(guò)程序所支持屏幕的上限時(shí),定義supportsscreens元素,這樣超出顯示的基準(zhǔn)線時(shí),平臺(tái)在此顯示黑色的背景圖。例如,WVGA 中精度屏幕上,如程序不支持這樣的大屏幕,系統(tǒng)會(huì)謊稱是一個(gè)320×480 的,多余的顯示區(qū)域會(huì)被填充成黑色。

  1.2.4 采用OpenGL 動(dòng)態(tài)繪制圖片

  Android 底層提供了OpenGL 的接口和方法,可以動(dòng)態(tài)繪制圖片,但是這種方式對(duì)不熟悉計(jì)算機(jī)圖形學(xué)的開發(fā)者來(lái)講是一個(gè)很大的挑戰(zhàn)。一般開發(fā)游戲,采用OpenGL 方式。

  1.2.5 多個(gè)apk 文件

  Symbian 和傳統(tǒng)的J2ME 就是采用這種方式,為一款應(yīng)用提供多個(gè)分辨率版本,用戶根據(jù)自己的需求下載安裝相應(yīng)的可執(zhí)行文件。針對(duì)每一種屏幕單獨(dú)開發(fā)應(yīng)用程序不失為一種好方法,但是目前Google Market 對(duì)一個(gè)應(yīng)用程序多個(gè)分辨率版本的支持還不完善,開發(fā)者還是需要盡可能使用一個(gè)apk 文件適應(yīng)多個(gè)分辨率。
 

2 多分辨率處理方案詳解

  2.1 基本術(shù)語(yǔ)介紹

  2.1.1 屏幕尺寸

  真正的物理尺寸,屏幕對(duì)角線的長(zhǎng)度,單位是英寸。為了簡(jiǎn)化起見,Android 把支持的所有物理尺寸分成了4 組:small,normal, large, extra large.

  2.1.2 屏幕密度Density

  一定物理范圍的像素的個(gè)數(shù),單位通常是dpi(dots perinch), 即每英寸的點(diǎn)數(shù)。例如一個(gè)低分辨率屏幕相對(duì)于高分辨率屏幕在一定的物理區(qū)域內(nèi)包含的像素點(diǎn)要少。為了簡(jiǎn)化起見,Android 將所有的屏幕密度分成四組:low, medium,high 和extra high.

  2.1.3 方向Orientation

  從用戶視角來(lái)看的屏幕的方向,Portrait 縱向和Landscape 橫向。

  2.1.4 分辨率Resolution

  屏幕上所有的像素點(diǎn)數(shù)目,一般用480*800 的形式來(lái)表示。密度無(wú)關(guān)像素dp: Android 平臺(tái)中虛擬的像素單位,定義成一種密度無(wú)關(guān)的形式,像素px 和dp 的轉(zhuǎn)換公式為 px =dp*(dpi/160)。在界面開發(fā)中應(yīng)使用dp 作為像素單位,從而保證在不同的屏幕密度上控件所占的實(shí)際px 因密度而自動(dòng)調(diào)整。

  2.2 手機(jī)屏幕的分類

  Android 采用兩種標(biāo)準(zhǔn)對(duì)屏幕進(jìn)行分類。按照屏幕尺寸分為四組small, normal, large, extra large;按照屏幕密度分為四組 low, medium ,high 和extra high,其分界線如圖1所示。

  

  圖1 Android 中的屏幕分類

  為了優(yōu)化程序UI,讓其適應(yīng)多種分辨率并能清晰顯示,一般情況下需要為不同屏幕大小密度提供不同的圖片文件和對(duì)應(yīng)的布局文件,在運(yùn)行的時(shí)候,Android 系統(tǒng)會(huì)根據(jù)當(dāng)前設(shè)備的屏幕大小及密度等信息,選擇加載其中一套匹配的資源加以運(yùn)行,從而達(dá)到適應(yīng)多分辨率的效果。

  2.3 Android 支持多分辨率原理及步驟

  由以上分析,默認(rèn)的加載方式都不能很好地適應(yīng)不同的分辨率,Android 從1.6 開始支持多種分辨率的處理,原理簡(jiǎn)而言之就是根據(jù)屏幕參數(shù),動(dòng)態(tài)加載資源文件。在Android 項(xiàng)目文件結(jié)構(gòu)中,drawable 文件夾下包含三個(gè)子文件夾,分別為drawable-hdpi, drawable-mdpi, drawable-ldpi, 分別存放hdpi,mdpi,ldip 的位圖。應(yīng)用程序運(yùn)行時(shí),Android 系統(tǒng)會(huì)根據(jù)當(dāng)前設(shè)備的屏幕大小、分辨率、屏幕密度、方向、長(zhǎng)寬比等信息,選擇相應(yīng)文件夾進(jìn)行加載。Android 配置修飾符的定義規(guī)則如下:

  1)在res 文件夾下新建目錄,命名為《resources_name》-《qualifier》 這種格式,其中《resources_name》 為標(biāo)準(zhǔn)資源名稱,例如drawable 或者layout;《qualifier》 即修飾符,指定對(duì)應(yīng)的屏幕參數(shù),比如normal/small/large,hdpi/mdpi/ldpi,land/port,long/notlong 等。

  2)在步驟1 新建的文件夾中存入相應(yīng)的資源,比如位圖資源或者layout 資源,資源文件的名字必須與默認(rèn)資源文件的名字相同。例如:

  

  3)Android 系統(tǒng)支持多分辨率的機(jī)制離不開Android-Manifest.xml 文件的supports-screen 元素,若應(yīng)用程序要適應(yīng)多種分辨率,需要將anyDensity 設(shè)置為true.

  2.4 界面設(shè)計(jì)技巧

  前面的部分已經(jīng)詳細(xì)講解了如何架構(gòu)應(yīng)用程序使其更好地適應(yīng)多種分辨率屏幕,此外,在界面設(shè)計(jì)和控制中我們還應(yīng)該掌握一些原則或者技巧,從而使應(yīng)用程序界面友好、適應(yīng)性強(qiáng)。

  1)在XML layout 文件中定義長(zhǎng)度的時(shí)候,最好使用wrap_content,fill_parent, 或者dp 進(jìn)行描述,這樣可以保證在屏幕上面展示的時(shí)候有合適的大小。例如,一個(gè)view layout_width=“100dip”,在 HVGA@160 density 的設(shè)備上顯示100 個(gè)px,而在 WVGA@240 density 的設(shè)備上顯示150 個(gè)px,但是所占的物理尺寸時(shí)相同的。

  2)在Activity 或者其他控制視圖加載的代碼處,不要使用像素單位的硬編碼。

  3)不要使用AbsoluteLayout.絕對(duì)布局是由AndroidUI toolkit 提供的布局容器中的一種。但是與其他layout 不一樣的是,AbsoluteLayout 使用固定的位置表示,使得在不同的屏幕上面顯示效果不好,因此AbsoluteLayout 在sdk1.6 及以后的版本中被棄用了。

  4)為不同屏幕密度的手機(jī),提供不同的位圖資源,可以使得界面清晰無(wú)縮放。

  3 多分辨率兼容性測(cè)試方案

  在產(chǎn)品發(fā)行之前,要在所有的目標(biāo)手機(jī)上進(jìn)行全面的測(cè)試。Android SDK 包含了一套測(cè)試多分辨率的機(jī)制。可以自己定制avd 作為應(yīng)用程序的測(cè)試環(huán)境,avd 會(huì)模擬真實(shí)機(jī)器的屏幕大小和密度。例如圖2 為模擬器的列表,可以將程序運(yùn)行在這四個(gè)模擬器中進(jìn)行多分辨率的測(cè)試。

  

  圖2 虛擬機(jī)列表

  4 結(jié)論

  本文介紹Android 平臺(tái)的體系架構(gòu)和分辨率相關(guān)的術(shù)語(yǔ),詳細(xì)論述資源加載原理和多分辨率處理的詳細(xì)流程,最后給出了測(cè)試應(yīng)用程序是否適應(yīng)多種分辨率的方法。結(jié)合本人實(shí)踐經(jīng)驗(yàn)進(jìn)行講述,內(nèi)容深入淺出,較完整地論述了如何使應(yīng)用程序盡可能多地適應(yīng)多種分辨率屏幕。

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

倫敦2024年8月29日 /美通社/ -- 英國(guó)汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時(shí)1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動(dòng) BSP

北京2024年8月28日 /美通社/ -- 越來(lái)越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時(shí)企業(yè)卻面臨越來(lái)越多業(yè)務(wù)中斷的風(fēng)險(xiǎn),如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報(bào)道,騰訊和網(wǎng)易近期正在縮減他們對(duì)日本游戲市場(chǎng)的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)開幕式在貴陽(yáng)舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語(yǔ)權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對(duì)環(huán)境變化,經(jīng)營(yíng)業(yè)績(jī)穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤(rùn)率延續(xù)升勢(shì) 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長(zhǎng) 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競(jìng)爭(zhēng)力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競(jìng)爭(zhēng)優(yōu)勢(shì)...

關(guān)鍵字: 通信 BSP 電信運(yùn)營(yíng)商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺(tái)與中國(guó)電影電視技術(shù)學(xué)會(huì)聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會(huì)上宣布正式成立。 活動(dòng)現(xiàn)場(chǎng) NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長(zhǎng)三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會(huì)上,軟通動(dòng)力信息技術(shù)(集團(tuán))股份有限公司(以下簡(jiǎn)稱"軟通動(dòng)力")與長(zhǎng)三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉