android核心技術與最佳實踐筆記(三)

第七章  深入解析android網絡編程
       第九章  android多媒體編程
9.1  音頻處理
 
       上述爲google爲開發者提供的音頻開發框架,在應用層,開發者可以調用MediaPlayer,  MediaRecorder,  SoundPool 等進行音頻的播放記錄以及遊戲的特效音製作等。在框架層,AudioFlinger,  AudioPolicyManager,  AudioSerivce  AudioHardwareInterface構成了對音頻控制的基本骨架。框架層本身包括java,C++兩部分,並通過JNI 進行通信,其中對服務的調用是基於C/S框架實現的。內核層提供不同音頻設備的驅動和ALSA框架等。音頻本身的播放涉及的多媒體引擎是 Stagefright。
       AudioPolicyService 所負責的工作在早期版本中是在AudioFlinger中實現的。隨着場景的複雜化,Google 將關於音頻設備的連接狀態,音頻的併發策略,音量的處理等工作放置到  AudioPolicyService 中,而AudioFlinger更側重於音頻流的控制 AudioHardwareInterface則是對不同設備產商的音頻設備的驅動與框架層的適配接口,爲框架層提供一個與驅動通信的統一接口
       下面對音頻播放,音頻錄製,音頻管理,音效處理等幾個方面進行介紹:
 9.1.1  音頻播放
     根據播放方式的不同,android爲應用層提供了多個播放接口,如MediaPlayer,  SoundPool ,  AudioTrack ,  AsyncPlayer,  JetPlayer, ToneGenerator 等, 他們適用於不同的場景。
  1.  基於MediaPlayer 播放
      功能強大,對音頻視頻都支持,爲保證播放期間系統正常工作,需要設置 android.permission.WAKE_LOCK權限
      MediaPlayer支持的音頻格式包括 AAC, AMR,  FLAC  MP3  MIDI,  OGG,  PCM等。
      在原生層 MediaPlayer 由狀態機控制,其狀態機如下圖:
            
        注意: 當調用reset()方法時,MediaPlayer會返回到MEDIA_PLAYER_IDLE狀態,除了圖中所示的狀態外,MediaPlayer還存在一個出錯的狀態,即 Media_Player_STATE_ERROR。監聽出錯的監聽器爲 MediaPlayer.OnErrorListener。
        對於背景音樂,將MediaPlayer封裝在Service中即可實現。
        MediaPlayer支持元數據,音頻文件,音頻流等形式的源數據的播放。
  (1)播放元數據
        所謂元數據即 raw 文件夾下的資源文件,調用方法如下: 
               mMediaPlayer = MediaPlayer.create( this,  R.raw.test.cbr );
               mMediaPlayer.start();
  (2)播放音頻文件
       對於本地文件,其數據源可通過 Uri 來表示,在開始播放前需要設置播放類型並加載緩衝,實例如下:
               Uri  myUri = ... ;
               MediaPlayer  mediaPlayer = new MediaPlayer();
               mediaPlayer.setAudioStreamType( AudioManager.STREAM_MUSIC );
               mediaPlayer.setDataSource( getApplicationContext(),  myUri );
               mediaPlayer.prepare(); //加載緩衝
               mediaPlayer.start();
  (3)播放流
       MediaPlayer 支持基於網絡的流播放,其支持的協議包括 RSTP,  HTTP漸進流,  HTTP生活流等。在網絡播放和本地文件播放略有不同,實例如下:
              String  url =  "http://zhangmenshiting.baidu.com/****.mp3";
              MediaPlayer  mediaPlayer = new MediaPlayer();
              mediaPlayer.setAudioStreamType( AudioManager.STREAM_MUSIC );
              mediaPlayer.setDataSource( url );
              mediaPlayer.prepareAsync();
              ..................     mediaPlayer.start();
       考慮到網絡情況的發雜性,以及獲取數據和解碼的時間比較長,不推薦在播放流時通過  prepare() 方法加載緩衝,尤其不能再UI主線程中調用。應通過 prepareAsync() 方法將緩衝的工作放置在非UI主線程中進行,當準備完成時,MediaPlayer通過 MediaPlayer.OnPreparedListener 監聽器可以監聽到該時間。設置方法如下:
             mMediaPlayer.setOnpreparedListener( mPreparedListener );
        通常在準備工作完成後開始進行播放。監聽器處理加載緩衝結束的消息的方法:
             MediaPlayer.OnPreparedListener  mPreparedListener = new MediaPlayer.onPreparedListener(){
                     public  void  onPrepared( MediaPlayer mp ){
                              mediaPlayer.start();
                     }
             }
       當播放結束時,通過MediaPlayer.OnCompletionListener 可以監聽到播放結束的消息,實例如下:
             mMediaPlayer.setOnCompletionListenr( new  MediaPlayer.OnCompletionListener(){
                      public  void  onCompletion( MediaPlayer  mediaPlayer ){
                      }
             } );
 2.  基於SoundPool 播放
        SoundPool 能夠播放音頻流的組合音,這對遊戲應用很有用,其對應的JNI接口爲 android_media_SoundPool.cpp
        SoundPool 可通過APK包中的資源文件或文件系統中的文件將音頻資源加載到內存中。在底層實現上,SoundPool 通過媒體播放服務(MediaPlaybackService)可以將音頻資源解碼爲一個 16位的單聲道或立體聲的 PCM流,使應用避免了再回放過程中進行解碼造成的延遲。
       除了回放過程中延遲小的優點外,SoundPool 還能夠對一定數量的音頻流進行同時播放。當要播放的音頻流數量超過SoundPool 所設置的最大值時,SoundPool 將會停止已播放的一條低優先級的音頻流。SoundPool 對最大音頻流數量的設置(默認爲32),可避免CPU過載。
       對遊戲等應用而言,MediaPlayer會使性能減低。在android中,專門提供了SoundPool 類來執行此類音頻播放,SoundPool 類佔用的CPU資源較少,反應較快。
       與其他音頻播放類相比,SoundPool 類可自行設置音頻播放時的品質,音量,播放速率等,並可管理多個音頻流,每個流均擁有自己獨立的ID,對單個音頻流的管理均是通過其ID 來進行的。 SoundPool 類使用的場景包括應用程序中的音效。
       SoundPool 組合音頻流的實例如下:
              int  srcQulity = 100;
              mSoundPool = new SoundPool( SOUNDPOOL_STREAMS,  AudioManager.STREAM_MUSIC, srcQuality );//創建SoundPool對象
              //架子音頻流
              int  sampleId1 = mSoundPool.load( mContext,  R.raw.a_4,  PRIORITY );
              AssetFileDescriptor  afd = mContext.getResources().openRawResourceFd( R.raw.c_sharp_5 );
              int  smapleId2 = mSoundPool.load( afd,  PRIORITY );
              FileDescriptor  fd = afd.getFileDescriptor();
              long  offset = afd.getStartOffset();
              long  length = afd.getLength();
              int  sampleId3;
              sampleId3 = mSoundPool.load( fd,  offset,  length,  PRIORITY );
              String  path = mFile.getAbsolutePath();
              int  sampleId4;
              sampleId4 = mSoundPool.load( path,  PRIORITY );
              mSoundPool.unload( sampleId4 );
     通過 SoundPool 播放某音頻流的方法如下:
              float  leftVolume = SILENT;
              float  rightVolume = LOUD;
              int  priority = 1;
              int  loop = 0 ;
              int  rate = 1f;
              int  streamID = mSoundPool.play( sampleID,  leftVolume,  rightVolume, priority, loop, rate );
3. 基於AudioTrack播放
        AudioTrack主要用於管理單個音頻,這是一個較底層的音頻播放方法。在構建AudioTrack時,需要明確流類型,採樣率,通道配置,音頻格式,緩衝大小,播放模式等參數。
        AudioTrack 支持 STREAM_VOICE_CALL,  STREAM_SYSTEM,  STREAM_RING,  STREAM_MUSIC 和 STREAM_ALARM等流類型。
        AudioTrack支持 44100Hz  , 22050Hz,  11025Hz等採樣率。
        AudioTrack支持單聲道(CHANNEL_OUT_MONO)和立體聲(CHANNEL_OUT_STEREO)兩種聲道。
        AudioTrack支持 ENCODING_PCM_16BIT 和 ENCODING_PCM_8BIT 兩種編碼格式。
        AudioTrack支持靜態模式(Static  Mode)和流模式(Streaming Mode)兩種播放模式。靜態模式由於沒有從java層向原生層傳遞數據造成的延遲,因此時延很小。當然,受限於音頻緩衝的大小,靜態模式常在遊戲場景中用於播放時長很短的音頻資源。當音頻流較大不足以在音頻緩衝證一些寫入時,可採用流模式。AudioTrack的應用實例如下:
              track = new AudioTrack(
                        AudioManager.STREAM_MUSIC,  44100,  AudioFormat.CHANNEL_OUT_MONO,  
                        AudioFormat.ENCODING_PCM_16BIT,  AudioTrack.getMinBufferSize(44100, 
                        AudioFormat.CHANNEL_OUT_MONO),  AudioTrack.MODE_STREAM);
       AudioTrack的播放狀態包括 PLAYSTATE_STOPPED,  PLAYSTATE_PAUSED,  PLAYSTATE_PLAYING 等。
       AudioTrack示例的狀態包括 STATE_INITIALIZED,  STATE_NO_STATIC_DATA,  STATE_UNINITIALIZED 等。
       向音頻緩衝中添加數據的方法爲  write(), 格式如下:
               public  int  write( short[] audioData,  int offsetInShorts, int sizeInShorts )
       通過AudioTrack.OnPlaybackPositionUpdateListener監聽器可以監聽播放進度。
 4. 基於AsyncPlayer播放
        對於不希望阻塞當前線程的簡單播放,可以考慮 AsyncPlayer,從而使播放線程可以從容加載緩衝。實例如下
                final  Uri  PLAY_URI = Setings.System.DEFAULT_NOTIFICATION_URI;
                AsyncPlayer  asyncPlayer = new  AsyncPlayer( null );
                asyncPlayer.play( getContext(),  PLAY_URI,  true, AudioManager.STREAM_RING );
                final  int  PLAY_TIME = 3000;
                Thread.sleep( PALY_TIME );
                asyncPlayer.stop(  );
         雖然底層依然是通過MediaPlayer進行播放的,但是 AsyncPlayer 支持的播放控制有限。
  7. 基於Ringtone播放
         Ringtone和RingtoneManager 爲鈴聲,提示音,鬧鐘等提供了快速播放及管理的接口。示例如下:
                final  ContentResolver  cr = mContext.getContentResolver();
                whichSound = Settings.System.CAR_DOCK_SOUND;
                final  String  soundPath = Settings.System.getString( cr,  whichSound );
                if( soundPath != null ){
                        final  Uri soundUri = Uri.parse( "file://" + soundPath );
                        if( soundUri != null ){
                                final  Ringtone  sfx = RingtoneManager.getRingtone( mContext,  soundUri );
                                if( sfx != null ){
                                        sfx.setStreamType( AudioManager.STREAM_SYSTEM );
                                        sfx.play();
                                }
                        } 
                }
 9.1.2 音頻錄製
       android僅提供了MediaRecorder,  AudioRecord 兩種接口用於音頻錄製。
         爲了錄製音頻,必須設置音源,輸出路徑,錄製格式等。其中音源由 MediaRecorder.AudioSource 統一定義。音源包括 MIC, VOICE_UPLINK(通話中自己的聲音), VOICE_DOWNLINK(通話中對方的聲音), VOICE_CALL(通話中雙方的聲音), CAMCORDER,  VOICE_RECOGNITION 和 VOICE_COMMUNICATION。
    1.  基於MediaRecorder錄製
             通過MediaRecorder錄製音頻的方法如下: 
                  mMediaRecorder = new MediaRecorder();
                  mMediaRecorder.setAudioSource( MediaRecorder.AudioSource.MIC );
                  mMediaRecorder.setOutputFormat( MediaRecorder.OutputFormat.THREE_GPP ); //3GPP格式
                  mMediaRecorder.setAudioEncoder( MediaRecorder.AudioEncoder.AMR_NB );//設置編碼器
                  mMediaRecorder.setOutputFile( AUDIO_CAPTURE_PATH );//設置輸出路徑
                  try{
                           mMediaRecorder.prepare();//加載緩衝
                  }catch( SecurityException  e ){
                           return;
                  }catch......
                  try{
                           mMediaRecorder.start();
                  }catch( SecurityException e ){
                           return;
                  }
        MediaRecorder支持的音頻編碼包括 AMR_NB,  AMR_WB和ACC等,支持的輸出格式。
   2. 基於AudioRecord錄製
            方法如下:
                  final  int  minBufferSize = AudioRecord.getMinBufferSize( 16000, AudioFormat.CHANNEL_IN_MONO, 
                                     AudioFormat.ENCODING_PCM_16BIT );
                  AudioRecord  mRecord = new  AudioRecord( MediaRecorder.AudioSource.VOICE_RECOGNITION, 
                                     16000,  AudioFormat.CHANNEL_IN_MONO,  AudioFormat.ENCODING_PAC_16BIT,  minBufferSize );
                  mRecord.startRecording();
9.1.3 音頻管理
      音頻管理主要通過AudioManager來進行,可以進行設備,音量,資源競爭,音效播放等方面的管理工作。
  1. 設備管理
        AudioManager的強大主要體現在對藍牙,揚聲器,麥克風,耳機振動等音頻相關設備的管理等方面。對音頻管理的主要方法如下:
                public   void   setBluetoothScoOn( boolean  on );
                public   void   setMicrophoneMute( boolean  on );
                public   void   setSpeakerPhoneOn( boolean  on );
                public   void   setVibrateSetting( int vibrateType,  int  vibrateSetting );
        當用於正在聽音樂,無意使耳機與終端斷開,這是需要處理Action爲 android.media.AUDIO_BECOMING_NOISY的廣播消息,暫停播放。
  2. 音量調節
        AudioManager支持對特定流或整體的音量調節,方法如下:
                public  void adjustStreamVolume( int  streamType,  int direction,  int flags );
                public  void  adjustVolume( int direction, int flags );
        上述代碼中,音量調節的方向包括 ADJUST_RAISE, ADJUST_LOWER,  ADJUST_SAME等,標誌位可以爲FLAG_SHOW_UI,  FLAG_ALLOW_RINGER_MODES,  FLAG_PALY_SOUND,  FLAG_VIBRATE,  FLAG_REMOVE_SOUND_AND_VIBRATE等。實例如下
                if( mAudioManager.isMusicActive() ){ //判斷是否有音樂在播放
                          mAudioManager.adjustStreamVolume( AudioManager.STREAM_MUSIC,  keyCode == KeyEvent.KEYCODE_VOLUME_UP ?                            AudioManager.ADJUST_RAISE :  AudioManager.ADJUST_LOWER, 0 ); 
                }
   3. 資源競爭
         當需要資源時,應用通過 AudioManager 的  requestAudioFocus() 方法獲得 Audio Focus, 一旦失去了 Audio Focus,當前的播放會被剝奪資源。下面是請求Audio Focus的實例:
                mAudioManager.requestAudioFocus( mAudioFocusListener,  AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN );
          上述代碼中,mAudioFocusListener 爲 AudioManager.OnAudioFocusChangeListener監聽器,其處理Audio Focus變化的方法如下: 
                private  OnAudioFocusChangeListener mAudioFocusListneer = new
                     OnAudioFocusChangeListener(){
                             public  void  onAudioFocusChange( int  focusChange ){
                             }
                     };
   4.  音效播放
        AudioManager還支持音效播放,方法爲 playSoundEffect()。在播放音效前,必須調用 loadSoundEffects() 方法,即創建一個SoundPool 對象來執行實際的播放,實例如下:
                mAudioManager.loadSoundEffects();
                float  volume = 13;
                mAudioManager.playSoundEffect( SoundEffectConstants.CLICK );
                mAudioManager.playSoundEffect( AudioManager.FX_FOCUS_NAVIGATION_UP );
                mAudioManager.playSoundEffect( AudioManager.FX_FOCUS_NAVIGATION_DOWN, volume );
                mAudioManager.uploadSoundEffects();
                mAudioManager.playSoundEffect( AudioManager.FX_KEY_CLICK );
                mAudioManager.playSoundEffect( AudioManager.FX_FOCUS_NAVIGATION_UP );
                mAudioManager.playSoundEffect( AudioManager.FX_FOCUS_NAVIGATION_DOWN, volume );
9.1.4 音效處理
         android對音頻提供的音效支持,其實現位於android.media.audiofx包中。

9.2 視頻錘鍊
         大致可分爲視頻播放,視頻記錄和視頻電話3部分, 其中視頻播放主要基於 MediaPlayer 進行,視頻錄製主要通過MediaRecorder進行。

  第十一章 android安全框架解析
       android主要的安全機制包括java混淆器,接入權限,數字證書,SSL,數據庫安全,虛擬機(安全沙箱),文件訪問控制等。android的安全框架如圖:
          
           代碼的安全主要是通過java混淆器實現。
           基於SSL可以實現安全的網絡通信。但數據的安全性在android中需要依據不同的應用場景採用不同的方案。
           在文件訪問控制方面,系統運行時,文件的訪問控制由linux系統提供,其中system.img所在的分區是隻讀的,不允許用戶寫入,而data.img所在的分區是可讀寫的,用於存放用戶數據。分區的用戶權限在 init.rc中定義。
11.1  java混淆器
       處於跨平臺的需求,java的運行和C /C++不同,java是通過java虛擬機託管字節碼來運行的。正式由於這一特性,java存在着可反編譯的特徵。java混淆器的目的在於打開字節碼的佈局,增加反編譯的難度。目前android自帶的java混淆器爲  proguard
       在源代碼編譯中,如果希望利用java混淆器,方法在android.mk中增加如下配置:
               LOCAL_PROGUARD_FLAG_FILES := proguard.flags
       proguard.flags聲明瞭不進行混淆的類或類的特定方法。如下是launcher2的proguard.flags的實現:
               //特定方法
               -keep class com.android.launcher2.Launcher{
                       public  void  previousScreen(android.view.View);
                       public  void  nextScreen(android.view.View);
                       public  void  launchHotSeat(android.view.View);
               }
               //特定類
               -keep class com.android.launcher2.AllApps3D$Defines{
                      *;
                }
               -keep class com.android.launcher2.ClippedImageView{
                     *;
                }
        在android本身的工具中,通過dexdump也可以查看DEX字節碼的執行情況
11.2 接入權限
       在android中,接入權限分爲4個等級,即 normal,  dangerous,  signature,  signatureOrSystem等。權限的等級決定了進行安全保護的級別。其中normal權限不會給用戶帶來實質性的傷害,如調節背光燈動作適用該權限; dangerous 權限可能會給用戶帶來潛在的傷害,如讀取電話簿,系統在安裝應用時發現應用需要該權限會提示用戶; signature權限要求具有統一簽名的應用間才能相互訪問; signatureOrSystem權限主要被設備商使用。
       框架層的接入權限定義在 frameworks\base\core\res\AndroidManifest.xml 中
 11.2.1 創建接入權限
       注意,和常規的理解不同,高級權限並不兼容低級權限,比如,對於normal接入權限,如果在兩個用於同樣數字證書籤名的應用間使用,則會導致失敗。創建一個接入權限的方法:
                 <permission  android:name="android.permission.GET_ACCOUNTS"
                                        android:permissionGroup="android.permission-group.ACCOUNTS"
                                        android:protectionLevel="normal" 
                                        andoird:description="@string/permdesc_getAccounts"
                                        android:label="@string/permlab_getAccounts"   />
        創建一個權限組的方法:
                 <permission-group android:name="android.permission-group.STORAGE"
                                                  android:label="@string/permgrouplab_storage"
                                                  android:description="@string/permgroupdesc_storage"  />
         注意:對於signatureOrSystem 級別的權限,在基於SDK開發的環境中,在調試應用時無法接入這個級別權限的服務。
  11.2.2 應用權限
       應用權限主要用來對應用的操作增加限制,防止惡意應用的非法操作造成敏感數據泄露,設備被非法控制,以及惡意收費等。設置應用權限的方法有兩種: 一種是設置共享用戶ID;另一種是直接設置應用軟件或組件的接入權限。
    1.  設置共享用戶ID
       android在權限管理上應用了linux的ACL(Access  Conrol  List)權限機制,通過在每個應用中使用sharedUserId屬性可共享系統賬戶權限。
       android源代碼樹攜帶的系統證書包括 media,  platfrom , shared, testkey等,其中 media 證書用於多媒體和下載場景中; platform 證書用於系統場景中; shared 證書用於啓動器和電話薄場景中;testkey證書用於開發場景中。 這些證書位於 build\target\product\security\目錄下,產生證書的方法如下:
           
       將 PK8 格式的密鑰轉換爲 PEM格式的方法如下:
                 #openssl  pkcs8 -inform DER -nocrypt -in testkey.pk8 -out testkey.pem
       通過PEM格式的密鑰生成簽名的方法如下:
                 #openssl dgst -binary -sha1 -sign testkey.pem FILE > FILE.sig
       通過設置用戶ID 可以將應用運行在共享的進程中。這一設置通常和在Android.ml中設置的本地證書同時使用。
       如果希望獲得系統權限,那麼應該設置 sharedUserId 屬性爲 android.uid.system。對APK使用系統證書籤名的方法如下:
                 #java  -jar SignApk.jar platform.x509.pem platform.pk8 unsigned.apk signed.apk
       如果APK已經簽名,那麼在apk解壓後的META-INF目錄下,可以看到相應的證書。目前採用的簽名算法爲 RSA算法
       注意:當簽名證書無法滿足用戶權限時,在運行應用時,會拋出 SecurityException 異常,如 android.permission.BIND_APPWIDGET 權限要求用戶具備系統級權限,當證書籤名不正確時,juice會拋出異常:
                 ERROR/AndroidRuntime(3759):  java.lang.SecurityException: Neither  user 10024 nor 
                         current  process has android.permission.BIND_APPWIDGET               #其中10024爲系統UID
   2. 直接設置應用的接入權限
            舉例:爲了調試BT,需進行如下設置:
                   <uses-permission  andrid:name="android.permission.BLUETOOTH" />
                   <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
   3.  設置activity的接入權限
            權限僅作用於單個的Activity,方法如下:
                   在<activity>中,設置  android:permission=-"android.permission.SHUTDOWN" //關機權限
   4.  數據庫的接入權限
            對數據庫的接入權限主要分爲3個層次,讀,寫,搜素,相關的配置實例如下:
                   <provider  android:name="SuggestionsProvider"
                                      android:readPermission="android.permission.READ_SMS"
                                      android:authorities="com.android.mms.SuggestionsProvider" >
                             <path-permission //搜索權限
                                      android:pathPrefix="/search_suggest_query"
                                      android:readPermission="android.permission.GLOBAL_SEARCH"   />
                             <path-permission
                                      android:pathPrefix="/search_suggest_shortcut"
                                      android:readPermission="android.permission.GLOBAL_SEARCH" />
                    </provider>
  11.2.3 權限驗證
         通過權限對組件進行保護帶來了一個問題,即如何判斷調用方是否具有相應的權限。android爲此提供了多種方法,分別應用與不同的應用場景。
         僅在受保護的組件的上下文中進行權限驗證,方法如下:
                 public  abstract  int  checkCallingOrSelfPermission( String permission )
                 public  abstract  int  checkCallingPermission( String permission )
                 public  abstract  int  checkPermission( String permission, int pid, int uid )
        要在第三方上下文驗證,其方法如下:
                 public  abstract  void  enforcePermission( String permission,  int pid,  int uid,  String  message )
        當前,android僅支持對調用者的進程信息進行提取,方法如下:
                 Binder.getCallingPid()                       Binder.getCallingUid()
        對特定的Uri,有讀權限和寫權限,進行權限驗證的方法如下:
                 public  abstract  int  checkCallingUriPermission( Uri  uri,  int  modeFlags )
                 public  abstract  int  checkUriPermission( Uri uri, int pid, int uid, int modeFlags )
                 public  abstract  int  checkUriPermission( Uri uri,  String  readPermission,  String  writePermission,  int pid,  int uid, int  modeFlags )
        如果調用方擁有相應的權限,則權限驗證的返回值爲 PackageManager.PERMISSION_GRANTED, 否則返回 PackageManager.PERMISSION_DENIED。
  11.2.4 接入服務
        提供敏感信息支持的服務必須保證對非法接入的阻止,在android中,這是通過權限和證書等機制來實現的。以牆紙爲例,要對服務進行權限保護,方法如下:
              <service  android:name="com.android.internal.service.wallpaper.ImageWallpaper"
                              android:permission="android.permission.BIND_WALLPAPER">
              </service>
        要在服務的方法被調用時,驗證接入的合法性,方法如下:
               private  int  enforceAccessPermission(){
                       int  ret = mContext.checkCallingOrSelfPermission("android.permission.BIND_WALLPAPER");
                       return  ret;
               }
        然後再調用的方法開始出進行接入合法性判斷,方法如下: 
               public  boolean  getSth(){
                       if(enforceAccessPermission() != PackageManager.PERMISSION_GRANTED){
                                return  false;
                       }
               }
  11.2.5 框架層接入限制
        在框架層,爲了隱藏某些方法或實現類,避免被第三方開發者開發的應用程序(基於SDK)調用,提供hide限制的方
        hide 限制根據作用的對象不同,可以分爲hide方法限制和hide類限制
        hide 方法限制的實現方法如下:
               /**
                * @hide
                */
               @Override
               protected  boolean  isVerticalScrollBarHidden(){
                       return  mFastScroller != null;
               }
        hide 類限制的實現方法如下:
               /**
                * @hide
                */
                public  class WifiService extends IWifiManager.Stub{
                }
       當框架層的API發生變化時,需要先通過make update-api來更新current.xml,否則會因爲一致性問題導致系統編譯上的失敗。
       如果確實需要在基於SDK開發時,在應用層引用隱藏的方法或類,可以採用java反射的機制達到這一目的,但考慮到兼容性的問題,除非確實必要,否則不建議此採用此類方式來實現。
       基於源代碼開發時,hide限制無效。
11.3 數字證書
         在android中,每個應用在發佈時,均必須擁有自己的數字證書籤名, 這個數字證書籤名用於在應用的作者和應用程序之間建立信任關系。
         事實上,數字證書一直存在,及時在通過SDK進行開發階段,每次運行應用程序時,SDK均會自動生成一個用於調試模式的數字證書籤名。只有在應用程序發佈時,纔會用到發佈模式的數字證書籤名。
         數字證書籤名機制有利於程序的升級,除了判斷包名外,只有當新版應用和舊版應用的數字簽名相同時,android纔會認爲這兩個程序是同一個應用的不同版本。另外,android允許擁有相同數字簽名的應用運行在同一個進程中,這同樣有理與在多個應用中共享數據。
          在進行數字簽名時,需要考慮證書的有效期問題,對於從應用商店上下載的應用,如果證書過期,意味着持有該數字簽名證書的應用將不能正常升級。android Markets強制要求所有應用程序的數字證書的有效期持續到2033年10月22日以後。
          在發佈應用時,開發者通過兩種方式爲自己的APK簽名
         》在命令行方式下,利用Keytool來生成數字證書,並利用 Jarsigner 來爲APK 進行數字簽名
         》使用ADT Export Wizard進行簽名
         使用Keytool生成數字證書的方式如下:
          #keytool -genkey -v -keystore android.keystore -alias miaozl -keyalg RSA -validity 2000
         上述代碼中,keystore  android.keystore 表示生成的數字證書爲android.keystore,可以加上路徑(默認在用戶主目錄下); alias miaozl 表示數字證書的別名是 miaozl; keyalg RSA 表示採用的是RSA算法;validity 20000表示數字證書的有效期爲20000天。另外通過keypass 可以設置數字證書私鑰的密碼; 通過keysize 可設置算法的位長,默認爲1024bit, 推薦2048bit及更長; 通過 storepass可設置數字證書的密碼。
         數字證書生成後,即可用它來進行應用程序的簽名了,方法如下:
             #jarsigner -verbose -keystore android.keystore demo.apk 證書別名
         接下來,jarsigner會提示輸入密鑰庫的口令和證書別名的口令,全部輸入後,即可完成簽名。
         具有相同數字證書的應用程序可以彼此分享數據和執行調用。查看應用程序是否已經簽名的方法如下:
             #jarsigner -verify demo.apk
         查看數字簽名證書的更詳細信息的方法如下:
             #jarsigner -verify -verbose demo.apk
             //"certs"選項可以顯示"CN=",揭示誰創建了密鑰
             #jarsigner -verify -verbose -certs demo.apk
         在進行數字證書籤名後,可用 zipalign 工具來優化應用。

第十二章 android的調試,測試與性能優化
         android中,提供了模擬器和目標端等兩種場景下使用的調試和優化工具。
12.1 android調試
        android中,提供ddms的工具組件供開發者使用。
        當發生android ANR錯誤時,錯誤信息會保存在\data\anr\traces.txt中,有利於無法實時查看日誌的情況下分析bug.
12.1.2 dmtracedump跟蹤
        dmtracedump是一個機遇圖形界面的顯示方法間調試關係的工具。在使用前,必須安裝Graphviz。 在linux下安裝Grapviz方法如下:
              #apt-get  install  graphviz
        dmtracedump的用法如下:
              dmtracedump [-ho] [-s sortable] [-d trace-base-name] [-g outfile] <trace-base-name>
        實際操作中,爲了分析函數間的調用關係,首先要在需要分析的方法中設置跟蹤的起始點和結束點,方法如下:
              //開始跟蹤
              Debug.startMethodTracing( "loadEvents" ); //輸出文件爲loadEvents.trace 
              //結束跟蹤
              Debug.stopMethodTracing();
         程序結束後,即可在\sdcard下看到loadEvents.trace 文件。將loadEvents.trace 導出到ubuntu下,通過traceview loadEvents.trace 即可分析時間關係和方法調用關係。這是文本界面的, 要通過圖形界面來顯示就要用到 dmtracedump,方法如下:
              #dmtracedump -g out.png calc.trace
         瀏覽out.png 接口看到相關函數的調用關係圖,其節點的格式如下:
              
 <ref> callname (<inc-ms>, <exc-ms>, <numcalls>)
         上述格式中,ref表示編號,callname表示方法名,inc-ms表示調用時間,exc-ms表示執行時間,numcalls表示執行次數。
 12.1.5 內存調試
     在android中,DDMS, Procrank, Dumpsys等可以獲得系統運行期的內存信息。
     1. DDMS內存調試
     直接啓動DDMS工具,然後在Sysinfo 下,可以看到內存的使用情況
         
    2.  Dumpsys內存調試
          分析內存的方法爲 adb shell dumpsys meminfo  。 通過Dumpsys觀察應用的使用情況,實例如下:
                 
   3.  Procrank內存調試
             另外 ,通過 adb shell procrank 也可查看進程佔用內存的情況,其中Uss(Unique Set Size)的大小代表屬於本進程正在使用的內存大小,這些內存在該進程被撤銷後,會被完全回收,Uss也是進行內存泄露觀察是的重點;  Vss(Virtual Set Size)和Rss(Resident Set Size)表示共享庫的內存使用,但由於共享庫的資源一般佔用比重較大,因此會是進程自身創建引起的內存波動所佔比例減小; 而 Pss(Proportional Set Size)則按照比例進行共享內存分割。
             
       通過間隔性地運行Procrank來觀察進程佔用Uss內存的變化,可以分析應用是否存在內存泄露。 Procrank的代碼位於system\extras\procrank文件夾下。用法:
               procrank  [-W] [-v  | -r | -p | -u | -h]
       下面是一個方便間隔運行Procrank觀察內存變化的腳本:
               #!/system/bin/sh
               while true; do
               date >> ./procrank.log
               procrank >> ./procrank.log
               echo >> ./procrank.log
               sleep 5
               done
         假設腳本名爲 procrank.sh ,其運行方法如下:
              #chmod  777  procrank.sh
              #./procrank .sh &
         考慮到android是基於Dalvik虛擬機的,垃圾回收並非實時的,故通過單個界面的單次啓動,關閉是無法確定內存是否泄露。一個好的策略是,重複執行某個界面的啓動和關閉,如果發現應用佔用的內存不斷上升,則可以判斷該界面存在內存泄露。
 4.  Eclipse 插件內存調試
          在eclipse中,插件 MAT 可以幫助分析java層的內存泄露。 MAT 分析的是 hprof文件,該文件存放了進程的內存快照。下面是從終端獲取hprof文件的方法:
               #adb shell
               #ps  //查看進程號
               #chmod  777 /data/misc
               #kill -10 PID  //PID即進程號
          這樣即可在\data\misc目錄下生成一個帶當前時間的hprof文件,但這個文件不能直接被MAT讀取,需要藉助 hprof-conv 將 hprof轉換爲MAT可以讀取的格式,然後,纔可用MAT進行分析。 hprof-conv 的用法:
               hprof-conv <infile><outfile>
12.2 android佈局優化
       android提供了兩個android佈局優化工具, 即 Layoutopt 和Hierarchyviewer。 其中 Layoutopt 可以優化佈局,幫助開發者減少冗餘信息,Hierarchyviewer可直接調試用戶界面
 1. Layoutopt 優化
        可幫助開發者分析採用的佈局是否合理,並給出修改意見。其用法:
                  layoutopt <directories/files to analyze>
        具體方法如下:
                  layoutopt  res/layout/land
                  layoutopt  res/layout/main.xml
 2. Hierarchyviewer 優化
        可清晰地看到當前設備的UI界面的實際佈局和控件屬性,其僅能優化debug模式的應用。
12.3 android測試
       分爲Monkey壓力測試和CTS兼容性測試
12.3.1 Monkey壓力測試
       Monkey 工具可以模擬各種按鍵,觸屏,軌跡球,導航,activity等時間。工具用法:
             adb  shell  monkey [options] <event-count> //特定事件
             adb  shell  monkey  -p  your.package.name -v 50000 //50000次隨機事件
       monkey測試僅能模擬系統事件監測應用中存在的語法Bug,對深層次的語義bug無能爲力,而且對於網絡功能,monkey也無法有效的測試。
       由於monkey是通過加載特定的Activity(category屬性需爲android.intent.category.LAUNCHER或android.intent.category.MOKEY)作爲程序入口來進行測試的,故在進行Monkey測試時,容易形成孤島的Activity,爲了全面的測試,需要爲其增加intent 過濾器。相應的參考:
             <activity  android:name="**">
                     <intent-filter>
                            <action android:name="android.intent.action.MAIN" />
                            <category android:name="android.intent.category.MONKEY" />
                     </intent-filter>
             </activity>
        注意,如果只針對單個應用進行壓力測試,monkey會阻止對其他包的調用,當然針對單個應用的壓力測試無法檢測到應用間交互可能存在的bug。爲了測試系統內應用交互與衝突帶來的問題,可針對系統進行monkey測試,方法如下
              #adb  shell  monkey  -p  -v  50000
       在進行壓力測試過程中,monkey會因爲應用奔潰,網絡超時以及一些無法處理的異常而停止測試。建議在對網絡進行壓力測試時,忽略網絡茶超時的情況,方法如下:
             #adb  shell  monkey  -p  your.package.name  -v  50000  --ignore-timeouts
        如果希望忽略應用奔潰的情況,可以執行如下方法:
             #adb  shell  monkey  -p  your.package.name  -v  50000  --ignore-crashes 
12.3.2 JUnit 迴歸測試
        即白盒測試,常用於單元測試場景中,如非圖形化界面的接口測試,這在開發框架性代碼時非常有用。android僅支持JUnit3.
        在android中,android JUnit 還支持對圖形界面如Activity,View等的測試,甚至還支持對圖形界面接口的功能壓力測試。android JUnit的內容主要分佈在android.test中。
        根據約束的不同,測試可分爲 AndroidOnly(表示測試項僅適用於android); SideEffect(表示測試具有副作用);UiThreadTest(表示測試項在UI主線程中運行); BrokenTest(表示測試想需要修復); Smoke(表示測試項爲冒煙測試); Suppress(表示該測試項不應出現在測試用例中)。
        爲了進行JUnit迴歸測試,需要在eclipse中創建Android Test Project,可通過執行file -> new -> other -> android -> android Test Project命令完成,運行的方法爲右擊工程項,在彈出的菜單中執行 run as -> android  JUnit Test命令。 對於具有多個InstrumentationTestRunner的測試工程,在執行測試時,應該先在工程的Run  Configurations 中,指明 instrumentationTestRunner。測試完成後,會自動給出測試結論。
        JUnit測試框架和測試步驟。
      1.  Junit測試的框架
           Junit 測試主要包括 TestCase,  Instrumentation 等。爲了運行測試用例,必須將測試用例添加到TestSuite中,通過Instrumentation來管理。
      2.  JUnit測試的實現
           步驟:
          》構建AndroidManifest.xml配置文件。
          》制定InstrumentationTestRunner文件
          》構建具體測試代碼
          》如果是基於源代碼進行的測試,那麼還需要構建Android.mk文件。
     下面以Calculator應用爲例:
       (1)構建AndroidManifest.xml配置文件
            需要構建AndoridManifest.xml配置文件;但和普通的工程不同的是,JUnit迴歸測試工程沒有圖形界面,必須聲明要用到”android.test.runner“ JAR包聲明相應的InstrumentationTestRunner。 在通過SDK創建測試工程時,AndroidManifest.xml會自動生成,默認的InstrumentationTestRunner 爲 android.test.InstrumentationTestRunner。在某些情況下,開發者需要自定義 InstrumentationTestRunner。 下面是JUnit迴歸測試的AndroidManifest.xml 文件實現:
                 <manifest  xmlns:android="http://schemas.android.com/apk/res/android"
                                    package="com.android.calculator2.texts">
                          <application>
                                  
  <uses-library  android:name="android.test.runner" />
                          </application>
                     //僅在源代碼下可用
                          <instrumentation  android:name="CalculatorLaunchPerformance"
                                      android:targetPackage="com.android.calculator2"
                                      android:label="Calculator Launch Performance">
                          </instrumentation>
                     //加載InstrumentationTestRunner時,需指定報名
                          <instrumentation
                                     android:name="android.test.InstrumentationTestRunner"
                                     android:targetPackage="com.android.calculator2"
                                     android:label="Calculator Functional Testset">
                          </instrumentation>
                  </manifest>
     (2)制定InstrumentationTestRunner文件
            InstrumentationTestRunner文件爲 JUnit測試的入口文件,在某些情況下,需要自定義InstrumentationTestRunner類,最重要的是addTestSuite()方法。 它將 TestCase 納入TestSuite流程,調用響應的方法,過程如下:
                        public  class  MusicPlayerFunctionalTestRunner  extends  InstrumentationTestRunner{
                                  public  TestSuite  getAllTests(){
                                           TestSuite  suite = new InstrumentationTestRunner(this);
                                           suite.addTestSuite(TestSongs.class);
                                           suite.addTestSuite(TestPlaylist.class);
                                           suite.addTestSuite(MusicPlayerStability.class);
                                           return  suite;
                                  }
                                  public  ClassLoader getLoader(){
                                           return  MusicPlayerFunctionalTestRunner.class.getClassLoader();
                                  }
                        }
     (3)構建具體的測試代碼
                需要根據組件的類型構建響應的測試用例,其中有兩個關鍵的方法:setUp()方法用來構建測試環境,如打開網絡鏈接等; tearDown()方法可以確保在進入下一個測試用例前所有資源被銷燬並被回收。對於不同的測試,應該配置不同的測試類型。
             測試Activity 的用例實現如下:
                    public  class  SpinnerTest  extends  ActivityInstrumentationTestRunner2<RelativeLayoutStubActivity>{
                              private  Context  mTargetContext;
                              public  SpinnerTest(){
                                        super( "com.android.cts.stub", RelativeLayoutStubActivity.class );
                              }
                              protected  void  setUp() throws  Exception{
                                        super.setUp();
                                        mTargetContext = getInstrumentation().getTargetContext();
                              }
                              protected  void  tearDown() throws  Exception{
                                        super.tearDown();
                              }
                              @UiThreadTest  //配置測試類型
                              public  void  testGetBaseline(){//測試項方法必須以test開頭
                                       。。。。。。。。。。。。
                              }
                    }
     (4)構建Android.mk文件
          和普通的android.mk文件不同,其需要制定 LOCAL_MODULE_TARGS 爲 tests, 通過 LOCAL_JAVA_LIBRARIES變量加載android.test.runner的JAR包,通過 LOCAL_INSTRUMENTATION_FOR 指定對那個包進行迴歸測試。下面是一個測試用例
                  LOCAL_PATH:=$(call my-dir)
                  include $(CLEAR_VARS)
                  LOCAL_MODULE_TAGS := tests 
                  LOCAL_JAVA_LIBRARIES := android.test.runner
                  LOCAL_SRC_FILES := $(call  all-java-files-under,  src)
                  LOCAL_PACKAGE_NAME := CalculatorTests
                  LOCAL_INSTRUMENTATION_FOR := Calculator
                  include $(BUILD_PACKAGE)
 12.2.2 CTS兼容性測試
        爲了防止OEM廠商對android的定製導致平臺的不兼容問題,google在發佈android版本時會發布相關的CTS測試,編譯CTS和啓動交互CTS控制檯的方法如下:
                cd  /path/to/android/root
                make cts
                cts
        執行CTS測試並設置測試參數的方法如下:
                cts  start  -plan  CTS  -p  android.os.cts.BuildVersionTest
      
  第十三章 android編譯
1.  android快捷方式:
     croot   用於改變當前路徑到android根目錄
     m   用於從android根目錄開始編譯
     mm  用於編譯當前目錄下的所有模塊
     mmm  用於編譯特定目錄下的所有模塊
     cgrep  用於在C/C++文件中查找
     jgrep  用於在java文件中查找
     resgrep 用於在資源文件中查找
     godir  用於跳轉到某個目錄
2.  主要腳本
        android中的腳本類文件主要用來配置產品,目標板,以及根據開發者的Host 和Target來選擇相應的工具並設定編譯選項。編譯系統的主要腳本包括 envsetup.sh ,  config.ml,    envsetup.mk  product_config.ml,   BoardConfig.mk    version_defaults.ml   product.mk,    build_id.mk.   AndroidProducts.ml    Makefile等。下圖爲android執行編譯所設計的主要腳本之間的調用關係:
         
        上圖中,AndroidProducts.mk 包含了具體的應用配置腳本; product_config.mk 主要定義AAPT, 產品製造商,wifi,OTA等相關信息; product.mk定義了產品的一些變量信息。
        對模塊編譯進行控制,主要是通過 core.mk,  generic.mk,  sdk.mk 等腳本及特定目標環境的腳本進行的;對單個模塊進行控制,主要是通過Android.mk, 和CleanSpec.mk等腳本進行的。
        下面詳細介紹:
   (1)envsetup.sh
         腳本主要功能包括定義環境變量信息,加載系統配置信息(軟件信息,硬件配置),定義編譯快捷方式,調試,冒煙測試,GDB調試等。
         若編譯代碼,就涉及代碼的編譯工具,目前android支持的原生代碼編譯工具鏈位於prebuilt目錄下,包括交叉編譯工具鏈和普通編譯工具鏈。目前交叉編譯工具鏈爲 arm-eabi。  所謂的EABI, 即應用程序二進制接口,針對ARM架構的CPU的,支持軟件浮點和硬件浮點功能混用,效率高。普通編譯工具鏈包括 i686-linux-glibc207-4.4.3,  i686-unknown-linux-gnu-4.2.1 和 sh-4.3.3等。設置交叉編譯鏈的過程如下:
                   export  ANDROID_EABI_TOOLCHAIN=
                   $prebuiltdir/toolchain/arm-eabi-4.4.3/bin
                   export  ANDROID_TOOLCHAIN=$ANDROID_EABI_TOOLCHAIN
                   export  ANDROID_QTOOLS=$T/development/emulator/qtools
          設置java編譯工具的方法如下 :
                    function  set_java_home(){
                            if [ ! "JAVA_HOME" ]; then
                                 case  'uname -s' in  Darwin)
                                        export  JAVA_HOME=/System/Library/Fromeworks/JavaVM.framework/Versions/1.6/Home )
                                        ;  ;
                                      )
                                        export  JAVA_HOME=/usr/lib/jvm/java-6-sun
                                        ;  ;
                                 esac
                            fi
                    }
              目前android的Makefile文件名爲Android.mk,與標準相同。
   (2)config.mk
         config.mk 用於定義系統相關的配置信息和編譯變量等。下面是config.mk中對輸出包後綴的設置:
                 COMMON_PACKAGE_SUFFIX := .zip
                 COMMON_JAVA_PACKAGE_SUFFIX := .jar
                 CONNON_ANDROID_PACKAGE_SUFFIX := .apk
          下面是config.mk中加載目標環境的過程:
                 board_config_mk := \
                  $(strip $(wildcard \
                  $(SRC_TARGET_DIR) /board/$(TARGET_DEVICE)/BoardConfig.mk \
                  device/*/$(TARGET_DEVICE) /BoardConfig.mk \
                  vendor/*/$(TARGET_DEVICE)/BoardConfig.mk \
                  ))
         默認情況下,目標環境信息位於SRC_TARGET_DIR, device,  vendor 下。開發者可以將自定義的目標環境文件夾放在這些目錄下。
   (3)envsetup.mk
          envsetup.mk 主要用於判斷駐留的操作系統環境,設置環境變量。比較重要的環境變量有 TARGET_PRODUCT,  TARGET_BUILD_VARIANT,
HOST_OS,  BUILD_OS,  BUILD_ARCH,  HOST_BUILD_TYPE,  OUT_DIR等。
         》 TARGET_PRODUCT 表示編譯的目標環境。TARGET_PRODUCT的定義具體由OEM廠商決定。對應於特定的目標環境,需要在build\target 或 device目錄下存在特定的目標環境配置文件夾,比如對應的generic目標環境,在 build\target\board\generic下定義了generic目標環境的具體配置(如系統屬性,鍵盤配置等)。而對於 Samsung的 crespo目標環境,配置文件目錄爲 device\Samsung\crespo.
        》TARGET_BUILD_VARIANT 表示目標編譯變量,說明有哪些文件被納入編譯控制。目前TARGET_BUILD_VARIANT的值爲 eng,  user,  debug,  tests 等。
       》HOST_OS用於設置駐留的操作系統。目前android支持的值有linux,Darwin(用於Darwin,Mac等操作系統),windows等。判斷操作系統的方法如下:
                  UNAME :=$(shell uname-sm) //UNAME包含了操作系統和CPU結構的信息
                  ifneq ( , $(findstring  Linux,  $(UNAME)))
                  HOST_OS :=linux
                  endif
       》BUILD_OS 表示真正執行編譯的操作系統,目前與HOST_OS相同。
       》BUILD_ARCH 表示駐留處理器架構,目前android僅支持x86 和PPC兩種架構
       》HOST_BUILD_TYPE 表示編譯的類型,目前僅支持 release 和debug兩種類型,debug用於調試
       》OUT_DIR 表示輸出文件的路徑。目前輸出文件位於out 目錄下,主要由 target, host 和 common 3部分,下面是定以OUT_DIR的實現:
                 ifeq ( , $(strip $(OUT_DIR)))
                         OUT_DIR := $(TOPDIR)out
                 endif
  (4)BoradConfig.mk
         用於設置硬件相關的信息,是構建目標環境配置的重要文本。目前android定義的目標環境包括 Emulator,  Generic,  Generic_x86, Sim。 除了以上目標環境外,android源碼還包含了HTC的passion與dream, 以及 Samsung 的 crespo等目標環境,可以作爲驅動開發人員進行目標環境配置的參考。
         build\target\board\generic\目錄下BoardConfig.ml的實現如下,其中定義了引導器,內核,編譯器,驅動,分區配置等方面的信息。
                   TARGET_NO_BOOTLOADER := true
                   TARGET_NO_KERNEL := true
                   TARGET_CPU_ABI := armeabi
                   HAVE_HTC_AUDIO_DEIVER := true
                   BOARD_USED_GENERIC_AUDIO :=true
                   TARGET_SHELL :=mksh
           crespo目標環境在BoardConfig.mk中對分區配置的定義如下:
                   
          在目標環境中,另一個重要的腳本爲 AndroidBoard.mk, 通常用於定義按鍵佈局等信息,與鍵盤有關的信息定義在 tuttle2.kl 和 tuttle2.kcm 等文件中。 build\target\board\generic\目錄下 AndroidBoard.mk實現如下:
                   
   (5)version_defaults.mk
          version_defaults.mk用於處理各種編譯版本信息,如PLATFORM_VERSION,  PLATFORM_SKD_VERSION,  BUILD_ID,  BUILD_NUMBER等。
          對於主分支的源碼,其PLATFORM_VERSION的值爲 AOSP, PLATFORM_SKD_VERSION的值爲數字,BUILD_ID的值爲OPENMASTER, BUILD_NUMBER的值是根據編譯日期生成的。
   (6)build_id.mk
         build_id.mk用於定義版本分支信息,其應用方法如下:
             BUILD_ID :=OPENMASTER //編譯分支
             DISPLAY_BUILD_NUMBER := true //是否顯示版本號
   (7)Makefile
         定義各種映像文件的配置,包括boot.img,  ramdisk.img,  userdata.img,  system.img,  recovery.img  等。
         下面是定義System.img包含內容的過程:
                  
        生成system.img的過程:
                 
        其他映像文件的生成方法和system.img類似
   (8)core.mk
        定義了產品的一些基本信息和核心包。基本信息主要包括產品的BRAND,  DEIVCE 以及產品屬性(如提示音,振鈴聲,多媒體框架配置等)。
  (9)generic.mk
        定義了generic目標環境的應用編譯控制腳本,主要側重將哪些應用加入到編譯系統中。如果開發者希望將實現的某一應用加入到編譯系統中,應在generic.mk中添加相應的配置,是應用的LOCAL_PACKAGE_NAME納入到 PRODUCT_PACKAGES變量的控制中。
        例如,如果希望將Calendar應用加入到編譯系統,通過查看packages\apps\Calendar\目錄下 Android.mk中的相關配置,得知 LOCAL_PACKAGE_NAME 爲 Calendar, 將 Calendar應用加入到編譯系統的方法如下:
                PRODUCT_PACKAGE :=\ 
                      Calendar \
  (10)sdk.mk
       如果在編譯SDK,則應注意 sdk.mk腳本,其和generic.mk一樣具有編譯控制功能。除了對普通應用進行控制外,根據SDK的特點,sdk.mk工具,資源相關的信息納入到編譯系統。
   (11)Android.mk
       對於單個工程,android是通過Android.mk來進行編譯控制的。實例如下:
                    
        注意:清空本地環境變量實際上是調用 build\core\目錄下的Clear_vars.mk完成的;  LOCAL_MODULE_TAGS 的可選值包括 samples, optional,  eng,  debug,  cts, tests,  user等,其默認值爲 optional。
        如果應用代碼中包含AIDL文件,那麼將AIDL文件添加到源代碼樹中的方法如下:
                
        a.  加載共享庫
             android.mk還支持加載java庫和原生庫,執行多個編譯任務等。以Calculator爲例介紹。Calculator的Android.mk執行了兩個編譯任務,出列編譯應用外,還編譯了一個JAR庫,相關實例如下:
                 
                 
        爲了加載JAR庫,需要重點了解 LOCAL_STATIC_JAVA_LIBRARIES 和 LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES 兩個本地環境變量。前者爲應用加載的庫名,後者用於設置與庫名相對應的具體的JAR庫。如果希望加載多個JAR庫,可按下面方式設置本地環境變量:
                  
        除了JAR庫外,部分應用可能還包含了基於JNI的原生代碼實現,這些代碼通常被編譯成共享庫的形式。假設生成的共享庫爲 libnative.so, 那麼應用的 Android.mk文件中加載共享庫的方法入下:
                    LOCAL_JNI_SHARED_LIBRARIES := libnative
          如果在SKD下進行開發,那麼對JAR庫和原生共享庫不需要進行配置,通常將JAR庫放置在應用的根目錄或libs目錄下,將原生共享庫放置在libs\armeabi目錄下。
          b. 應用權限
          要進行限制級的操作,如查看電話薄,撥打電話,就涉及權限問題。android.mk支持設置本地證書,方法如下:
                 LOCAL_CERTIFICATE := platform
          目前 LOCAL_CERTIFICATE的可選值包括,platform,  shared,  media,  testkey, cts/tests/appsecurity-tests/certs/cts-testkey1,  cts/tests/appsecurity-tests/certs/cts-testkey2等。其中 platform 表示系統證書,通常和在AndroidManifest.xml中加入的 android:sharedUserId="android.uid.system" 屬性結合使用。
          c. 混淆器設置
          在android中加載 proguard混淆器的方法如下:
                 LOCAL_PROGUARD_FLAG_FILES := proguard.flags
          以launcher2爲例,其proguard.flags實現如下:
                   
                  
         如不希望進行java混淆,則可進行如下設置:
                    LOCAL_PROGUARD_ENABLED := disabled
       d.  安裝到特定目錄下
           設置變量值 LOCAL_MODULE_CLASS 可選擇器安裝的目錄,可選值有 EXECUTABLES,  ETC, DATA,  STATIC_LIBRARIES,  JAVA_LIBRARIES,  SHARED_LIBRARIES等。
  (12)CleanSpec.mk
         用於編譯時,清除遺留的中間文件和數據文件,通常不需要進行設置。

13.1.4 環境變量
   android編譯系統中,有幾個重要的環境變量需要注意,下面做簡單的介紹。
  (1)ANDROID_TOOLCHAIN
        ANDROID_TOOLCHAIN主要用於設置交叉編譯工具鏈,目前android的交叉編譯工具鏈爲arm-eabi-4.4.3。需要注意看,工具鏈包括ar, as, c++,  g++,  gcc,  ld,  nm,  objcopy,  objdump,  ranlib,  strip 等。查看模塊間的依賴關係的工具ldd並不在其中。
  (2)ANDROID_PRODUCT_OUT
        ANDROID_PRODUCT_OUT定義編譯目標環境輸出的絕對路徑。對於generic目錄環境而言,其ANDROID_PRODUCT_OUT的值爲ANDROIDROOT/out/target/product/generic,  由 TARGET_PRODUCT_OUT_ROOT和 TARGET_DEVICE組成。
  (3)TARGET_PRODUCT
        TARGET_PRODUCT表示編譯的目標環境,這是android編譯系統中最重要的環境變量。目前android提供了多個目標環境,包括sdk, sim, full , full_x86, generic,  full_crespo等。需要注意:generic表示最低配置; full表示集成所有語言,應用,輸入法的配置;
  (4)TARGET_BUILD_VARIANT
        TARGET_BUILD_VARIANT定義了編譯的變量,目前支持的有: eng,  user,  debug ,  tests 等。
  (5)TARGET_BUILD_TYPE
        表示編譯的類型,指定的是release 還是debug類型。
  (6)TARGET_SIMULATOR
        爲一個布爾型的變量,常用於判斷輸出目標環境的真實的設備還是模擬器。
13.1.5 目標環境
    目標環境由 TARGET_PRODUCT 和 TARGET_BUILD_VARIANT共同定義,目前android自帶的編譯模式有: full_eng,  full_x86-eng 和 simulator等。
        在進行源碼編譯時,通過如下方式可執行目標環境:     
                #lunch " TARGET_PRODUCT"-"TARGET_BUILD_VARIANT"
        接着直接執行make即可執行相應的目標環境編譯。
13.4 應用程序編譯
  13.4.1 本地環境變量
        LOCAL_MODULE:  表示本地模塊名,常用於編譯原生代碼的模塊,在應用層開發匯中,多出現在JNI場景中,用於編譯動態庫,靜態庫。
        LOCAL_PATH :  表示本地路徑,通常在編譯模塊時表示當前編譯過程中的根路徑。其實現多維當前路徑,
                                  LOCAL_PATH:=$(call my-dir)
        LOCAL_MODLUE_TAGS:  表示編譯的標籤,可選值optional,  eng,  debug,  tests,  samples,  user等。
        LOCAL_MODULE_PATH: 表示編譯輸出文件放置的位置。
        LOCAL_MODULE_CLASS: 表示模塊的類型,可選值 STATIC_LIBRARIES,  EXECUTABLES, JAVA_LIBRARIES,  ETC,  SHARED_LIBRARIES等。
        LOCAL_SRC_FILES:  表示編譯的源文件,是最基本的編譯變量
        LOCAL_SDK_VERSION 表示編譯模塊的SDK版本,默認值爲current。 如果希望在源代碼下編譯特定SDK版本的模塊,需顯式聲明SDK版本。
                   eg: 要在Gingerbread上編譯Froyo版本,可進行配置:LOCAL_SDK_VERSION :=8
        LOCAL_PACKAGE_NAME : 表示編譯的模塊名,在源碼下編譯應用時,應注意其爲編譯開關的角色。
        LOCAL_CERTIFICATE:  表示採用的系統證書。LOCAL_CERTIFICATE通常與AndroidManifest.xml中的android:sharedUserId屬性同時使用。
        LOCAL_SHEARED_LIBRARIES: 表示需要加載的動態庫、
        LOCAL_PRELINK_MODULE: 如果希望將模塊鏈接到系統中,則應設置LOCAL_PRELINK_MODULE爲true, 否則爲false.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章