java攻城狮之路(Android篇)--BroadcastReceiver&Service

时间:2023-03-09 22:31:47
java攻城狮之路(Android篇)--BroadcastReceiver&Service

四大组件:
activity 显示。
contentProvider 对外暴露自己的数据给其他的应用程序。
BroadcastReceiver 广播接收者,必须指定要接收的广播类型。必须明确的指定action
service 服务,是运行后台,它是没有界面的。对某件事情进行监听。

一、广播:事件。
普通广播: 是异步的。会广播接收者同时接收,不能被中断
sendBroadcast()
有序广播: 是同步的。会根据广播接收的优先级进行接收,是可以中断 短信到来广播
sendOrderBroadcast()
-1000 ~ 1000
如果有序广播明确的指定了广播接收者,他是无法被中断的。
广播接收者的订阅:
1 在清单文件里面指定

<receiver android:name=".SmsReceiver">
<intent-filter android:priority="1000">
<action android:name="android.provider.Telephony.SMS_RECEIVED"/>
</intent-filter>
</receiver>

2 在代码里面指定

IntentFilter filter = new IntentFilter();
filter.setPriority(1000);
filter.addAction("android.provider.Telephony.SMS_RECEIVED");
registerReceiver(new SmsReceiver(), filter);

当广播接收者一旦接收到广播,它会执行里面的onReceive()方法,但是里面是不能执行耗时的操作,这个方式如果在10s之内没有执行完毕,就会出现anr异常,也就是应用无响应异常

练习1:利用广播拦截短信、拦截外拨电话、增加IP拨号功能

java攻城狮之路(Android篇)--BroadcastReceiver&Service

package com.shellway.mysmsreceiver;

import android.support.v7.app.ActionBarActivity;
import android.content.IntentFilter;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem; public class MainActivity extends ActionBarActivity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//代码注册广播接收者
IntentFilter filter = new IntentFilter();
filter.setPriority(1000);
filter.addAction("android.provider.Telephony.SMS_RECEIVED");
registerReceiver(new SmsReceiver(), filter);
}
}

MainActivity.java

package com.shellway.mysmsreceiver;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.text.BoringLayout;
import android.util.Log; public class PhoneReceiver extends BroadcastReceiver { @Override
public void onReceive(Context context, Intent intent) {
//得到外拨电话
String number = getResultData();
Log.i("i", number);
// abortBroadcast();//因为底层明确了广播接收者,所以这种中断打电话的效果不行
// setResultData(null);//应该用这种方式来中断打电话
// 给用户设置IP拨号
setResultData("17951"+number);
}
}

PhoneReceiver.java

package com.shellway.mysmsreceiver;

import java.text.SimpleDateFormat;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.SmsMessage;
import android.telephony.gsm.SmsManager;
import android.util.Log; public class SmsReceiver extends BroadcastReceiver { @Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
Log.i("i", "已经接收到短信"); Bundle bundle = intent.getExtras();
Object[] objects = (Object[]) bundle.get("pdus");
for (Object object : objects) {
SmsMessage smsMessage = SmsMessage.createFromPdu((byte[])object);
//获取短信内容
String body = smsMessage.getDisplayMessageBody();
//获取来短信的电话号码
String addr = smsMessage.getDisplayOriginatingAddress();
//获取短信到来的时间
Long time = smsMessage.getTimestampMillis();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date = sdf.format(time);
Log.i("i", "电话号码:"+addr);
Log.i("i", "短信内容: "+body);
Log.i("i","时间: "+ date);
if(addr.equals("5558")){
//中断广播,注意在配置文件里要设置好这个接收者的优先级为最高
abortBroadcast();
//拦截消息转发给第三方
android.telephony.SmsManager smsManager = android.telephony.SmsManager.getDefault();
smsManager.sendTextMessage("5554", null, addr + ","+ body +","+ date, null, null);
}
}
}
}

SmsReceiver.java

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.shellway.mysmsreceiver"
android:versionCode="1"
android:versionName="1.0" > <uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="21" />
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<uses-permission android:name="android.permission.SEND_SMS"/>
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity> <!-- 这里使用了代码来实现广播接收者的注册来代替
<receiver android:name=".SmsReceiver">
<intent-filter android:priority="1000">
<action android:name="android.provider.Telephony.SMS_RECEIVED"></action>
</intent-filter>
</receiver>
--> <receiver android:name=".PhoneReceiver">
<intent-filter android:priority="1000">
<action android:name="android.intent.action.NEW_OUTGOING_CALL"/>
</intent-filter>
</receiver>
</application> </manifest>

AndroidManifest.xml

运行结果截图:

java攻城狮之路(Android篇)--BroadcastReceiver&Servicejava攻城狮之路(Android篇)--BroadcastReceiver&Service

java攻城狮之路(Android篇)--BroadcastReceiver&Servicejava攻城狮之路(Android篇)--BroadcastReceiver&Service

练习2:自定义广播(这里演示自己接收自己发出的广播)

java攻城狮之路(Android篇)--BroadcastReceiver&Service

package com.shellway.customreceiver;

import android.support.v7.app.ActionBarActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText; public class MainActivity extends ActionBarActivity { private EditText et_data; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//获取用户输入的数据
et_data = (EditText) findViewById(R.id.et_data);
} public void send(View view){
String data = et_data.getText().toString();
//创建一个意图
Intent intent = new Intent(this,CustomReceiver.class);
//给我这个广播自定义一个动作名称
intent.setAction("com.shellway.CustomReceiver");
//往意图里封装数据
intent.putExtra("data", data);
//发送的是普通广播,不是有序广播
sendBroadcast(intent);
}
}

MainActivity.java

package com.shellway.customreceiver;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log; public class CustomReceiver extends BroadcastReceiver { @Override
public void onReceive(Context context, Intent intent) { //接收广播过来的数据
String data = intent.getStringExtra("data");
Log.i("i", data);
}
}

CustomReceiver.java

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
> <TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="要广播的数据:" />
<EditText
android:id="@+id/et_data"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="请输入要广播发送的数据"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="send"
android:text="开始广播"
/> </LinearLayout>

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.shellway.customreceiver"
android:versionCode="1"
android:versionName="1.0" > <uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="21" /> <application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".CustomReceiver">
<intent-filter>
<action android:name="com.shellway.CustomReceiver"></action>
</intent-filter>
</receiver>
</application> </manifest>

AndroidManifest.xml

运行结果截图:

java攻城狮之路(Android篇)--BroadcastReceiver&Service

二、service:服务
运行于后台,没有界面,对某件事情进行监听。它不能自己运行,必须手动激活。

当服务被创建的时候会调用一个onCreate()方法。

练习3:通过服务监听电话的呼叫状态

java攻城狮之路(Android篇)--BroadcastReceiver&Service

package com.shellway.myservice;

import android.support.v7.app.ActionBarActivity;
import android.telephony.TelephonyManager;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View; public class MainActivity extends ActionBarActivity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
} public void startService(View view){
//创建一个意图
Intent intent = new Intent(this,MyService.class);
//启动一个服务
startService(intent);
}
}

MainActivity.java

package com.shellway.myservice;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.Log; public class MyService extends Service { //定义一个系统默认的电话服务管理者
private TelephonyManager tm;
private MyPhoneStateListener listener; //当服务被创建的时候调用该方法
@Override
public void onCreate() {
super.onCreate();
listener = new MyPhoneStateListener();
tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
//监听电话的呼叫状态
tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);
} private final class MyPhoneStateListener extends PhoneStateListener{ @Override
public void onCallStateChanged(int state, String incomingNumber) {
// TODO Auto-generated method stub
super.onCallStateChanged(state, incomingNumber); switch (state) {
case TelephonyManager.CALL_STATE_IDLE://闲置状态
Log.i("i", "闲置状态");
Log.i("i", incomingNumber);
break;
case TelephonyManager.CALL_STATE_OFFHOOK://接听状态
Log.i("i", "接听状态");
break;
case TelephonyManager.CALL_STATE_RINGING://响铃状态
Log.i("i", "响铃状态");
break;
default:
break;
}
}
} @Override
public IBinder onBind(Intent intent) {
return null;
}
}

MyService.java

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
> <Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="startService"
android:text="启动一个监听电话服务"
/> </LinearLayout>

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.shellway.myservice"
android:versionCode="1"
android:versionName="1.0" > <uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="21" />
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".MyService"></service>
</application> </manifest>

AndroidManifest.xml

运行结果截图:

java攻城狮之路(Android篇)--BroadcastReceiver&Service

java攻城狮之路(Android篇)--BroadcastReceiver&Service

java攻城狮之路(Android篇)--BroadcastReceiver&Service

练习4:在通话的同时刻录音频

package com.shellway.myservice;

import java.io.File;
import java.io.IOException; import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.media.MediaRecorder;
import android.os.Environment;
import android.os.IBinder;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.Log; public class MyService extends Service { //定义一个系统默认的电话服务管理者
private TelephonyManager tm = null;
private MyPhoneStateListener listener;
//声明一个音频刻录机
private MediaRecorder mr; //当服务被创建的时候调用该方法
@Override
public void onCreate() {
super.onCreate();
listener = new MyPhoneStateListener();
tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
//监听电话的呼叫状态
tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);
} private final class MyPhoneStateListener extends PhoneStateListener{ String fileName ;
@Override
public void onCallStateChanged(int state, String incomingNumber) {
// TODO Auto-generated method stub
super.onCallStateChanged(state, incomingNumber);
switch (state) {
case TelephonyManager.CALL_STATE_IDLE://闲置状态
Log.i("i", "闲置状态");
Log.i("i", incomingNumber);
if (mr!=null) {
mr.stop();
mr.reset();
mr.release();
mr = null;
}
if (fileName!=null) {
//这里可以写一些上传录制的音频到服务器
//默认只能刻录自己的声音,若要刻录对方声音就要对底层进行修改
Log.i("i", fileName);
}
break;
case TelephonyManager.CALL_STATE_OFFHOOK://接听状态
Log.i("i", "接听状态");
mr = new MediaRecorder();
//1、设置刻录的音频来源,来自麦克风
mr.setAudioSource(MediaRecorder.AudioSource.MIC);
//2、设置所刻录的音频输出格式
mr.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
//3、设置输出数据的编码
mr.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
File file = new File(Environment.getExternalStorageDirectory(),
System.currentTimeMillis()+".3gp");
//获取文件名,如果在刻录完成后可以把它上传到服务器,就要用到 httpClient 3.0
fileName = file.getName();
//4、设置输出的路径
mr.setOutputFile(file.getAbsolutePath()); try {
//准备刻录
mr.prepare();
//开始刻录
mr.start();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
break;
case TelephonyManager.CALL_STATE_RINGING://响铃状态
Log.i("i", "响铃状态");
break;
default:
break;
}
}
} @Override
public IBinder onBind(Intent intent) {
return null;
} @Override
public void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy(); }
}

在练习3项目的MyService类中添加录音的代码

同时添加录音权限和外存储权限:

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>

运行结果截图:

java攻城狮之路(Android篇)--BroadcastReceiver&Service

Service的生命周期:

如果用startService()去启动服务。只能通过stopService()的方式去停止,且:
onCreate() 只会被调用一次
onStart() 只要启动就会多次调用
onDestory() 只会被调用一次

如果用bindService()去启动(绑定)服务:
onCreate() 只会被调用一次
onBind() 只会被调用一次
onUnbind() 只会被调用一次
onDestroy() 只会被调用一次
注意:
如果访问者退出,需要把连接释放。
可以多次绑定,但是解绑服务只能执行一次,多次解绑就会出错。
用bindService()后要记得解绑,若绑定后没有解绑而直接退出,则会出现以下异常:
09-01 04:10:53.014: E/ActivityThread(5415):
Activity cn.itcast.service.MainActivity has leaked ServiceConnection cn.itcast.service.MainActivity$MyServiceConnection@44ee6248 that was originally bound here

服务里面能否执行耗时的操作?
答:服务里面是不能执行耗时的操作。如果需要执行耗时的操作:可以开启子线程来执行。
在开发的时候往往都需要和服务进行通信,就需要使用bindService()来启动服务。

Service的生命周期练习:

java攻城狮之路(Android篇)--BroadcastReceiver&Service

package com.shellway.servicelifecycle;

import android.support.v7.app.ActionBarActivity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View; public class MainActivity extends ActionBarActivity {
private MyServiceConnection conn = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
} //用startService方式开启一个服务
public void startService(View view){
Intent intent = new Intent(this,ServiceLifeCycle.class);
startService(intent);
}
//用startService方式开启一个服务,只能用stopService()去停止这个服务
public void stopService(View view){
Intent intent = new Intent(this,ServiceLifeCycle.class);
stopService(intent);
} //绑定服务,即用bindService()开启一个服务
public void bindService(View view){
Intent intent = new Intent(this,ServiceLifeCycle.class);
bindService(intent, conn, BIND_AUTO_CREATE);
} public void unbindService(View view){
//解绑服务
unbindService(conn);
conn = null;
} private class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// TODO Auto-generated method stub
} @Override
public void onServiceDisconnected(ComponentName name) {
// TODO Auto-generated method stub
}
} @Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
if (conn!=null) {
//解绑服务
unbindService(conn);
conn = null;
}
}
}

MainActivity.java

package com.shellway.servicelifecycle;

import android.app.Service;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.util.Log; public class ServiceLifeCycle extends Service { @Override
public void onCreate() {
// TODO Auto-generated method stub
super.onCreate();
Log.i("i", " onCreate ");
} @Override
public void onStart(Intent intent, int startId) {
// TODO Auto-generated method stub
super.onStart(intent, startId);
Log.i("i", " onStart ");
} @Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
Log.i("i", " onBind ");
return null;
} @Override
public void unbindService(ServiceConnection conn) {
// TODO Auto-generated method stub
super.unbindService(conn);
Log.i("i", " unbindService ");
} @Override
public void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
Log.i("i", " onDestroy ");
}
}

ServiceLifeCycle.java

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="启动服务startService"
android:onClick="startService"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="停止服务stopService"
android:onClick="stopService"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="绑定服务bindService"
android:onClick="bindService"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="解绑服务unbindService"
android:onClick="unbindService"
/> </LinearLayout>

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.shellway.servicelifecycle"
android:versionCode="1"
android:versionName="1.0" > <uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="21" /> <application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".ServiceLifeCycle"></service>
</application> </manifest>

AndroidMainfest.xml

运行结果截图:

java攻城狮之路(Android篇)--BroadcastReceiver&Servicejava攻城狮之路(Android篇)--BroadcastReceiver&Service

如何调用服务里面的方法?步骤:
1 设计一个接口:IStudentQueryService Student queryStudent(int no);
2 在activity里面进行绑定操作:
bindService(intent,conn,flag);
3 写一个连接实现类:

private final class MyServiceConnection implements ServiceConnection{

    public void onServiceConnected(ComponentName name, IBinder service) {
// TODO Auto-generated method stub
ibinder = (IStudnetQueryService) service;
} public void onServiceDisconnected(ComponentName name) {
// TODO Auto-generated method stub
conn = null;
ibinder = null;
}
}

4 在activity里面用IStudentQueryService 来接收onServiceConnected(ComponentName name, IBinder service)
返回过来的IBinder对象。

5 写一个StudentService extends Service ,重写onBind()
编译一个内部类StudentBinder extends Binder implements IStudnetQueryService

6 创建StudentBinder的对象,通过onBind()方法返回。
7 在activity通过onBind()返回的对象调用服务里面的方法。

练习:访问本地服务的方法

java攻城狮之路(Android篇)--BroadcastReceiver&Service

package com.shellway.callservicemethods;

import com.shellway.callservicemethods.domain.Student;
import com.shellway.callservicemethods.imp.IStudentService; import android.support.v7.app.ActionBarActivity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView; public class MainActivity extends ActionBarActivity {
private MyServiceConnection conn;
private EditText et_id;
private IStudentService istu;
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et_id = (EditText) findViewById(R.id.et_id);
tv = (TextView) findViewById(R.id.tv_show);
//创建一个绑定服务的连接
conn = new MyServiceConnection();
//应用一启动就绑定一个服务
Intent intent = new Intent(this,CallServiceMethods.class);
bindService(intent, conn, BIND_AUTO_CREATE);
} //点击查询按钮操作
public void query(View view){
//获得用户输入的数据
String id = et_id.getText().toString();
//调用本地服务里面的方法
Student stu = istu.getStudent(Integer.valueOf(id));
//把返回数据设置到界面
tv.setText(stu.toString());
} private class MyServiceConnection implements ServiceConnection{ @Override
public void onServiceConnected(ComponentName name, IBinder service) {
// TODO Auto-generated method stub
//因为在服务中接口的实现类继承了IBinder,所以可以把它通过Ibinder这个通道返回
istu = (IStudentService) service;
} @Override
public void onServiceDisconnected(ComponentName name) {
// TODO Auto-generated method stub
conn = null;
istu = null;
}
} @Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
//当界面退出时记得解绑
if (conn!=null) {
unbindService(conn);
conn=null;
}
}
}

MainActivity.java

package com.shellway.callservicemethods;

import com.shellway.callservicemethods.domain.Student;
import com.shellway.callservicemethods.imp.IStudentService; import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder; public class CallServiceMethods extends Service {
//定义一个我们自定义的接口的实现类
private MyStudentService iBinder = new MyStudentService();
//模拟一个学生信息数据库
Student[] students = new Student[]{new Student(),new Student(1,"韩信",67),
new Student(2,"蒙恬",58),new Student(3,"颜路",25),new Student(4,"张良",28)}; @Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return iBinder;
} private class MyStudentService extends Binder implements IStudentService{ @Override
public Student getStudent(int id) {
// TODO Auto-generated method stub
return reStudent(id);
}
}
//通过传过来的学生ID获得学生信息,这里是被调用的服务里面的方法
public Student reStudent(int id){
return students[id];
}
}

CallServiceMethods.java

package com.shellway.callservicemethods.domain;

public class Student {
private int id;
private String name;
private int age; public Student() {
super();
// TODO Auto-generated constructor stub
} public Student(int id, String name, int age) {
super();
this.id = id;
this.name = name;
this.age = age;
} public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
} @Override
public String toString() {
return "Student [id=" + id + ", name=" + name + ", age=" + age + "]";
}
}

Student.java

package com.shellway.callservicemethods.imp;

import com.shellway.callservicemethods.domain.Student;

public interface IStudentService {
public Student getStudent(int id);
}

IStudentService.java

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
> <TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="请输入要查询学生的学号:" />
<EditText
android:id="@+id/et_id"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="query"
android:text="查询"
/>
<TextView
android:id="@+id/tv_show"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="学生信息"
/> </LinearLayout>

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.shellway.callservicemethods"
android:versionCode="1"
android:versionName="1.0" > <uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="21" /> <application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".CallServiceMethods"></service>
</application> </manifest>

AndroidManifest.xml

运行结果截图:

java攻城狮之路(Android篇)--BroadcastReceiver&Service

上面的操作是一个本地的服务。在开发的时候有可能还会去调用别人应用里面提供好的服务。

远程绑定服务,调用服务里面的方法。这里一两个名词:IPC(Inter-Process Communication,进程间通信)和AIDL
1 编写一个接口,再把接口文件修改为aidl,不能有修饰符。
如果我们使用了自定义对象需要实现Parcelable接口,还需要定义个一个aidl文件对这个类进行一个描述(Student.aidl).
编译器就会在gen目录下面生成对应的xx.java文件
2 在服务里面创建一个内部类:extends Stub 实现未实现的方法。
生成这个类的对象通过onBind()方法返回。

3 在客户端绑定服务。
注意:

public void onServiceConnected(ComponentName name, IBinder service) {
// TODO Auto-generated method stub
ibinder = Stub.asInterface(service);
}

练习:远程服务的绑定,即访问远程服务的方法。

java攻城狮之路(Android篇)--BroadcastReceiver&Servicejava攻城狮之路(Android篇)--BroadcastReceiver&Service

服务端:注意运行的时候先启动服务端

package com.shellway.callremoteservice;

import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem; public class MainActivity extends ActionBarActivity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
} }

MainActivity.java

package com.shellway.callremoteservice;

import com.shellway.callremoteservice.domain.Student;
import com.shellway.callremoteservice.imp.IStudentService.Stub; import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException; public class CallRemoteService extends Service {
//自定义一个类继承Stub
private MyStudentService iBinder = new MyStudentService();
//模拟虚拟数据
Student[] students = new Student[]{new Student(1,"荆轲",67),new Student(2,"端慕容",58),
new Student(3,"高渐离",25),new Student(4,"高月",28)};
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
//通过ononBind()返回自定义类的对象
return iBinder;
} private class MyStudentService extends Stub{
@Override
public Student getStudent(int id) throws RemoteException {
// TODO Auto-generated method stub
return reStudent(id);
}
} //获取数据
public Student reStudent(int id){
return students[id-1];
}
}

CallRemoteService.java

package com.shellway.callremoteservice.domain;

import android.os.Parcel;
import android.os.Parcelable; public class Student implements Parcelable {
private int id;
private String name;
private int age; public Student(int id, String name, int age) {
super();
this.id = id;
this.name = name;
this.age = age;
} @Override
public int describeContents() {
// TODO Auto-generated method stub
return 0;
} @Override
public void writeToParcel(Parcel dest, int flags) {
// TODO Auto-generated method stub
dest.writeInt(id);
dest.writeString(name);
dest.writeInt(age);
} public static final Parcelable.Creator<Student> CREATOR
= new Parcelable.Creator<Student>() {
public Student createFromParcel(Parcel in) {
return new Student(in);
} public Student[] newArray(int size) {
return new Student[size];
}
}; public Student(Parcel in){
id = in.readInt();
name = in.readString();
age = in.readInt();
} @Override
public String toString() {
return "Student [id=" + id + ", name=" + name + ", age=" + age + "]";
} }

Student.java

package com.shellway.callremoteservice.domain;
parcelable Student;

Student.aidl

package com.shellway.callremoteservice.imp;
import com.shellway.callremoteservice.domain.Student; interface IStudentService {
Student getStudent(int id);
}

IStudentService.aidl

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.shellway.callremoteservice.MainActivity" > <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" /> </RelativeLayout>

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.shellway.callremoteservice"
android:versionCode="1"
android:versionName="1.0" > <uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="21" /> <application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- 这里的service要给它一个action,好让它对外提供接口 -->
<service android:name=".CallRemoteService">
<intent-filter >
<action android:name="com.shellway.callremoteservice.service"/>
</intent-filter>
</service>
</application> </manifest>

AndroidManifest.xml

客户端:注意这里面的另外两个包中的类都要从服务端拷贝过来,所以客户端就不贴一样的代码了。

package com.shellway.callremoteservicemethods;

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.support.v7.app.ActionBarActivity;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView; import com.shellway.callremoteservice.domain.Student;
import com.shellway.callremoteservice.imp.IStudentService;
import com.shellway.callremoteservice.imp.IStudentService.Stub; public class MainActivity extends ActionBarActivity {
private IStudentService iBinder;
private MyServiceConnection conn;
private EditText et_id;
private TextView tv; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et_id = (EditText) findViewById(R.id.et_id);
tv = (TextView) findViewById(R.id.tv_show);
conn = new MyServiceConnection();
//程序一开始就要绑定一个远程服务
Intent intent = new Intent();
intent.setAction("com.shellway.callremoteservice.service");
bindService(intent, conn, BIND_AUTO_CREATE);
} public void query(View view){
String id = et_id.getText().toString();
try {
//这里是调用远程服务中的方法,即自己先前在服务端定义接口实现类,
//然后New出一个对象并传过来,通过它来调用里面服务里面的方法
Student stu = iBinder.getStudent(Integer.valueOf(id));
tv.setText(stu.toString());
} catch (NumberFormatException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} private class MyServiceConnection implements ServiceConnection{ @Override
public void onServiceConnected(ComponentName name, IBinder service) {
// TODO Auto-generated method stub
//这里是得到远程服务中,自己先前在服务端定义接口实现类的对象
iBinder = Stub.asInterface(service);
} @Override
public void onServiceDisconnected(ComponentName name) {
// TODO Auto-generated method stub }
} @Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
unbindService(conn);
}
}

MainActivity.java

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
> <TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="请输入要查询学生的学号:" />
<EditText
android:id="@+id/et_id"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="query"
android:text="查询"
/>
<TextView
android:id="@+id/tv_show"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="学生信息"
/> </LinearLayout>

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.shellway.callremoteservicemethods"
android:versionCode="1"
android:versionName="1.0" > <uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="21" /> <application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application> </manifest>

AndroidManifest.xml

运行效果截图:

java攻城狮之路(Android篇)--BroadcastReceiver&Servicejava攻城狮之路(Android篇)--BroadcastReceiver&Service

练习:调用Android底层已经实现的挂断电话endcall()方法,这里也用到了调用远程服务里方法知识

java攻城狮之路(Android篇)--BroadcastReceiver&Service

/* //device/java/android/android/content/Intent.aidl
**
** Copyright 2007, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/ package android.telephony; parcelable NeighboringCellInfo;

NeighboringCellInfo.aidl

/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ package com.android.internal.telephony; import android.os.Bundle;
import java.util.List;
import android.telephony.NeighboringCellInfo; /**
* Interface used to interact with the phone. Mostly this is used by the
* TelephonyManager class. A few places are still using this directly.
* Please clean them up if possible and use TelephonyManager insteadl.
*
* {@hide}
*/
interface ITelephony { /**
* Dial a number. This doesn't place the call. It displays
* the Dialer screen.
* @param number the number to be dialed. If null, this
* would display the Dialer screen with no number pre-filled.
*/
void dial(String number); /**
* Place a call to the specified number.
* @param number the number to be called.
*/
void call(String number); /**
* If there is currently a call in progress, show the call screen.
* The DTMF dialpad may or may not be visible initially, depending on
* whether it was up when the user last exited the InCallScreen.
*
* @return true if the call screen was shown.
*/
boolean showCallScreen(); /**
* Variation of showCallScreen() that also specifies whether the
* DTMF dialpad should be initially visible when the InCallScreen
* comes up.
*
* @param showDialpad if true, make the dialpad visible initially,
* otherwise hide the dialpad initially.
* @return true if the call screen was shown.
*
* @see showCallScreen
*/
boolean showCallScreenWithDialpad(boolean showDialpad); /**
* End call if there is a call in progress, otherwise does nothing.
*
* @return whether it hung up
*/
boolean endCall(); /**
* Answer the currently-ringing call.
*
* If there's already a current active call, that call will be
* automatically put on hold. If both lines are currently in use, the
* current active call will be ended.
*
* TODO: provide a flag to let the caller specify what policy to use
* if both lines are in use. (The current behavior is hardwired to
* "answer incoming, end ongoing", which is how the CALL button
* is specced to behave.)
*
* TODO: this should be a oneway call (especially since it's called
* directly from the key queue thread).
*/
void answerRingingCall(); /**
* Silence the ringer if an incoming call is currently ringing.
* (If vibrating, stop the vibrator also.)
*
* It's safe to call this if the ringer has already been silenced, or
* even if there's no incoming call. (If so, this method will do nothing.)
*
* TODO: this should be a oneway call too (see above).
* (Actually *all* the methods here that return void can
* probably be oneway.)
*/
void silenceRinger(); /**
* Check if we are in either an active or holding call
* @return true if the phone state is OFFHOOK.
*/
boolean isOffhook(); /**
* Check if an incoming phone call is ringing or call waiting.
* @return true if the phone state is RINGING.
*/
boolean isRinging(); /**
* Check if the phone is idle.
* @return true if the phone state is IDLE.
*/
boolean isIdle(); /**
* Check to see if the radio is on or not.
* @return returns true if the radio is on.
*/
boolean isRadioOn(); /**
* Check if the SIM pin lock is enabled.
* @return true if the SIM pin lock is enabled.
*/
boolean isSimPinEnabled(); /**
* Cancels the missed calls notification.
*/
void cancelMissedCallsNotification(); /**
* Supply a pin to unlock the SIM. Blocks until a result is determined.
* @param pin The pin to check.
* @return whether the operation was a success.
*/
boolean supplyPin(String pin); /**
* Supply puk to unlock the SIM and set SIM pin to new pin.
* Blocks until a result is determined.
* @param puk The puk to check.
* pin The new pin to be set in SIM
* @return whether the operation was a success.
*/
boolean supplyPuk(String puk, String pin); /**
* Handles PIN MMI commands (PIN/PIN2/PUK/PUK2), which are initiated
* without SEND (so <code>dial</code> is not appropriate).
*
* @param dialString the MMI command to be executed.
* @return true if MMI command is executed.
*/
boolean handlePinMmi(String dialString); /**
* Toggles the radio on or off.
*/
void toggleRadioOnOff(); /**
* Set the radio to on or off
*/
boolean setRadio(boolean turnOn); /**
* Request to update location information in service state
*/
void updateServiceLocation(); /**
* Enable location update notifications.
*/
void enableLocationUpdates(); /**
* Disable location update notifications.
*/
void disableLocationUpdates(); /**
* Enable a specific APN type.
*/
int enableApnType(String type); /**
* Disable a specific APN type.
*/
int disableApnType(String type); /**
* Allow mobile data connections.
*/
boolean enableDataConnectivity(); /**
* Disallow mobile data connections.
*/
boolean disableDataConnectivity(); /**
* Report whether data connectivity is possible.
*/
boolean isDataConnectivityPossible(); Bundle getCellLocation(); /**
* Returns the neighboring cell information of the device.
*/
List<NeighboringCellInfo> getNeighboringCellInfo(); int getCallState();
int getDataActivity();
int getDataState(); /**
* Returns the current active phone type as integer.
* Returns TelephonyManager.PHONE_TYPE_CDMA if RILConstants.CDMA_PHONE
* and TelephonyManager.PHONE_TYPE_GSM if RILConstants.GSM_PHONE
*/
int getActivePhoneType(); /**
* Returns the CDMA ERI icon index to display
*/
int getCdmaEriIconIndex(); /**
* Returns the CDMA ERI icon mode,
* 0 - ON
* 1 - FLASHING
*/
int getCdmaEriIconMode(); /**
* Returns the CDMA ERI text,
*/
String getCdmaEriText(); /**
* Returns true if OTA service provisioning needs to run.
* Only relevant on some technologies, others will always
* return false.
*/
boolean needsOtaServiceProvisioning(); /**
* Returns the unread count of voicemails
*/
int getVoiceMessageCount(); /**
* Returns the network type
*/
int getNetworkType(); /**
* Return true if an ICC card is present
*/
boolean hasIccCard(); /**
* Return if the current radio is LTE on CDMA. This
* is a tri-state return value as for a period of time
* the mode may be unknown.
*
* @return {@link Phone#LTE_ON_CDMA_UNKNOWN}, {@link Phone#LTE_ON_CDMA_FALSE}
* or {@link PHone#LTE_ON_CDMA_TRUE}
*/
int getLteOnCdmaMode();
}

ITelephony.aidl

package com.shellway.endcall;

import android.support.v7.app.ActionBarActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem; public class MainActivity extends ActionBarActivity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//这里我们让应用一启动就开始一个服务
Intent intent = new Intent(this,EndCallService.class);
startService(intent);
}
}

MainActivity.java

package com.shellway.endcall;

import java.lang.reflect.Method;
import com.android.internal.telephony.ITelephony; import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
import android.provider.CallLog.Calls;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager; public class EndCallService extends Service {
private TelephonyManager tm;
private MyPhoneStateListener listener;
@Override
public void onCreate() {
// TODO Auto-generated method stub
super.onCreate();
listener = new MyPhoneStateListener();
tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);
} @Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
} private class MyPhoneStateListener extends PhoneStateListener {
@Override
public void onCallStateChanged(int state, String incomingNumber) {
// TODO Auto-generated method stub
super.onCallStateChanged(state, incomingNumber); switch (state) {
case TelephonyManager.CALL_STATE_IDLE://闲置状态 break;
case TelephonyManager.CALL_STATE_OFFHOOK://通话状态 break;
case TelephonyManager.CALL_STATE_RINGING://响铃状态
/**
* Android里面所有的底层服务都是交给了一个类ServiceManager来管理,我们可以用Everything工具来找到这个类
* 这个类里面有IBinder getService(String name),void addService(String name,IBinder service)
* 这里我们要想得到Android给我们提供好的挂断电话方法endCall(),就得先得到它给我们返回的IBinder对象
* 然后把它转成ITelephoneService
*/
endcall(incomingNumber); break; default:
break;
}
} public void endcall(String incomingNumber){
if(incomingNumber.equals("5558")){
try {
//这里为了搞到ServiceManager里的getService返回给我们一个IBinder对象,用了反射实现
Class clazz = Class.forName("android.os.ServiceManager");
Method method = clazz.getMethod("getService", String.class);
//这个是代理对象,我们还要对它进行转化
IBinder service = (IBinder) method.invoke(null, Context.TELEPHONY_SERVICE);
ITelephony ibinder = ITelephony.Stub.asInterface(service);
ibinder.endCall();
//注册一个内容提供者
getContentResolver().registerContentObserver(Calls.CONTENT_URI, true,
new MyContentObserver(incomingNumber)); } catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} private class MyContentObserver extends ContentObserver{
private String incomingNumber;
public MyContentObserver(String incomingNumber) {
super(new Handler());
// TODO Auto-generated constructor stub
this.incomingNumber = incomingNumber;
} @Override
public void onChange(boolean selfChange) {
// TODO Auto-generated method stub
super.onChange(selfChange);
//删除通话记录 通过记录的存储是一个异步的操作
Uri uri = Calls.CONTENT_URI;
getContentResolver().delete(uri, "number = ?", new String[]{"5558"});
//取消内容观察者
getContentResolver().unregisterContentObserver(this);
}
}
}
}

EndCallService.java

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.shellway.endcall.MainActivity" > <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" /> </RelativeLayout>

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.shellway.endcall"
android:versionCode="1"
android:versionName="1.0" > <uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="21" />
<!-- 监听电话状态的权限 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<!-- 挂断电话需要同打电话一样的权限 -->
<uses-permission android:name="android.permission.CALL_PHONE"/>
<!-- 读、写通话记录的权限 -->
<uses-permission android:name="android.permission.READ_CALL_LOG"/>
<uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".EndCallService"></service>
</application> </manifest>

AndroidManifest.xml