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.
展开阅读全文

没有更多推荐了,返回首页