Android技能樹 — 多程式相關小結

青蛙要fly發表於2018-02-27

前言

最近過完年了,打算把自己的Android知識都整理一下。

Android技能書系列:

Android基礎知識

Android技能樹 — 動畫小結

Android技能樹 — View小結

Android技能樹 — Activity小結

Android技能樹 — View事件體系小結

Android技能樹 — Android儲存路徑及IO操作小結

Android技能樹 — 多程式相關小結

Android技能樹 — Drawable小結

資料結構基礎知識

Android技能樹 — 陣列,連結串列,雜湊表基礎小結

Android技能樹 — 樹基礎知識小結(一)

演算法基礎知識

Android技能樹 — 排序演算法基礎小結

這次是講Android儲存路徑及IO的基本操作。因為我們在開發的時候會經常這種方便的需求。這篇文章的內容我寫的可能很少,都沒有細寫。別吐槽。o( ̄︶ ̄)o

其他不多說,先上腦圖:

多程式小結腦圖下載

Android技能樹 — 多程式相關小結

Android技能樹 — 多程式相關小結

多程式

程式與執行緒

Android技能樹 — 多程式相關小結

有時候面試別人的時候,我會問說什麼是多程式,怎麼開啟多程式,他們會說new 一個Thread。所以很多人會把多程式和多執行緒弄錯。我就簡單說明下:一般來說我們啟動一個APP,就是一個程式,然後這個APP裡面有很多執行緒,最熟悉的就是我們平常的主執行緒(UI)執行緒。所以程式是包含執行緒的。

當然我這講的就比較通俗了: 可以看下其他類似的文章介紹:Android--程式與執行緒

開啟多程式

Android技能樹 — 多程式相關小結

其實開啟多程式很簡單。只需要在AndroidManifest.xml的四大元件中新增android:process即可。這時候就會執行在你定義好的名字的程式中了。

多程式開啟後的問題

簡單來說就是同步會有問題。我們剛才說了一般來說啟動一個APP,就建立了一個程式,然後所有的東西都在這個程式裡面。這時候你對某個Activity定義了android:process。他就執行在另外一個程式了。這時候Application也會重新建立一次,在這個新的程式中。這個Activity也會在這個新的程式中。而且我們建立的一些實體類物件也是不同程式裡面各自產生自己的副本物件。互不關聯。

Android技能樹 — 多程式相關小結

所以我們知道了一些執行緒同步。單例模式都無效了,因為物件是各個程式中有副本,同步鎖的鎖物件都不是同一個物件。當然執行緒同步機制就失效了。

其中SharePreferences本身是一個檔案,所以不受多程式的影響,但是因為SharePreferences不支援多個程式同時執行寫操作,所以有可能會導致出現資料丟失等問題。甚至是併發讀和寫也可能有問題。但是如果你只是一個程式寫,一個程式讀,而且不是同時,那就問題不大了。

程式間通訊

既然說了多程式,如果我們現在就是二個程式進行通訊怎麼辦。在講如何通訊之前,我們可以先看下相關的基礎,那就是序列化及反序列化。

我們看序列化有哪些:

Android技能樹 — 多程式相關小結

我們可以看到,序列化一般主要是二個,那就是Serialzable和Parcelable。

具體的時候都很簡單。下面寫大致提下這二個的使用。

Serialzable

User.java (要傳遞的實體類)

public class User implements Serializable {

	private static final long serialVersionUID = 512345678910L;

    public int userId;
    public String userName;
    public boolean isMale;

    public User(int userId, String userName, boolean isMale) {
        this.userId = userId;
        this.userName = userName;
        this.isMale = isMale;
    }
}
複製程式碼

我們只要直接將我們的類實現Serializable介面即可。很簡單。這裡我提一下serialVersionUID。因為我們平時寫都不會寫這個。也是正常使用。但是比如我把這個User物件通過ObjectOutputStream序列化後寫到了本地檔案,但是這時候我們把我們的User物件裡面的屬性改了,比如增加了一項:public boolean haha;然後再通過ObjectInputStream去讀取出來就會拋異常。因為反序列化會和序列化時候的serialVersionUID進行比較,如果不同,直接不進行反序列化了,就丟擲異常。但是我們不手動寫這個值,它會根據當前這個類結構去生成的hash值為值。所以當我們把這個類結構更改後,再去反序列化就報錯了。

Parcelable

public class User implements Parcelable {

    public int userId;
    public String userName;
    public boolean isMale;


    public User(int userId, String userName, boolean isMale) {
        this.userId = userId;
        this.userName = userName;
        this.isMale = isMale;
    }

    protected User(Parcel in) {

        userId = in.readInt();
        userName = in.readString();
        isMale = in.readInt() == 1;
    }

    public static final Creator<User> CREATOR = new Creator<User>() {
        @Override
        public User createFromParcel(Parcel in) {
            return new User(in);
        }

        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {

        dest.writeInt(userId);
        dest.writeString(userName);
        dest.writeInt(isMale ? 1 : 0);

    }
}

複製程式碼

你寫的類實現了Parcelable後AS會自動提示,你就按照他的提示生成相應的程式碼即可。這裡我們只要注意這麼幾個地方:

1.我們在序列化前,總要先把這個類例項化成物件,然後把相應的內容賦值進去是吧,所以上面程式碼中,我寫了個建構函式:

public User(int userId, String userName, boolean isMale) {
        this.userId = userId;
        this.userName = userName;
        this.isMale = isMale;
}
複製程式碼

這樣我們寫程式碼的時候就new User(10,"dyp",true)(當然你也可以寫setXXX方法去設定)

2.我們要序列化了,我們把我們的這個類裡面的屬性值都寫進Parcel中,就好比我們是拿了個本子,一行行的記下內容,然後等會一行行的取出來。所以我們看到了。我們是按照順序先記錄下來,所以等會還原的時候也要按順序取出來相應的值。所以順序很重要。

@Override
public void writeToParcel(Parcel dest, int flags) {
   //記下userId,因為是Int型別,所以用writeInt
   dest.writeInt(userId);
   //記下userName,因為是String型別,所以用writeString
   dest.writeString(userName);
   
   /*記下isMale ,因為是Boolean型別,
     但是沒有writeBoolean,只有writeBooleanArray,
     所以我們用writeInt()來記錄,1是true,0是false。
     額外說下writeBooleanArray內部其實還是用writeInt來記錄的。
   */
   dest.writeInt(isMale ? 1 : 0);
}
複製程式碼

3.我們最後傳到了其他的程式,肯定是要從Parcel裡面把我們的物件給還原出來,肯定是先new 一個User物件,然後把各種我們前面第二步儲存好的值給它重新賦值。

public static final Creator<User> CREATOR = new Creator<User>() {
        @Override
        public User createFromParcel(Parcel in) {
			
	    //這裡是不是我們先進行了new一個物件,同時把Parcel物件傳入。
            return new User(in);
        }

        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };
複製程式碼
protected User(Parcel in) {
	
     //然後我們再生成這個物件的同時,再把這個物件的屬性都賦值好,切記要按照上面寫入的順序來讀取出來賦值。
     userId = in.readInt();
     userName = in.readString();
     isMale = in.readInt() == 1;
}
複製程式碼

Binder

序列化的相關的基礎講了。我們來看Binder ,其實吧,Binder我也不知道怎麼講,直接貼別的大佬的相關文章了。

圖文詳解 Android Binder跨程式通訊的原理

Android Binder之應用層總結與分析

然後這裡特別提一下:

Android技能樹 — 多程式相關小結

程式間通訊方式

Android技能樹 — 多程式相關小結

所以我們可以一個個具體來看實現程式間通訊方式。

使用Bundle

Android技能樹 — 多程式相關小結

其實這個我們平時用的很多,

我寫個Demo大家就知道了。

MainActivity.java

User user = new User(10,"dongyaoping",true);
Intent intent = new Intent(MainActivity.this,MyService.class);
Bundle bundle = new Bundle();
bundle.putSerializable("data",user);
bundle.putInt("int",10);
bundle.putString("string","haha");
intent.putExtras(bundle);
startService(intent);
複製程式碼

MyService.java (記得在AndroidManifest.xml中設定android:process屬性,讓它在另外一個程式)

public class MyService extends Service{

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        User user = (User) intent.getExtras().getSerializable("data");
        Log.v("dyp","user:"+user.toString());
        return super.onStartCommand(intent, flags, startId);
    }

}
複製程式碼

所以我們看到Bundle可以put進去很多東西,因為Bundle本身實現了Parcelable

public final class Bundle extends BaseBundle implements Cloneable, Parcelable {}
複製程式碼

使用檔案共享

Android技能樹 — 多程式相關小結

其實這個就更簡單了。我們只需要把一個要傳的資料寫到一個檔案,然後在另外一個程式中去讀取這個檔案就可以了。

一個程式中取寫入:

User user = new User(100,"dyp",true);
try {
     ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(path));
     out.writeObject(user);
     out.close();
} catch (Exception e) {
     e.printStackTrace();
}
複製程式碼

另外一個程式中取讀取:

try {
     ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(path));
     User user = (User ) inputStream.readObject();                   
} catch (Exception e) {
     e.printStackTrace();
}
複製程式碼

使用Socket

Android技能樹 — 多程式相關小結

直接貼上大佬的文章:

Android:這是一份很詳細的Socket使用攻略

使用Binder

  1. AIDL
  2. Messenger
  3. ContentProvider

Android技能樹 — 多程式相關小結

我們主要講下AIDL,因為Messenger是AIDL的封裝,使用起來也更方便。AIDL會了。Messenger也就會使用。ContentProvider的教程就更多了。說實話平時四大元件中ContentProvider使用的頻率很少很少。所以我也不具體寫了,網上的教程也很多。

貼上大佬的ContentProvider教程:

Android:關於ContentProvider的知識都在這裡了!

貼上另外大佬的 Messenger的教程:

Android 進階10:程式通訊之 Messenger 使用與解析

我們主要來看AIDL的實現:

Android技能樹 — 多程式相關小結

具體的細節大家可以看腦圖,我就不細說了。

我們可以看到在客戶端跨程式訪問服務端的時候,我們分了五步。

第一步:建立AIDL檔案。

Android技能樹 — 多程式相關小結

這裡我們要注意一點。我們在AS中建立AIDL,直接就可以右鍵 --> New --> AIDL即可。這時候會在這個目錄下面。

Android技能樹 — 多程式相關小結

這時候我們會看到這樣的介面。

interface IMyAidlInterface {

    String getInfor(String s);
    String getName(char name);

	//傳遞物件。
	String getBook(in Book book);

}

複製程式碼

如果我們要傳遞一個Book物件,這時候這個Book.java應該是在java包裡面,所以我們同時還要再aidl資料夾中建立一個跟這個物件同名的aidl檔案。所以變成了這樣:

Android技能樹 — 多程式相關小結

不過一定要切記,整個Book.java和Book.aidl的包名要一樣。不然會提示找不到Book這個類。

第二步:宣告一個 IBinder 介面例項(或者基於 AIDL 生成)。

然後我們build下之後,AS就會根據我們寫的AIDL自動生成一個IBinder檔案。(當然如果你第一步不寫AIDL,完全自己寫一個IBinder檔案也是可以的。)

Android技能樹 — 多程式相關小結

第三步:實現 ServiceConnection
final ServiceConnection connection = new ServiceConnection() {
      @Override
      public void onServiceConnected(ComponentName name, IBinder service) {
         
      }

      @Override
      public void onServiceDisconnected(ComponentName name) {
         
      }
};
複製程式碼
第四步:呼叫 Context.bindService(),以傳入您的 ServiceConnection 實現。
Intent intent = new Intent(MainActivity.this, MyService.class);
bindService(intent, connection, Context.BIND_AUTO_CREATE);
複製程式碼
第五步:onServiceConnected() 實現中實現相關操作

在你的 onServiceConnected() 實現中,你將收到一個 IBinder 例項(名為 service)。呼叫 YourInterfaceName.Stub.asInterface((IBinder)service),以將返回的引數轉換為 YourInterface 型別。

final ServiceConnection connection = new ServiceConnection() {
      @Override
      public void onServiceConnected(ComponentName name, IBinder service) {
          Log.v("dyp", "已經連線上了");
          IMyAidlInterface iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
          try {
              String haha = iMyAidlInterface.getInfor("hello,我是activity");
              Log.v("dyp", "接受到Service發過來的字串:" + haha);
          } catch (RemoteException e) {
              e.printStackTrace();
          }
      }

      @Override
      public void onServiceDisconnected(ComponentName name) {
           Log.v("dyp","斷開了連線");
      }
};
複製程式碼
服務端第一步:例項化 YourInterfaceName.Stub物件
private IBinder binder = new IMyAidlInterface.Stub() {
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }

        @Override
        public String getInfor(String s) throws RemoteException {
            Log.v("dyp","接收到Activity的字串:"+s);
            return "service傳過去的字串";
        }

        @Override
        public String getBook(Book book) throws RemoteException {
            return null;
        }

        @Override
        public String getName(char name) throws RemoteException {
            return null;
        }
};

複製程式碼

服務端第二步:onBind方法中返回上面生成的物件

@Nullable
@Override
public IBinder onBind(Intent intent) {
    return binder;
}
複製程式碼

各種通訊方式比較

直接複製別人網上的圖片:

Android技能樹 — 多程式相關小結

結尾

我還是不知道說啥,大家輕點噴我就行。。。。。

Android技能樹 — 多程式相關小結

相關文章