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

   第四章  深入解析Android數據存儲與管理
4.1 xml文件管理
        xml文件更多的是用來維護應用或系統的配置文件。SharedPreferences這個輕量級的類作爲xml文件存儲的上層接口。本質爲<key, value>
       根據配置信息是否對外開放,SharedPreferences提供了 MODE_PRIVATE,   MODE_WORLD_READABLE兩種權限,SharedPreferences的操作分爲獲取配置信息和存儲配置信息兩種。下面是獲取配置信息方法:
               SharedPreferences  settings = getSharedPreferences( PREFS_NAME,  MODE_PRIVATE );
               boolean  silent = settings.getBoolean( "silentMode" , false );
        存儲配置信息方法:
               SharedPreferences  settings = getSharedPreferences( PREFS_NAME,  MODE_PRIVATE );
               SharedPreferences.Editor  editor = settings.edit();
               editor.putBoolean( "silentMode" , mSilentMode );
               editor.commit();
        若希望配置信息對其他應用程序開發,在設置權限時,可使用 MODE_WORLD_READABLE 。在其他應用獲得相應的配置信息時,必須先獲得相應的上下文,方法如下:
               context = createPackageContext( "com.miaozl.text" , Context.CONTENXT_IGNORE_SECURITY );
               if(context != null){
                        SharedPreferences  settings = context.getSharedPreferences(PREFS_NAME,  Context.MODE_WORLD_READABLE);
                        mTest = settings.getString( "test", null );
               }
        上述PreferenceActivity中內置了對SharedPreferences的支持。對於上層代碼中的接入,實例如下;
                   <ListPreference
                           android:lkey="window_animations"
                           ......   />
         接入: 
                    private final static String WINDOW_ANIMATIONS_PREF = "window_animations";
                    mWindowAnimationsPref = (ListPreference)prefSet.findPreference(WINDOW_ANIMATIONS_PREF);    
                    mWindowAnimationsPref.setOnPreferenceChangeListener(this);
                    public boolean onPreferenceChange( Preference preference ,  Object objValue ){
                             if( preference == mWindowAnimationsPref ){
                                       writeAniamtionPreference( 0, objValue );
                             }
                    }
          若希望ListPreference保存或查看當前的選擇,可調用ListPreference的方法:             
                    public  void  setValue(String  value);  //對應android:entries屬性的值
                    public  void  setValueIndex(int index);  //對應android:entryValues屬性的值
4.2  內部文件管理
       對於二進制數據,android提供內部存儲的方式,可將數據存儲在應用的私有空間中,避免其他程序訪問,內部存儲的數據會在應用卸載時刪除。
       內部存儲權限包括  MODE_PRIVATE,  MODE_APPEND,  MODE_WORLD_READABLE,   MODE_WORLD_WRITEABLE 等。
       內存存儲所在目錄爲:  \data\data\com.company.packagename\files.
   4.2.1 寫入數據
        寫入字符串:
                     FileOutputStream  out = context.openFileOutput( file, Context.MODE_WORLD_WRITEABLE );
                     out.write( captureArray.toString().getBytes() );
                     out.close();
        寫入結構體數據的方法:
                     DataOutputStream out = new DataOutputStream( context.openFileOutput( PREFERENCES, MODE_PRIVATE ) );
                     out.writeUTF( configuration.locale );
                     out.writeInt( configuration.mcc );
                     out.writeInt( configuration.mnc );
   4.2.2 讀取數據
         使用函數  openFileInput()實現
         對於應用攜帶的靜態數據,可放置在應用的 assets 目錄或res   raw目錄下
         對於assets目錄下的靜態數據,存在單個文件最大僅支持1MB的侷限,讀取方式如下:
                  InputStream  is = getAssets().open("read_asset.txt");
         對於res  raw目錄下的靜態數據,可通過如下方式讀取:
                 InputStream  inputStream = resources.openRawResource( R.raw.definitions );
  4.3 外部文件管理
        即存儲在外置SD卡上,可通過getExternalFilesDir()來獲得具體路徑,該具體路徑依賴於應用的包名,其SD卡上的私有目錄如下:
                     \mnt\sdcard\Android\data\com.miaozl.hello\files\
        獲得圖片路徑的方法: 
                     File  path = getExternalFilesDir( Environment.DIRECTORY_PAICTURES );
        若希望存儲在SD卡上的公共目錄,可銅鼓    getExternalStoragePublicDirectory() 獲得。注意,公共目錄的具體路徑視需要存儲的文件類型而定。下面是獲取公共目錄的方法:
                    String  sdroot = Environment.getExternalStorageDirectory().getAbsolutePath();
                    String  dwndir = Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_DOWNLOADS ).gerAbsolutePath();
         注意使用SD卡目錄前,先通過 Environment.getExternalStorageState()方法判斷SD卡是否已掛載。
 4.4 數據庫管理
       android提供兩種SQLite的實現方法:  基於android封裝接口來實現的;  基於原生 SQLite 的方法來實現。
  4.4.1  android封裝接口
         google提供的封裝類 SQLiteOpenHelper, 通過繼承SQLiteOpenHelper來設計操作數據庫。注意封裝會使android的性能降低。
         在繼承SQLiteOpenHelper時,必須實現 onCreate() 和 onUpgrade()方法。下面是通過封裝android接口設計數據庫的步驟:
       (1)定義數據庫名,數據庫版本號,數據表名
       (2)實現 onCreate() 和 onUpgrade() 方法
       (3)實現插入,更新,刪除和查詢方法
         實現過程:
              定義數據庫名,數據庫版本號,數據表名:
                    private  static final  String  DATABASE_NAME = "notepad.db";
                    private  static  final  int  DATABASE_VERSION = 2;
                    private  static  final  String  NOTES_TABLE_NAME = "notes";
              實現 onCreate() 和 onUpgrade() 方法 :
                    public  void  onCreate( SQLiteDatabase db ){
                             db.execSQL( "CREATE TABLE" + NOTES_TABLE_NAME + "(" 
                              + NoteColumns._ID + "  INTEGER PRIMARY KEY,"
                              + NoteColumns.TITLE +  " TEXT, "
                              + NoteColumns.NOTE + " TEXT, "
                              + NoteColumns..CREATED_DATE + " INTERGER,"
                              + NoteColumns.MODIFIED_DATA + " INTEGER"
                              + ");");
                     }
                     public  void  onUpgrade( SQLiteDatabase db,  int oldVersion, int newVersion ){
                               db.execSQL("DROP TABLE IF EXISTS notes");
                               onCreate(db);
                     }
                當一次性需要修改多個數據時,建議通過SQLite的書屋處理進行批量處理,有利於提高執行效率,事務處理相關的方法: 
                     public  void  beginTransaction();
                     public  void  beginTransactionWithListener(SQLiteTransactionListener  transactionListener);
                     public  void  endTransaction();
                     public  boolean  inTransaction();
                     public  void  setTransactionSuccessful();
  4.4.3 原生方法處理
          採用 raw 方法來操作SQLite數據庫,但必須做好SQL語句異常處理:
                    SQLiteDatabase  db;
                    String args[] = { id };
                    ContentValues  cv = new ContentValues();
                    cv.put("miaozl", id);
                    Cursor c = db.rawQuery( "SELECT * FROM table  WHERE miaozl = ?", args );
                    if( c.getCount() != 0 ){
                              ContentValues cv = new ContentValues();
                              cv.put(" miaozl ", "cwj");
                              db.insert("table", " miaozl ", cv);
                              String args[] = {id};
                              ContentValues cv2 = new ContentValues();
                              cv2.put("miaozl", id);
                              db.delete("table",  "miaozl = ?", args);
                    }
  4.5  數據處理
   

  第五章 android通信機制
        android,通信技術涉及多個層面,在UI層涉及多種事件(觸控事件,按鍵事件,軌跡球事件等);框架層涉及intent,message等。
 5.1 intent 通信
      通常情況下,intent僅能傳遞基本的數據類型,對於複雜的數據類型,則要藉助Serializable,  Parcelable 兩個接口進行。
  5.1.1 intent的常見用法
      針對網絡,地圖,電話,消息,電子郵件,多媒體,系統等幾個方面介紹Intent用法:
    1.  網絡相關
       網絡功能實現,需要擁有 android.permission.INTERNET 權限,與網絡相關的intent通信包括顯示網頁,google地圖,搜索等。
       在由HTTP定義的與服務器交互的方法中,發起請求的方式有兩種,即GET 和 POST, 其中GET方式通過URL提交數據,數據在URL中可以看到,無法保證私密性,且提交的數據最多隻能爲1024字節; 而 POST方式將數據放置在HTML  HEADER中提交,且在數據長度上沒有限制,通常用於傳輸敏感數據。
       在Intent中,目前只支持GET方式,爲了打開網站,其採用的ACTION 爲 ACTION_VIEW,方法如下:
               Uri  uri = Uri.parse("http://www.163.com");
               Intent  it = new Intent(Intent.ACTION_VIEW, uri);
               startActivitity(it);
       如果傳遞敏感數據,在WebView中,採用的方法如下:
               public  void  postUrl( String url,  byte[] postData )
       在網絡中搜索相關的信息,實現方法如下:
               intent.setAction( Intent.ACTION_WEB_SEARCH );
               intent.putExtra(SearchManager.QUERY,  "android123");
               startActivity(intent);
    2. 消息相關
        爲了查看消息,需有android.permission.READ_SMS權限,方法如下: 
                Intent  it  = new intent( Intent.ACTION_VIEW );
                it.setType("vnd.android-dir/mms-sms");
                startActivity(it);
        爲了發送短信,需有android.permission.WRITE_SMS權限,方法如下:
                Uri  uri = Uri.parse( "smsto:10010" );
                Intent  it = new  Intent( Intent.ACTION_SENDTO,  uri );
                it.putExtra( "sms_body", "hello" );
                startActivity(it);
         發送彩信,還要涉及附件,下面是一個發送實例:
                Uri  uri  = Uri.parse("content://media/external/images/media/10");
                Intent  it  = new Intent( Intent.ACTION_SEND );
                it.putExtra( "sms_body", "hello" ); 
                it.putExtra(Intent.EXTRA_STREAM,  uri);
                it.setType("image/png");
                startActivity(it);
    3. 電子郵件相關
            直接發送文字的郵件:
                   Intent  it =  new  Intent( Intent.ACTION_SEND );
                   it.putExtra( Intent.EXTRA_EMAIL,  "[email protected]" );
                   it.putExtra( Intent.EXTRA_TEXT,  "EMAIL——內容" );
                   it.setType( "text/plain" );
                   startActivity( Intent.createChooser( it , "選擇一個Email客戶端" ) );
          發送的時候有抄送人,方法如下;
                   it.putExtra( Intent.EXTRA_CC,  "[email protected]" );//抄送人
                   it.putExtra( Intent.EXTRA_SUBJECT,  "標題" );
                   it.setType( "message/rfc822" ); //編碼類型
                   startActivity( Intent..createChooser( it, "選擇一個Email客戶端" ) );
          帶附件:
                   it.putExtra( Intent.EXTRA_STREAM, "file:///sdcard/miaozj.mp3" );
                   sendIntent.setType( "audio/mp3" );
   4. 系統相關
          卸載應用:
                  Uri  uri  =  Uri.fromParts( "package", strPackageName, null );
                  Intent  it  = new  Intent( Intent.ACTION_DELETE,  uri );
                  startActivity( it );
          安裝應用:
                 Uri  installUri  =  Uri.fromParts( "package" ,  "xxx",  null );
                 return  it = new Intent( Intent.ACTION_PACKAGE_ADDED,  installUri );
          創建快捷方式:
                 Intent  shortcut = new Intent( "com.android.launcher.action.INSTALL_SHORTCUT" );
                 shortcut.putExtra( Intent.EXTRA_SHORTCUT_NAME, "shortcutname" );
                 shortcut.putExtra( "duplicate",  false ); //不允許重複創建
                 ShortcutIconResource  iconRes = Intent.ShortcutIconResource.fromContext( this, R.drawable.background );
                 shortcut.putExtra( Intent.EXTRA_SHORTCUT_ICON_RESOURCE,  iconRes ); //資源圖標
                 String  action = "com.android.action.START";
                 Intent  respondIntent = new intent( this, this.getClass() );
                 respondIntent.setAction( action );
                 shortcut.putExtra( Intent.EXTRA_SHORTCUT_INTENT,  respondIntent );
                 sendBroadcast( shortcut );
          刪除快捷方式:
                 Intent  shortcut = new Intent( "com.android.launcher.action.UNINSTALL_SHORTCUT" );
                 shortcut.putExtra( Intent.EXTRA_SHORTCUT_NAME,  "shortcutname" );    
                 String action = "com.android.action.START";
                 String appClass = this.getPackageName() + " . " + this.getLocalClassName();
                 ComponentName comp = new ComponentName( this.getPackageName(),  appClass );//應用標識符
                 shortcut.putExtra( Intent.EXTRA_SHORTCUT_INTENT,  new Intent(action).setComponent(comp) );
                 sendBroadcast( shortcut );
         創建和刪除快捷鍵的權限:   com.android.launcher.permission.INSTALL_SHORTCUT            ......UNINSTALL_SHORTCUT
  5.1.2  Serializable 接口
       Serializable 接口。要實現某個類支持序列化,可是該類繼承Serializable接口。Serializable接口通常在Intent中使用。
       Serializable 接口本質上是基於 ObjectOutStream 和 ObjectInputStream 對象進行的。具備Serializable序列化的類的實現非常簡單。實例如下:      
               public  final  class  CodeSigner  implements Serializable{
                        private  static  final  long serialVersionUID = 4648464646846846L;
               }
       serialVersionUID用於運行時,判斷類版本的一致性,其可由JDK中的 serialver 工具生成,也可以採用默認的方式來定義。
       在Intent中,可通過Bundle來設置序列化數據:
                   putSerializable( String key, Serializable value );
  5.1.3  Parcelable接口
           Parcelable接口是android特定的序列化接口,比Serializable接口有效,常用於Binder 和 AIDL場景中。
           Parcelable接口序列化的數據可存儲在Parcel中,繼承Parcelable接口的類必須具有一個CREATOR的靜態變量。下面是一個Parcelable接口序列化的示例:
                    public  class  CatCmdMessage  implements  Parcelable{
                            public  CatCmdMessage(Parcel  in){
                                     mCmdDet = in.readParcelable(null);
                                     mTextMsg = in.readParcelable(null); 
                                     mMenu = in.readParcelable(null); 
                                     mInput = in.readParcelable(null);
                            }
                            public  void  writeToParcel( Parcel dest,  int flags ){
                                     dest.writeParcelable(mCmdDet, 0);
                                     dest.writeParcelable(mTextMsg, 0);
                                     dest.writeParcelable(mMenu, 0);
                                     dest.writeParcelable(mInput, 0);
                            }
                             public  int  describeContents(){
                                      return 0;
                             }
                             //CREATOR的靜態變量
                             public  static final  Parcelable.Creator<CatCmdMessage> CREATOR = new
                             Parcelable.Creator<CatCmdMessage>(){
                                      public  CatCmdMessage createFromParcel(Parcel in){
                                                 return new CatCmdMessage(in);
                                      }
                                      public  CatCmdMessage[] newArray(int size){
                                                 return new CatCmdMessage[size];
                                      }
                             };
                    }
          通過Intent傳遞Parcelable接口序列化數據的方法如下:
                     public  Intent  putExtra(String name ,  Parcelable value );
          另外,Parcelable也可以通過Bundle在Intent 中傳遞。在Bundle中設置Parcelable的方法如下:
                     public  void  putParcelable( String key,  Parcelable value );  //Parcelable 方法
                     public  Intent  putExtra( String name, Bundle value );  //Intent方法
 5.2 UI事件處理
        主要包括事件監聽器相關事件,事件句柄相關事件,焦點相關事件觸控事件,按鍵事件,軌跡球時間等。UI事件均由View來實現的。
       
 第六章 android多線程編程
        在android中,UI主線程並非線程安全的,所有UI相關的操作均需在UI主線程中完成。在默認情況下,開發者創建的Service, Activity,  Broadcast均運行在UI主線程中,一些耗時的操作,如網絡下載,大文件讀取,加解密計算,數據庫操作等,也放在UI線程中執行,往往會阻塞線程,造成ANR異常。
       android中,實現多線程的方式有,通過原生java線程,或android對java線程的封裝及AsyncTask來實現多線程的效果。
6.1 java線程實現
   1.  基於Thread的實現
          基於Thread實現新線程和在Runnable構造器中實現新線程是傳統java實現線程的兩種方式,基於Thread的實現如下:
                    Static  class  AThread  extends  Thread{
                            ActivityManagerSerivce  mService;
                            boolean  mReady = false;
                            public  AThread(){
                                      super( "ActivityManager" );
                            }
                            public  void  run(){
                                      Looper.prepare();  //初始化事件循環之後應調用loop()
                                   android.os.Process.setThreadPriority( android.os.Process.THREAD_PRIORITY_FOREGROUND ); //設置線程優先級
                                      android.os.Process.setCanSelfBackground( false );
                                      ActivityManagerService m = new ActivityManagerService();
                                      synchronized( this ){
                                               mService = m;
                                               notifyAll();
                                      }
                                      synchronized( this ){
                                               while( !mReady ){
                                                          try{        wait();
                                                          }catch (InterruptedException e){  ........  } 
                                               }
                                      }
                                      Looper.loop();
                            }
                    }
         在Thread聲明週期中,存在NEW,  RUNNABLE,  BLOCKED,  WAITING,  TIMED_WAITING,  TERMINATED等多個狀態。線程的生命週期和狀態遷移過程如下:
            
   2. 基於Runnable的實現
                  private  Runnable  mForceCheckBoxRunnable = new Runnable(){
                          public  void  run(){
                                   if( mCheckBoxPreference != null ){
                                              mCheckBoxPreference.setChecked( !mCheckBoxPreference.isChecked() );
                                   }
                                   mHandler.postDelayed( this, 1000 );
                          }
                  }
             通過Handler的 removeCallbacks()方法可以移除待運行的線程,通過postAtTime()方法可以在特定的時間將線程放入消息隊列。
  3. 線程優先級
       基於linux設置,用數字表示,範圍-20~~19,其中-20爲最高優先級,19爲最低優先級。優先級的設置通常用於處理併發線程產生的阻塞。設置優先級的方法:
                Process.setThreadPriority( priority );
6.2  android線程封裝
     對java線程進行了封裝,最主要的工作包括 AsyncTask封裝 和UI線程。
   1.  AsyncTask封裝
        AsyncTask通常用於後臺線程和UI線程的交互。雖然AsyncTask本質上還是Thread加 Handler的一個封裝,一個實例如下:
               private  class  ReceivePushTask  extends  AsyncTask<Intent ,  Void,  Void> {
                         protected  void  doInBackground( Intent ... intents ){
                                    Intent  intent = intents[ 0 ];
                         }
                         public  void  onReceive(){ ... }
               }
         AsyncTask中最基本的兩個方法doInBackground() 和 onReceive(), 前者執行真正的後臺計算,後者向UI主線程反饋計算結果。爲了使用AsyncTask,必須繼承它。
         其他重要方法: 
                 public  final AsyncTask<Params,  Progress,  Result> execute( Params......  params ); //執行AsyncTask
                 protected  void  onPostExecute( Result  result ); //在doInBackground()方法執行後執行,在UI線程中執行
                 protected  void  onPreExecute(); //在執行doInBackground()方法前執行
                 protected  final void publishProgress( Progress...   values ); //向UI線程反饋計算進度 
         AsyncTask可以支持多個輸入參數甚至可變參數,其參數在execute()方法中傳入。實際開發中,通常存在中斷線程執行的情況,方法如下:
                 public  final  boolean  cancel ( boolean  mayInterruptIfRunning );
         此方法可能因爲進程已經執行結束,已經被中斷或其他原因而執行失敗,判斷AsyncTask是否已經被中斷方法:
                 public  final  boolean  isCancelled();
         在中斷執行時,系統會調用AsyncTask的onCancelled()方法,開發者可在該方法中進行一些收尾工作,但要注意,必須顯式判斷線程是否中斷。
   2. 接入UI線程
        在多線程編程中,執行後臺計算的線程通常需要和UI線程進行交互,將計算結果反饋給UI線程,呈現給用戶,傳統的方法一般需要藉助於Handler。  android,從activity,view和UI主線程3 個層次,提供了4中方式接入Ui線程。
   (1)Activity的runOnUiThread(Runnable)方法
            這個方法本質上是基於Handler的 post(Runnable r) 方法實現的。實例如下:
                    mActivity.runOnUiThread( new Runnable(){
                             public  void  run(){   ......  }
                    } );
   (2)View的 post(Runable)方法
            View的 post(Runnable) 方法從控件層面對多線程提供了支持,實現原理與(1)類似,實現實例:
                     public  void  onClick( View v ){
                              new Thread( new Runnable(){
                                        public  void  run(){
                                                final  Bitmap  bitmap = loadImageFromNetwork( "http://example.com/image.png" );
                                                mImageView.post( new Runnable(){
                                                         public  void  run(){
                                                                  mImageView.setImageBitmap( bitmap );
                                                         }
                                                } );
                                       }
                              } ).start();
                     }
     (3)View 的 postDelayed(Runnable, long) 方法
              View 的postDelayed(Runnable, long)方法提供了延時處理的能力,實例如下:
                     final  static  int  PAUSE_DELAY = 100;
                     Runnable uRunnable = new Runnable(){
                            public  void  run(){
                                    if( mToggle ){
                                            mCount++;
                                            mView.onResume();
                                    }else{
                                           mView.onPause();
                                    }
                                    mToggle = !mToggle;
                                    mView.postDelayed( mRunnable,  PAUSE_DELAY );
                            }
                     }
     (4)Handler 方法
            利用Message消息向UI主線程發送後臺計算的信息,及時將計算結果反饋給用戶。當UI線程接收到Message消息後,會調用其Handler進行消息處理。Handler處理消息的過程如下:
                   Message message = mHandler.obtainMessage( ORIENTATION_CHANGED );
                   mHandler.sendMessage( message );
                   ......
                   Handler mHandler = new Handler(){
                             public  void  handleMessage(Message msg){
                                        switch( msg.what ){
                                                   case  ORIENTATION_CHANGED:
                                                   ......
                                                   break;
                                        }
                             }
                   };
  6.3 線程間的消息通信
         相比Thread加Handler的封裝,AsyncTask更爲可靠,更易於維護。AsyncTask的缺點是,一旦線程開啓即dobackground方法執行後無法向線程發送消息,僅能通過預先設置好的標記來控制邏輯,當然可以通過掛起線程並等待標誌位的改變來進行通信。
        
下面主要介紹消息隊列以及消息的分發和接收
6.3.1 消息隊列
       消息隊列的維護是在線程中進行的,但默認除UI主線程外,其他線程並不擁有消息線程。爲了維護一個消息隊列,需要在線程中調用 Looper 的prepare() 方法進行初始化。爲了使線程可以接收發送過來的消息,通常需要藉助Handler來接收消息。一個維護消息隊列的實例如下:
                 private  class  QueryRunner  extends  Thread{
                          public  Handler  mHandle;
                          public  void  run(){
                                   Looper.prepare();//loop()方法應隨後調用,實現維護消息隊列的初始化
                                   mHandler = new Handler(){
                                             public  void  handleMessage( Message msg ){
                                               //處理得到的消息
                                             }
                                   };
                                   Looper.loop();//提供對消息的監聽
                          }
                 }
          通過Looper還可以判斷當前線程是否爲UI主線程,方法如下:
                 private  void  assertUiThread(){
                          if( !Looper.getMainLooper().equals( Looper.myLooper() ) ){
                                    throw new RuntimeException( "not  on the UI thread" );
                          }
                 }
           通過查看Message的實現即可發現, Message繼承了Parcelable,在消息的傳遞過程中,實際上傳遞的是序列化數據。Message的實現如下:
                  public  final  class Message  implements  Parcelable{
                           public  int  whatl;     //消息標識符
                           public  int  arg1;      //傳遞參數
                           public  int  arg2;      //傳遞對象
                           public  Object  obj; //傳遞的對象
                           public  Messenger  replyTo;
                           long  when;
                           Bundle  data;
                           Handler  target;  //目標Handler
                           Runnable  callback;
                           Message  next;
                           private  static   final  int  MAX_POOL_SIZE = 10;  .......
                  }
         源碼顯示,一個線程會自動維護一個消息池,該消息池的大小爲10 。 在需要生成消息時,首先從消息池中獲取信息,只有當消息池中的消息均被使用時,纔會重新創建新消息,當消息使用結束時,消息池會回收消息,實現過程如下:
                  private  static  final  Object  sPoolSync = new Object();
                     private  static  Message  sPool;
                     private  static  int sPoolSize = 0;  // 消息池當前的大小
                     private  static  final  int  MAX_POOL_SIZE = 10;  //消息池極限
        從消息池中獲取消息:
                   public  static  Message  obtain(){
                            Synchronized( sPoolSync ){
                                    if( sPool != null ){
                                             Message m = sPool;
                                             sPool = m.next;
                                             m.next = null;
                                             sPoolSize--;
                                             return m;
                                    }
                            }
                            return new Message
                   }
        從上可見,在發送消息時,通過obtain()方法獲得Message比創建Message的效率更高,發送消息的實例如下:
                   pubic  void progress( boolean  progress ){
                            android.os.Message   msg =  android.os.Message.obtain();
                            msg.what = MSG_PROGRESS;
                            msg.arg1 = progress ? 1 : 0;
                            sendMessage( msg );
                   }
         向消息池中回收消息的實現過程:
                   public   void  recycle(){
                            synchronized( sPoolSync ){
                                      if( sPoolSize < MAX_POOL_SIZE ){
                                               clearForRecycle();
                                               next = sPool;
                                               sPool = this;
                                               sPoolSize++;
                                      } 
                            }
                   }
   6.3.2 消息分發
           消息的分發和接收均與Handler密切相關,對於消息的分發,android目前提供了兩種消息類型,一種是 post 消息,一種是 send 消息。 其中 post 消息會在未來某一時間加入消息隊列,而 send 消息則會立即加入到消息隊列。
      1.  send  消息
           分發一個簡單的 send 消息的方法如下:
                  Message  msg = mHandler.obtainMessage( KILL_APPLICATION_MSG );
                  msg.arg1 = uid;
                  msg.arg2 = 0;
                  msg.obj = pkg;
                  mHandler.sendMessage( msg );
          如果沒有消息需要傳遞,那麼可以發送僅攜帶消息標示符的空消息,方法: 
                   mHandler.sendEmptyMessage( CANCEL_HEAVY_NOTIFICATION_MSG );
     2.  post 消息
           通過 post 消息可以實現類似循環計算的功能,方法如下:
                   mTicker = new Runnable(){
                            public  void  run(){
                                      if( mTickerStopped ) return;
                                      mCalender.setTimeInMillis( System.currentTimeMillis() );
                                      setText( DateFormat.format( mFormat,  mCalendar ) );
                                      invalidate();
                                      long  now = SystemClock.uptimeMillis();
                                      long  next = now + (1000 - now%1000);
                                      mHandler.postAtTime(mTicker,  next);  //間隔一段時間後,再調用線程
                            } 
                   };
                   mTicker.run();
   6.3.3  消息接收
           消息接收相對簡單,如果存在重要問題,應注意數據的同步,Handler中消息接收的過程:
                  public  void  dispatchMessage( Message msg ){
                           if( msg.callback != null ){
                                     handleCallback( msg ); //通過Message自身回調進行處理
                           }else{
                                     if( mCallback != null ){ //通過當前線程回調進行處理
                                              if( mCallback.handleMessage(msg) ){
                                                         return;
                                              }
                                     }
                                     handleMessage(msg);//Handler自己處理
                           }
                  }
        對於消息隊列而言,在Looper 的loop()方法中執行對消息的監聽。 loop()方法的實現如下:
                  public  static  final  void  loop(){
                           Looper  me = myLooper();
                           MessageQueue  queue = me.mQueue;
                           Binder.clearCallingIdentity();
                           final  long  ident = Bindler.clearCallingIdentity();
                           if( msg != null ){
                                  if(msg.target == null){   return;   }
                           msg.target.dispatchMessage(msg);//調用Handler進行消息處理
                           msg.recycle();//消息回收
                           }
                  }
6.4 線程安全處理
       線程安全多是由多線程對共享資源的訪問引起的。在線程安全層面,android更多采用java的實現,除了java的join(), wait(), sleep(),  notify()等方法和synchronized關鍵字外,還有java的併發庫。
6.4.1 synchronized同步
     在android應用層,線程的併發很多是靠 synchronized 關鍵字來實現的,通過 synchronized 關鍵字,可以實現方法和語句塊的同步。同步的本質是對特定對象的加鎖,他們可以是來自調用方法的對象,也可以是開發這自己的對象。
     另外,synchronized 關鍵字並不能繼承,對於父類的同步方法,在子類中必須再次顯式聲明才能成爲同步方法。
     synchronized 關鍵字的侷限在與 在試圖獲得鎖時,無法設定超時和中斷,每個所只有一個條件,在某些場景下可能不夠用。
     synchronized 關鍵字可以是實現方法同步和語句塊同步。
1. 方法同步
     方法同步分一般方法同步和靜態方法同步。一般方法同步的本質在於將synchronized關鍵字過用於對象引用,作用域僅限類的單個對象。實例:
             public  synchronized  void  setMax(int max){
                      super.setMax(max);   ......
             }
             等同於:
             public  void  setMax(int max){
                      synchronized(this){
                      super.setMax(max);   ......
                      }
             }
     靜態方法同步的本質是將類本身作爲鎖,其作用域是該類的所有對象。下面是BitmaoManager中利用synchronized實現單子模式的過程:
             private  static  BitmapManager  sManager = null;
             public  static  synchronized  BitmapManager  instance(){
                      if( sManager == null ){
                              sManager = new BitmapManager();
                      }
                      return sManager;
             }
  2. 語句塊的同步
        當需要同步的範圍不是很大時,可以採用語句塊同步,下面是一個實例:
              private  void  setOrientation( int  orientation ){
                      synchronized(this){   ......   }
              }
        將對象本身作爲鎖,顯然影響併發效率,更靈巧的是設計自定義鎖。 自定義鎖可以是任何類型的對象,但通常將其類型設計爲Object。實例如下:
              public  abstract  class  AbstractPreferences  extends  Preferences
              {  
                        protected  final  Object  lock;
                        protected   AbstractPreferences getChild(String  name) throws  BackingStoreException{
                                 synchronized(lock){  ......  }
                        }
              }
6.4.2  RPC通信
6.4.3 SQLite調用
      對於SQLite的調用,可能會存在多處同時執行讀寫操作的場景,這種場景也需要考慮線程的安全性。android提供了類似AysncTask的 AsyncQueryHandler 方法來解決這一問題。將耗時的查詢等操作放置非UI主線程中,操作結束後,通過Handler 調用響應的UI主線程的方法處理操作執行的結果。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章