您的位置:首页 > 理论基础 > 计算机网络

详解android高仿微信后台接受消息,发送通知,监测网络连接

2016-06-23 18:16 1236 查看
公司让我研究websocket,利用这个时间写了个仿微信的demo。

服务器端功能比较简单:转发给所有的client (类似与群聊)

android端功能:service后台接受消息;broadcastreceived监测网络连接断:断网时给出提示,重连时重新执行service中的websocket方法;接受到消息时如果activity没有在最前端发送notification通知用户。

要点:service将消息传递给activity使用的是activity中的全局handler

activity通service是否在屏幕最前时使用的binder对象,activity的onResum方法绑定service,onpause方法解绑service,来通知service是否需要保存消息并且发送notification。每次绑定后都查询一下service是否存着消息没有显示出来。

service中动态注册一个广播接收器来监听网络连接的状态

先看服务器端代码:

<pre name="code" class="java">/**
* @author mace
* @email 631534395@qq.com
*/



public class ChatServer extends WebSocketServer {

public ChatServer(int port) throws UnknownHostException {
super(new InetSocketAddress(port));
}

public ChatServer(InetSocketAddress address) {
super(address);
}

@Override
public void onOpen(WebSocket conn, ClientHandshake handshake) {

sendToAll(conn.getRemoteSocketAddress().getAddress().getHostAddress()
+ " 进入房间 !");

System.out.println(conn.getRemoteSocketAddress().getAddress()
.getHostAddress()
+ " 进入房间 !");
}

@Override
public void onClose(WebSocket conn, int code, String reason, boolean remote) {

sendToAll(conn.getRemoteSocketAddress().getAddress().getHostAddress()
+ " 离开房间 !");

System.out.println(conn.getRemoteSocketAddress().getAddress()
.getHostAddress()
+ " 离开房间 !");
}

@Override
public void onMessage(WebSocket conn, String message) {

sendToAll("["
+ conn.getRemoteSocketAddress().getAddress().getHostAddress()
+ "]" + message);

System.out.println("["
+ conn.getRemoteSocketAddress().getAddress().getHostAddress()
+ "]" + message);
}

@Override
public void onError(WebSocket conn, Exception e) {
e.printStackTrace();
if (conn != null) {
System.out.println( String.valueOf(e));
conn.close();

}
}

// 发送给所有的聊天者
private void sendToAll(String text) {
Collection<WebSocket> conns = connections();
synchronized (conns) {
for (WebSocket client : conns) {
client.send(text);
}
}
}

// 测试
public static void main(String[] args) throws InterruptedException,
IOException {

int port = 8887;

ChatServer server = new ChatServer(port);
server.start();

System.out.println("房间已开启,等待客户端接入,端口号: " + server.getPort());

BufferedReader webSocketIn = new BufferedReader(new InputStreamReader(
System.in));

while (true) {
String stringIn = webSocketIn.readLine();
server.sendToAll(stringIn);
}
}
}


下面详解android端的代码

service:service在Application中start,从程序一启动就开始运行

</pre><pre code_snippet_id="1727119" snippet_file_name="blog_20160623_4_78510" name="code" class="java"><pre name="code" class="java">/**
* @author mace
* @email 631534395@qq.com
*/


public class MyService extends Service {
public static WebSocketClient client;
private ConnectivityManager connectivityManager;
private NetworkInfo info;
private ArrayList<String> msgQueen = new ArrayList<String>();
private boolean isSaveInSrevice = true;
private MyBinder mBinder = new MyBinder();
private boolean iscon = true;//用于在broadcast中判断是否是需要重新连接的
private NotificationManager manager;
private NotificationCompat.Builder notifyBuilder;
private Vibrator vibrator;

@Override
public void onCreate() {
super.onCreate();
manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
IntentFilter mFilter = new IntentFilter();
mFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
registerReceiver(mReceiver, mFilter);
}

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

String address = "ws://192.168.31.184:8887";
try {
WebSocketImpl.DEBUG = true;
System.setProperty("java.net.preferIPv6Addresses", "false");
System.setProperty("java.net.preferIPv4Stack", "true");
client = new WebSocketClient(new URI(address), new Draft_17()) {
@Override
public void onOpen(final ServerHandshake serverHandshakeData) {
iscon = true;
if (!isSaveInSrevice) {
Message msg = new Message();
msg.what = ChatClientActivity.ONOPEN;
ChatClientActivity.uihandler.sendMessage(msg);
} else {
Log.e("Mace", "没有打开activity是的Onopen+1");
}
}

@Override
public void onMessage(final String message) {
//接受到消息就发送震动
openVibrator();
//判断activity是否是在最前端,如果是就将消息直接传给activity,如果不是就保存在service中
//并且发送
if (!isSaveInSrevice) {
Message msg = new Message();
msg.what = ChatClientActivity.ONMESSEGE;
Bundle b = new Bundle();
b.putString("msg", message);
msg.setData(b);
ChatClientActivity.uihandler.sendMessage(msg);
} else {
msgQueen.add(message);
//发送通知
sendNotification();
Log.e("Mace", message);
}
}

@Override
public void onClose(final int code, final String reason, final boolean remote) {
iscon = false;
if (!isSaveInSrevice) {
Message msg = new Message();
msg.what = ChatClientActivity.ONCLOSE;
ChatClientActivity.uihandler.sendMessage(msg);
} else {
Log.e("Mace", "没有activity时的" + " onClose");
}

}

@Override
public void onError(final Exception e) {
iscon = false;
client.close();
Log.e("Mace", String.valueOf(e));
}
};
} catch (URISyntaxException e) {
e.printStackTrace();
}

4000
client.connect();
return super.onStartCommand(intent, flags, startId);

}

private void openVibrator() {
vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
long[] pattern = {100, 400, 100, 400};   // 停止 开启 停止 开启
vibrator.vibrate(pattern, -1);           //重复两次上面的pattern 如果只想震动一次,index设为-1
}

@Override
public boolean onUnbind(Intent intent) {
mBinder.setIsSaveInSer();
return super.onUnbind(intent);
}

@Override
public void onDestroy() {
super.onDestroy();
Log.e("Mace", "+++++++++++Srevice Destroy++++++++++");
}

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

/**
* 接受到网络重连的广播时重新执行startcommoned方法让client重连
*/
private BroadcastReceiver mReceiver = new BroadcastReceiver() {

@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
connectivityManager = (ConnectivityManager)
getSystemService(Context.CONNECTIVITY_SERVICE);
info = connectivityManager.getActiveNetworkInfo();
if (info != null && info.isAvailable() && iscon == false) {
//断网的时候client会被close (调用了onclose方法)
Intent serviceIntent = new Intent(context, MyService.class);
context.startService(serviceIntent);
//                    client.connect(); //不能用client直接connect,具体原因期待有人指出,可能是classnotfind?
Log.e("Mace", "StartService");
}
}
}
};

/**
* 实现1.act到屏幕最前方时将保存在service中的msg向UI发送
* 2.act不再屏幕最前方时通知service保存消息
*/
class MyBinder extends Binder {

public void sendToUI() {
Message msg = new Message();
msg.what = ChatClientActivity.ONMESSEGELIST;
Bundle b = new Bundle();
ArrayList l = new ArrayList();
l.addAll(msgQueen);
b.putParcelableArrayList("list", l);
msg.setData(b);
ChatClientActivity.uihandler.sendMessage(msg);
isSaveInSrevice = false;
msgQueen.clear();
manager.cancel(121);
}

public void setIsSaveInSer() {
isSaveInSrevice = true;
}
}

//发送notification
private void sendNotification() {
//点击的意图ACTION是跳转到Intent
Log.e("Mace", "sendNotification");
Intent resultIntent = new Intent(this, ChatClientActivity.class);
resultIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, resultIntent, PendingIntent.FLAG_UPDATE_CURRENT);

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.a);
notifyBuilder = new NotificationCompat.Builder(this)
/*设置large icon*/
.setLargeIcon(bitmap)
/*设置small icon*/
.setSmallIcon(R.mipmap.a)
/*设置title*/
.setContentTitle("您收到了" + String.valueOf(msgQueen.size()) + "条消息")
/*设置详细文本*/
.setContentText(msgQueen.get(msgQueen.size() - 1))
/*设置发出通知的时间为发出通知时的系统时间*/
.setWhen(System.currentTimeMillis())
/*设置发出通知时在status bar进行提醒*/
.setTicker("收到新消息")
/*setOngoing(boolean)设为true,notification将无法通过左右滑动的方式清除 * 可用于添加常驻通知,必须调用cancle方法来清除 */
.setOngoing(false)
/*设置点击后通知消失*/
.setAutoCancel(true)
/*设置通知数量的显示类似于QQ那种,用于同志的合并*/
//                .setNumber(3)
/*点击跳转到MainActivity*/
.setContentIntent(pendingIntent);

manager.notify(121, notifyBuilder.build());
}

}



activity中代码:

/**
* @author mace
* @email 631534395@qq.com
*/
public class ChatClientActivity extends AppCompatActivity implements OnClickListener {
public static int ONMESSEGE = 1;
public static int ONMESSEGELIST = 2;
public static int ONCLOSE = -1;
public static int ONOPEN = 0;
private ScrollView svChat;
private EditText etDetails;
private EditText etName;
private EditText etMessage;
private Button btnSend;
private TextView closetv;
private long exitTime = 0;
private MyService.MyBinder myBinder;

private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myBinder = (MyService.MyBinder) service;
myBinder.sendToUI();
Log.e("Mace", "onServiceConnected");
}

@Override
public void onServiceDisconnected(ComponentName name) {
}

};

public static MyHandler uihandler;

//activity回到最前端时连接service进行通信
@Override
protected void onResume() {
super.onResume();
Intent bindIntent = new Intent(this, MyService.class);
bindService(bindIntent, connection, BIND_AUTO_CREATE);
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

uihandler = new MyHandler();
setContentView(R.layout.activity_chat_client);
svChat = (ScrollView) findViewById(R.id.svChat);
etDetails = (EditText) findViewById(R.id.etDetails);
etName = (EditText) findViewById(R.id.etName);
etMessage = (EditText) findViewById(R.id.etMessage);
btnSend = (Button) findViewById(R.id.btnSend);
btnSend.setOnClickListener(this);
closetv = (TextView) findViewById(R.id.onClose);
}

@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btnSend:
try {
if (MyService.client != null) {
Log.e("Mace", "btnSend" + String.valueOf(Thread.currentThread().getId()));
MyService.client.send(etName.getText().toString().trim() + "说:" + etMessage.getText().toString().trim());
svChat.post(new Runnable() {
@Override
public void run() {
svChat.fullScroll(View.FOCUS_DOWN);
etMessage.setText("");
etMessage.requestFocus();
}
});
}
} catch (Exception e) {
e.printStackTrace();
}
break;
}
}

@Override
protected void onPause() {
super.onPause();

unbindService(connection);
}

public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
exit();
return false;
}
return super.onKeyDown(keyCode, event);
}

public void exit() {
if ((System.currentTimeMillis() - exitTime) > 2000) {
Toast.makeText(getApplicationContext(), "再按一次退出程序",
Toast.LENGTH_SHORT).show();
exitTime = System.currentTimeMillis();
} else {
finish();
}
}

//接受service传来的内容,并相应的修改UI
class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == ONMESSEGE) {
addChatMessage(String.valueOf(msg.getData().get("msg")));
} else if (msg.what == ONCLOSE) {
setConClose();
} else if (msg.what == ONOPEN) {
setConOpen();
} else if (msg.what == ONMESSEGELIST) {
ArrayList msgList = msg.getData().getParcelableArrayList("list");
String s = "";
for (int i = 0; i < msgList.size(); i++) {
s = (String) msgList.get(i);
addChatMessage(s);
}
}
}
}

//添加message进入对话框
public void addChatMessage(String msg) {
etDetails.append(msg + "\n");
}

//连接关闭时的界面
public void setConClose() {
btnSend.setEnabled(false);
closetv.setVisibility(View.VISIBLE);
}

//连接可用时的界面
public void setConOpen() {
btnSend.setEnabled(true);
closetv.setVisibility(View.GONE);
}
}


manifest文件
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.wlf.websocket">

<!-- 网络权限 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.VIBRATE" />

<application
android:name=".MyApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<activity
android:name=".ChatClientActivity"
android:windowSoftInputMode="stateHidden|adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".MyService"
android:enabled="true"
android:persistent="true"></service>
</application>

</manifest>
欢迎各路大神指出不足,大家共同提高\(^o^)/

demo实例:
http://download.csdn.net/detail/mace_android/9558242

                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息