游戏开发工具

Android Service精通(第二部分)

Step 3:创建一个ISalary.aidl的文件,在里面写一个简单的获取工资信息的方法:

package com.jay.example.aidl;

import com.jay.example.aidl.Salary;  
import com.jay.example.aidl.Person;  
interface ISalary  
{  
  //定义一个Person对象作为传入参数  
  //接口中定义方法时,需要制定新参的传递模式,这里是传入,所以前面有一个in  
  Salary getMsg(in Person owner);  
}


**ps:**这里可以记得如果使用的是自定义的数据类型的话,需要import哦!!!切记!!!

**Step 4:**核心Service的编写: 定义一个SalaryBinder类继承Stub,从而实现ISalary和IBinder接口;定义一个存储信息的Map集合! 重新onBind方法,返回SalaryBinder类的对象实例!

AidlService.java

package com.jay.example.aidl_complexservice;  

import java.util.HashMap;  
import java.util.Map;  
import com.jay.example.aidl.ISalary.Stub;  
import com.jay.example.aidl.Person;  
import com.jay.example.aidl.Salary;  
import android.app.Service;  
import android.content.Intent;  
import android.os.IBinder;  
import android.os.RemoteException;  

public class AidlService extends Service 
{  
    private SalaryBinder salaryBinder;  
    private static Map<Person,Salary> ss = new HashMap<Person, Salary>();  
    //初始化Map集合,这里在静态代码块中进行初始化,当然你可也以在构造方法中完成初始化  
    static  
    {  
        ss.put(new Person(1, "Jay"), new Salary("码农", 2000));  
        ss.put(new Person(2, "GEM"), new Salary("歌手", 20000));  
        ss.put(new Person(3, "XM"), new Salary("学生", 20));  
        ss.put(new Person(4, "MrWang"), new Salary("老师", 2000));  
    }  

    @Override  
    public void onCreate() 
{  
        super.onCreate();  
        salaryBinder = new SalaryBinder();  
    }  

    @Override  
    public IBinder onBind(Intent intent) 
{  
        return salaryBinder;  
    }  

    //同样是继承Stub,即同时实现ISalary接口和IBinder接口  
    public class SalaryBinder extends Stub  
    {  
        @Override  
        public Salary getMsg(Person owner) throws RemoteException 
{  
            return ss.get(owner);  
        }  
    }  

    @Override  
    public void onDestroy() 
{  
      System.out.println("服务结束!");  
      super.onDestroy();  
    }  
}


注册下Service:

<service android:name=".AidlService">  
    <intent-filter>    
        <action android:name="android.intent.action.AIDLService" />  
        <category android:name="android.intent.category.DEFAULT" />  
    </intent-filter>    
</service>


2——客户端编写

**Step 1:**把服务端的AIDL文件拷贝下,拷贝后目录如下:

1.jpg


Step 2:编写简单的布局,再接着就是核心MainActvitiy的实现了 定义一个ServciceConnection对象,重写对应方法,和前面的普通数据的类似 再接着在bindService,然后再Button的点击事件中获取Salary对象并显示出来!

MainActivity.java

package com.jay.example.aidl_complexclient;  

import com.jay.example.aidl.ISalary;  
import com.jay.example.aidl.Person;  
import com.jay.example.aidl.Salary;  

import android.app.Activity;  
import android.app.Service;  
import android.content.ComponentName;  
import android.content.Intent;  
import android.content.ServiceConnection;  
import android.os.Bundle;  
import android.os.IBinder;  
import android.os.RemoteException;  
import android.view.View;  
import android.view.View.OnClickListener;  
import android.widget.Button;  
import android.widget.EditText;  
import android.widget.TextView;  

public class MainActivity extends Activity 
{  
private ISalary salaryService;  
private Button btnquery;  
private EditText editname;  
private TextView textshow;  
private ServiceConnection conn = new ServiceConnection() 
{  

@Override  
public void onServiceDisconnected(ComponentName name) 
{  
            salaryService = null;  
}  

@Override  
public void onServiceConnected(ComponentName name, IBinder service) 
{  
    //返回的是代理对象,要调用这个方法哦!  
    salaryService = ISalary.Stub.asInterface(service);  
}  
};  

@Override  
protected void onCreate(Bundle savedInstanceState) 
{  
super.onCreate(savedInstanceState);  
setContentView(R.layout.activity_main);  

btnquery = (Button) findViewById(R.id.btnquery);  
editname = (EditText) findViewById(R.id.editname);  
textshow = (TextView) findViewById(R.id.textshow);  

Intent it = new Intent();  
it.setAction("com.jay.aidl.AIDL_SERVICE");  
bindService(it, conn, Service.BIND_AUTO_CREATE);  

btnquery.setOnClickListener(new OnClickListener() 
{    
@Override  
public void onClick(View v) 
{  
try  
{  
            String name = editname.getText().toString();  
            Salary salary = salaryService.getMsg(new Person(1,name));  
    textshow.setText(name + salary.toString());  
}
catch(RemoteException e)
{
    e.printStackTrace();
        }  
}  
});  

}  
@Override  
protected void onDestroy() 
{  
            super.onDestroy();  
    this.unbindService(conn);  
}  
}


运行截图:

2.jpg


PS: 这里的代码是之前用Eclipse写的代码,Android Studio下自定义类型有点问题, 暂时没找到解决方法,如果知道的朋友请告知下!!!万分感激!!! 出现的问题如下: 

3.jpg


两个实例的代码下载(基于Eclipse的):
1)
使用AIDL完成进程间的简单通信
2)传递复杂数据的AIDL Service的实现


3.直接通过Binder的onTransact完成跨进程通信

上面讲过Android可以通过Binder的onTrensact方法来完成通信,下面就来简单试下下,还是前面那个根据 序号查询名字的例子:

服务端实现

/**
 * Created by Jay on 2015/8/18 0018.
 */
public class IPCService extends Service
{

private static final String DESCRIPTOR = "IPCService";
private final String[] names = {"B神","艹神","基神","J神","翔神"};
private MyBinder mBinder = new MyBinder();

private class MyBinder extends Binder 
{
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) 
throws RemoteException 
{
switch (code)
{
case 0x001: 
{
data.enforceInterface(DESCRIPTOR);
int num = data.readInt();
reply.writeNoException();
reply.writeString(names[num]);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
}

@Override
public IBinder onBind(Intent intent) 
{
return mBinder;
}
}


客户端实现

public class MainActivity extends AppCompatActivity implements View.OnClickListener
{

private EditText edit_num;
private Button btn_query;
private TextView txt_result;
private IBinder mIBinder;
private ServiceConnection PersonConnection  = new ServiceConnection()
{
@Override
public void onServiceDisconnected(ComponentName name)
{
mIBinder = null;
}

@Override
public void onServiceConnected(ComponentName name, IBinder service)
{
mIBinder = service;
}
};

@Override
protected void onCreate(Bundle savedInstanceState) 
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindViews();

//绑定远程Service
Intent service = new Intent("android.intent.action.IPCService");
service.setPackage("com.jay.ipcserver");
bindService(service, PersonConnection, BIND_AUTO_CREATE);
btn_query.setOnClickListener(this);
}

private void bindViews() 
{
edit_num = (EditText) findViewById(R.id.edit_num);
btn_query = (Button) findViewById(R.id.btn_query);
txt_result = (TextView) findViewById(R.id.txt_result);
}

@Override
public void onClick(View v) 
{
int num = Integer.parseInt(edit_num.getText().toString());
if (mIBinder == null)
{
Toast.makeText(this, "未连接服务端或服务端被异常杀死", Toast.LENGTH_SHORT).show();

else 
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
String _result = null;
try
{
_data.writeInterfaceToken("IPCService");
_data.writeInt(num);
mIBinder.transact(0x001, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
txt_result.setText(_result);
edit_num.setText("");
}
catch (RemoteException e)
{
e.printStackTrace();

finally
{
_reply.recycle();
_data.recycle();
}
}
}
}


运行截图:

1.gif


代码比较简单,就不多解释了~用到自己改改即可! PS:代码参考于:Android aidl Binder框架浅析


4.Android 5.0后Service一些要注意的地方

今天在隐式启动Service的时候,遇到这样一个问题 

4.jpg

然后程序一启动就崩了,后来苦扣良久才发下是Android 5.0惹的祸, 原来5.0后有个新的特性,就是: Service Intent must be explitict! 好吧,就是不能隐式去启动Service咯,解决的方法也很简单! 比如StartService的:

startService(new Intent(getApplicationContext(), "com.aaa.xxxserver")); 这样写程序直接crash掉,要写成下面这样: startService(new Intent(getApplicationContext(), LoadContactsService.class));

如果是BindService的: Intent service = new Intent("android.intent.action.AIDLService"); 的基础上,要加上包名: service.setPackage("com.jay.ipcserver"); 这样就可以了~

官方文档:http://developer.android.com/intl/zh-cn/guide/components/intents-filters.html#Types 文档说明处:

5.jpg

本节小结:

好的,关于Service的最后一节就到这里,本节讲解了Binder的基本概念以及实现进程间通信的 两种方式:通过AIDL以及Binder.onTransact()来实现跨进程通信!最后还讲解了下Android 5.0后 使用Service不能隐式启动的注意事项!就到这里,谢谢~