众所周知,在Android系统中,系统允许单个app使用的内存是有限的,这个限制因手机而异。但有时候,我们需要一个计算量较大的后台任务,不希望它占用前台太多的内存。此时,我们可以用Service。通常的Service是在本app的内存中的,接下来我们就记录一种方法,为Service新开一个进程。由于是新开的进程,所以系统会为它分配专门的内存空间,缓解了app前台使用内存的压力。
这个例子,是以一个图书管理的进程为例子。后台进程负责管理图书,拥有添加图书和获取图书列表的方法。
所以,首先,我们需要一个图书类。由于我们将跨进程传输这个对象,所以它必须可以序列化。
// Book.java public class Book implements Parcelable{ public int bookId; public String bookName; public Book(int bookId , String bookName){ this.bookId = bookId; this.bookName = bookName; } private Book(Parcel in) { bookId = in.readInt(); bookName = in.readString(); } public static final Creator<Book> CREATOR = new Creator<Book>() { @Override public Book createFromParcel(Parcel in) { return new Book(in); } @Override public Book[] newArray(int size) { return new Book[size]; } }; @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(bookId); dest.writeString(bookName); } }
图书类比较简单,拥有两个成员属性,id和name。然后有一个public的构造方法,其他是继承了Parcelable接口生成的。由于我们需要用AIDL来传输它,我们还需要一个AIDL文件。
// Book.aidl package com.dream.fishbonelsy.aidldemo.aidl; parcelable Book;
接下来我们需要一个Book的管理接口。所谓管理接口,即后台进程将提供哪些方法供前台使用,我们需要用AIDL来建立这个中间协定。
// IBookManager.aidl package com.dream.fishbonelsy.aidldemo.aidl; import com.dream.fishbonelsy.aidldemo.aidl.Book;interface IBookManager { List<Book> getBookList(); void addBook (in Book book); }
这个接口提供了getBookList()和addBook( in Book book)两个方法,见文知意,这两个方法的作用很明确。
写好了这个接口。我们就可以Clean Project----->Rebuild Project。
Android Studio会我们自动生成IBookManager.java。这个文件比较隐蔽在build---->generated---->source---->aidl---->debug---->包名+.aidl中。我们可以先不要改它写的是什么,因为我们暂时不需要修改它。我们只需要知道,它通过我们建立的接口,以IBinder为通信方式,为我们实现了跨进程通信的功能。
完成了通信接口,我们该去完成后台任务了。
// BookManagerService.java public class BookManagerService extends Service { AtomicBoolean mIsServiceRunning = new AtomicBoolean(true); CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>(); private final IBookManager.Stub mBinder = new IBookManager.Stub() { @Override public List<Book> getBookList() throws RemoteException { synchronized (mBookList) { return mBookList; } } @Override public void addBook(Book book) throws RemoteException { synchronized (mBookList) { if (!mBookList.contains(book)) { mBookList.add(book); } } } }; @Override public IBinder onBind(Intent intent) { return mBinder; } @Override public void onDestroy() { mIsServiceRunning.set(false); super.onDestroy(); } }
在Service中获得IBookManager代理的Binder。在onBind中将其返回。我们则只需要在IBookManager.Stub类,实现我们定义好的方法getBookList()和addBook(Book book)。唯一要注意的就是,这所有的数据都是需要线程安全的,因为作为后台,可能有多个前台需要来访问。
此外,我们还需要在AndroidManifest中对这个Service进行配置,确保它运行在单独的进程中。
<service android:name=".service.BookManagerService" android:process=":remote" > <intent-filter> <action android:name="com.dream.fishbonelsy.aidldemo.service"/> </intent-filter> </service>
写好了这个后台任务,前台的工作就比较简单了。
MainActivity.java:
IBookManager mService; private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mService = IBookManager.Stub.asInterface(service); } @Override public void onServiceDisconnected(ComponentName name) { } }; Intent intent = new Intent("com.dream.fishbonelsy.aidldemo.service"); bindService(intent, connection, Context.BIND_AUTO_CREATE);
只需要两部,我们就可以绑定后台服务,并获取服务的代理IBookManager mService。
这样,就可以调用IBookManager中的方法,并通知后台服务执行它。
btn = (Button) findViewById(R.id.test_btn_id); btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { try { if (mService != null && mService.getBookList() != null){ Log.d("tag", "mService.getBookList().size()==" + mService.getBookList().size()); if (mService.getBookList().size() > 0){ Log.d("tag", "the name of the first book ==" + mService.getBookList().get(0).bookName); } } } catch (RemoteException e) { e.printStackTrace(); } } }); btn2 = (Button) findViewById(R.id.test_btn_id2); btn2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { try { if (mService != null && mService.getBookList() != null) { mService.addBook(new Book(mService.getBookList().size() + 1, "Random Name =" + Math.random())); } } catch (RemoteException e) { e.printStackTrace(); } } });
这只是一个简单的版本,其中还有很多坑等待解决和优化,将在后面的博客的记录。
PS:附上文件结构图,这个是坑,容易犯错。
Done~