首先是环境搭建,http://www.devdiv.com/thread-101586-1-1.html,这边文章讲的很详细了。 要注意的是: 由于adt的升级,我们需要把工程的lib目录手动改成libs,然后build-path.
Client这边包含有消息的收发,解析以及持久连接的发起,重连等功能呢,十分强大,我们开发时完全不用管底层的连接,也不用担心断线,可以专注于业务部分的开发。
同时,代码结构也很简单。去除android的Service和BroadCast类以及一些工具类和常量类不谈:
1.NotificationIQ,NotificationIQProvider,NotificationPacketListener三个类负责对收到的Notification格式的消息进行解析和处理,
2.XmppManager是主控制器,NotificationService通过这个类,在后台维护androidpn连接。
3.PersistentConnectionListener,PhoneStateChangeListener,ReconnectionThread.java三个类则负责监听手机的状态并进行断线重连。
我们自定义消息时需要定义3个类:在***IQ中定义消息的实体,在***IQProvider中将消息转化为***IQ实体,在***PacketListener中对实体进行处理,具体的实现可参考NotificationIQ,NotificationIQProvider,NotificationPacketListener三个类。
程序执行流程:
DemoAppActivity->ServiceManager.startService()->NotificationService.onCreate()->NotificationService.start()->xmppManager.connect()->xmppManager.submit…Task()->xmppManager.addTask()->任务执行->NotificationReceiver发出通知。
下面结合代码分析
DemoAppActivity: 程序入口,在onCreate方法中调用
ServiceManager.startService().
ServiceManager serviceManager = new ServiceManager(this);
serviceManager.setNotificationIcon(R.drawable.notification);
serviceManager.startService();
程序来到ServiceManager,首先在构造方法中加载了raw文件夹下的一些参数放在首选项中,主要是xmpp的ip与port.同时把当前context,既DemoAppActivity的包名与类名也放在首选项中,供后边回调。
props = loadProperties();
apiKey = props.getProperty("apiKey", "");
xmppHost = props.getProperty("xmppHost", "127.0.0.1");
xmppPort = props.getProperty("xmppPort", "5222");
sharedPrefs = context.getSharedPreferences(
Constants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE);
Editor editor = sharedPrefs.edit();
editor.putString(Constants.API_KEY, apiKey);
editor.putString(Constants.VERSION, version);
editor.putString(Constants.XMPP_HOST, xmppHost);
editor.putInt(Constants.XMPP_PORT, Integer.parseInt(xmppPort));
editor.putString(Constants.CALLBACK_ACTIVITY_PACKAGE_NAME,
callbackActivityPackageName);
editor.putString(Constants.CALLBACK_ACTIVITY_CLASS_NAME,
callbackActivityClassName);
editor.commit();
然后是执行startService方法,其中启动了一个线程去启动NotificationService
public void startService() {
Thread serviceThread = new Thread(new Runnable() {
@Override
public void run() {
Intent intent = NotificationService.getIntent();
context.startService(intent);
}
});
serviceThread.start();
}
程序来到NotificationService,这个类是负责执行xmppManager中task的后台服务类。首先在构造方法中初始化了一系列的成员变量。
notificationReceiver = new NotificationReceiver(); //监听通知的广播接收者
connectivityReceiver = new ConnectivityReceiver(this); //监听系统连接状态的广播接收者
phoneStateListener = new PhoneStateChangeListener(this); //系统状态监听器
executorService = Executors.newSingleThreadExecutor(); //线程池
taskSubmitter = new TaskSubmitter(this);
taskTracker = new TaskTracker(this);
其中notificationReceiver、connectivityReceiver、phoneStateListener主要负责系统断开连接或断网重新连接,executorService 是jdk提供的单例的线程池,负责线程的执行。
TaskSubmitter是自定义的成员类,用来通过executorService 来管理系统线程的执行,submit方法用来执行线程。
TaskTracker主要是记录线程数。
public Future submit(Runnable task) {
Future result = null;
if (!notificationService.getExecutorService().isTerminated()
&& !notificationService.getExecutorService().isShutdown()
&& task != null) {
result = notificationService.getExecutorService().submit(task); //执行线程
}
return result;
}
接下来执行NotificationService生命周期方法onCreate, 在onCreate方法中首先获取设备id,计在着选项中,然后初始化XmppManager, 通过taskSubmiiter开一个线程去执行自己的start方法。
xmppManager = new XmppManager(this);
taskSubmitter.submit(new Runnable() {
public void run() {
NotificationService.this.start();
}
});
再来看NotificationService.start()方法, 主要做了两件事:
1.注册广播接收者,这里通过代码方式注册,并在服务停止时注销掉,让广播接收者与服务的生命周期一致。
2.调用xmppManager .connect(),连接服务器。
private void start() {
Log.d(LOGTAG, "start()...");
registerNotificationReceiver();
registerConnectivityReceiver();
// Intent intent = getIntent();
// startService(intent);
xmppManager.connect();
}
最后来看XmppManager这个类,这是项目最核心的一个类,负责与服务器端的交互。首选是在构造方法中做一些初始化操作,之前放在首选项中的参数,就是在这里获取并使用的。
context = notificationService;
taskSubmitter = notificationService.getTaskSubmitter();
taskTracker = notificationService.getTaskTracker();
sharedPrefs = notificationService.getSharedPreferences();
xmppHost = sharedPrefs.getString(Constants.XMPP_HOST, "localhost");
xmppPort = sharedPrefs.getInt(Constants.XMPP_PORT, 5222);
username = sharedPrefs.getString(Constants.XMPP_USERNAME, "");
password = sharedPrefs.getString(Constants.XMPP_PASSWORD, "");
connectionListener = new PersistentConnectionListener(this);
notificationPacketListener = new NotificationPacketListener(this);
handler = new Handler();
taskList = new ArrayList<Runnable>();
reconnection = new ReconnectionThread(this);
再看connect方法。调用了submitLoginTask
public void connect() {
Log.d(LOGTAG, “connect()…”);
submitLoginTask();
} ,往下看,可以看到一个调用链
submitLoginTask-> submitRegisterTask->submitConnectTask,然后没个方法里面又执行了addTask方法。
其实就是把在几个成员类Task,放到taskList中,然后再看这几个task在什么时候执行呢。
来看addTask方法。
private void addTask(Runnable runnable) {
Log.d(LOGTAG, "addTask(runnable)...");
taskTracker.increase();
synchronized (taskList) {
if (taskList.isEmpty() && !running) {
running = true;
futureTask = taskSubmitter.submit(runnable);
if (futureTask == null) {
taskTracker.decrease();
}
} else {
taskList.add(runnable);
}
}
Log.d(LOGTAG, "addTask(runnable)... done");
}
在addTask方法中有一个判断,当taskList为空时,就调用taskSubmitter.submit()方法来执行线程。所以在submitConnectTask方法中执行了ConnectTask这个线程。
接下来看ConnectTask
public void run() {
Log.i(LOGTAG, "ConnectTask.run()...");
if (!xmppManager.isConnected()) {
// Create the configuration for this new connection
ConnectionConfiguration connConfig = new ConnectionConfiguration(
xmppHost, xmppPort);
// connConfig.setSecurityMode(SecurityMode.disabled);
connConfig.setSecurityMode(SecurityMode.required);
connConfig.setSASLAuthenticationEnabled(false);
connConfig.setCompressionEnabled(false);
XMPPConnection connection = new XMPPConnection(connConfig);
xmppManager.setConnection(connection);
try {
// Connect to the server
connection.connect();
Log.i(LOGTAG, "XMPP connected successfully");
// packet provider
ProviderManager.getInstance().addIQProvider("notification",
"androidpn:iq:notification",
new NotificationIQProvider());
} catch (XMPPException e) {
Log.e(LOGTAG, "XMPP connection failed", e);
}
xmppManager.runTask();
} else {
Log.i(LOGTAG, "XMPP connected already");
xmppManager.runTask();
}
}
可以看到在方法中
其实就是调用smack的api去建立联接了。在run方法最后调用了runTask方法,原来taskList里的其它任务是在这里被执行的。
public void runTask() {
Log.d(LOGTAG, "runTask()...");
synchronized (taskList) {
running = false;
futureTask = null;
if (!taskList.isEmpty()) {
Runnable runnable = (Runnable) taskList.get(0);
taskList.remove(0);
running = true;
futureTask = taskSubmitter.submit(runnable);
if (futureTask == null) {
taskTracker.decrease();
}
}
}
taskTracker.decrease();
Log.d(LOGTAG, "runTask()...done");
}
runTask每次会执行list中的第一次元素也就是最选添加进去的,也就是RegisterTask,程序执行到RegisterTask,同样是调用smackapi去注册一个用户,这里每次就是用一个随机数去注册
public void run() {
Log.i(LOGTAG, "RegisterTask.run()...");
if (!xmppManager.isRegistered()) {
final String newUsername = newRandomUUID();
final String newPassword = newRandomUUID();
Registration registration = new Registration();
PacketFilter packetFilter = new AndFilter(new PacketIDFilter(
registration.getPacketID()), new PacketTypeFilter(
IQ.class));
PacketListener packetListener = new PacketListener() {
public void processPacket(Packet packet) {
Log.d("RegisterTask.PacketListener",
"processPacket().....");
Log.d("RegisterTask.PacketListener", "packet="
+ packet.toXML());
if (packet instanceof IQ) {
IQ response = (IQ) packet;
if (response.getType() == IQ.Type.ERROR) {
if (!response.getError().toString().contains(
"409")) {
Log.e(LOGTAG,
"Unknown error while registering XMPP account! "
+ response.getError()
.getCondition());
}
} else if (response.getType() == IQ.Type.RESULT) {
xmppManager.setUsername(newUsername);
xmppManager.setPassword(newPassword);
Log.d(LOGTAG, "username=" + newUsername);
Log.d(LOGTAG, "password=" + newPassword);
Editor editor = sharedPrefs.edit();
editor.putString(Constants.XMPP_USERNAME,
newUsername);
editor.putString(Constants.XMPP_PASSWORD,
newPassword);
editor.commit();
Log
.i(LOGTAG,
"Account registered successfully");
xmppManager.runTask();
}
}
}
};
connection.addPacketListener(packetListener, packetFilter);
registration.setType(IQ.Type.SET);
// registration.setTo(xmppHost);
// Map<String, String> attributes = new HashMap<String, String>();
// attributes.put("username", rUsername);
// attributes.put("password", rPassword);
// registration.setAttributes(attributes);
registration.addAttribute("username", newUsername);
registration.addAttribute("password", newPassword);
connection.sendPacket(registration);
} else {
Log.i(LOGTAG, "Account registered already");
xmppManager.runTask();
}
}
这里里有一个packetListener,可以监听到服务端的返回值,当注册成功后会用户名与密码会存在首选项中,下次再进程序就不会再次注册。
之后会执行到LoginTask,用随机的用户密码去登陆。
private class LoginTask implements Runnable {
final XmppManager xmppManager;
private LoginTask() {
this.xmppManager = XmppManager.this;
}
public void run() {
Log.i(LOGTAG, "LoginTask.run()...");
if (!xmppManager.isAuthenticated()) {
Log.d(LOGTAG, "username=" + username);
Log.d(LOGTAG, "password=" + password);
try {
xmppManager.getConnection().login(
xmppManager.getUsername(),
xmppManager.getPassword(), XMPP_RESOURCE_NAME);
Log.d(LOGTAG, "Loggedn in successfully");
// connection listener
if (xmppManager.getConnectionListener() != null) {
xmppManager.getConnection().addConnectionListener(
xmppManager.getConnectionListener());
}
// packet filter
PacketFilter packetFilter = new PacketTypeFilter(
NotificationIQ.class);
// packet listener
PacketListener packetListener = xmppManager
.getNotificationPacketListener();
connection.addPacketListener(packetListener, packetFilter);
xmppManager.runTask();
} catch (XMPPException e) {
Log.e(LOGTAG, "LoginTask.run()... xmpp error");
Log.e(LOGTAG, "Failed to login to xmpp server. Caused by: "
+ e.getMessage());
String INVALID_CREDENTIALS_ERROR_CODE = "401";
String errorMessage = e.getMessage();
if (errorMessage != null
&& errorMessage
.contains(INVALID_CREDENTIALS_ERROR_CODE)) {
xmppManager.reregisterAccount();
return;
}
xmppManager.startReconnectionThread();
} catch (Exception e) {
Log.e(LOGTAG, "LoginTask.run()... other error");
Log.e(LOGTAG, "Failed to login to xmpp server. Caused by: "
+ e.getMessage());
xmppManager.startReconnectionThread();
}
} else {
Log.i(LOGTAG, "Logged in already");
xmppManager.runTask();
}
}
}
那么程序是在哪里监听服务求的数据的呢,回过头看看ConnectTask,在执行connection.connect()后有一段代码
ProviderManager.getInstance().addIQProvider("notification",
"androidpn:iq:notification",
new NotificationIQProvider());
看看NotificationIQProvider发现在其实就是对服务端发送来xml内容的解析,,那么这段代码就可以理解成添加解析器。
接下来就要看 LoginTask中标红的一段代码,这里也是添加一个监听器,xmppManager.getNotificationPacketListener()
其实就是NotificationPacketListener。看看processPacket, 这个方法是监听器的回调方法。
@Override
public void processPacket(Packet packet) {
Log.d(LOGTAG, "NotificationPacketListener.processPacket()...");
Log.d(LOGTAG, "packet.toXML()=" + packet.toXML());
if (packet instanceof NotificationIQ) {
NotificationIQ notification = (NotificationIQ) packet;
if (notification.getChildElementXML().contains(
"androidpn:iq:notification")) {
String notificationId = notification.getId();
String notificationApiKey = notification.getApiKey();
String notificationTitle = notification.getTitle();
String notificationMessage = notification.getMessage();
// String notificationTicker = notification.getTicker();
String notificationUri = notification.getUri();
Intent intent = new Intent(Constants.ACTION_SHOW_NOTIFICATION);
intent.putExtra(Constants.NOTIFICATION_ID, notificationId);
intent.putExtra(Constants.NOTIFICATION_API_KEY,
notificationApiKey);
intent
.putExtra(Constants.NOTIFICATION_TITLE,
notificationTitle);
intent.putExtra(Constants.NOTIFICATION_MESSAGE,
notificationMessage);
intent.putExtra(Constants.NOTIFICATION_URI, notificationUri);
// intent.setData(Uri.parse((new StringBuilder(
// "notif://notification.androidpn.org/")).append(
// notificationApiKey).append("/").append(
// System.currentTimeMillis()).toString()));
xmppManager.getContext().sendBroadcast(intent);
}
}
}
方法很简单,就是把消息包转换成我们自定义的NotificationIQ, 解析工作在上边的NotificationIQProvider中完成,这里就是把数据封装到intent中,然后发一个广播,到NotificationReceiver中去处理,
然后NotificationReceiver.onReceive()方法中发出一个系统通知。
把整个流程分析之后,想要把这个功能移值到自己项目中,应该很简单了。