网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。
建立网络通信连接至少要一对端口号(socket)。socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口;HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。
Socket的英文原义是“孔”或“插座”。通常也称作”套接字”,用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。
理论上两台主机能互相ping通,则可以互相socket通信。
一台具有固定ip的android设备(作为Socket服务端),客户端(可以多个)需要给它发送一些命令来即时播放文本、图片、视频等信息。
一般来说,Android开发时像一般简单的需求可以通过http或者webservice进行通信,对即时性要求不高的通信也可以折中选择轮询服务的机制(但耗流量)。但是要实现上述需求所说的即时播放多媒体信息的话,显然用Socket来实现最佳。
服务端: Java中能接收其他通信实体连接请求的类是ServerSocket,ServerSocket对象用来监听来自客户端的Socket连接,如果没有连接,它将一直处于等待状态。ServerSocket类提供如下构造器:
客户端 通常使用Socket来连接指定服务器,Socket类提供如下构造器:
Socket通信需要服务端和客户端,刚开始分析时把Android端当客户端,发现这样行不通啊,Android设备是负责监听某个端口,有消息来时就作出响应(播放多媒体信息),这应该是服务端做的事情。所以让Android端作为服务端。
需要自定义协议来播放文本、图片、视频消息。
开始标志(1byte) 业务数据包 结束标志(1byte)
0x02 0x03
业务数据包定义:顺序定义(各域间用"|"分隔,第一个域为消息类型)
1)文本通知
2001|优先级|每次时长(秒)|播放次数(默认1)|字体大小|通知内容
2)图片通知
2071|优先级|每次时长(秒)|播放次数(默认1)|是否全屏|图片URL
5)视频通知(1为全屏播放)
2051|优先级|播放次数|是否全屏|视频URL
通知内容中不能包含分隔符|,字体大小默认60(大于0才有效)
播放次数:<=0则为默认1次,大于0才有效
优先级:同类通知中数字越大表示优先级越高,<=0表示放到队列末尾。
普通文本>图片>视频
返回客户端:
OK -- 成功
ERROR:MSG -- 失败 例如 ERROR:消息类型不正确
Android设备作为服务端,需要开一个端口监听。很容易想到用Service来实现,这里采用它的一个子类IntentService来实现(该类的销毁由系统管理,非常方便),为了可以多个客户端连接,需要启动多个线程分别与客户端建立连接。
import android.app.IntentService;
import android.content.Intent;
import android.support.annotation.Nullable;
import android.util.Log;
import com.jykj.departure.util.SocketHelper;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SocketService extends IntentService {
private ServerSocket server;
private static final int PORT = 54321;
private List mList = new ArrayList<>();
/**
* Creates an IntentService. Invoked by your subclass's constructor.
*
* @param name Used to name the worker thread, important only for debugging.
*/
public SocketService(String name) {
super(name);
}
public SocketService() {
super("Socket Service");
System.out.println("Create SocketService!");
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
ExecutorService es;
try {
server = new ServerSocket(PORT);
server.setReuseAddress(true);
es = Executors.newCachedThreadPool();
} catch (IOException e) {
e.printStackTrace();
return;
}
Log.e("WS", "begin client connected");
Socket client;
while (true) {
Log.e("WS", "线程连接数:" + mList.size());
try {
client = server.accept();
Log.e("WS", "client connected:" + client.getInetAddress() + ":" + client.getPort());
mList.add(client);
es.execute(new ServiceThread(client));
} catch (IOException e) {
e.printStackTrace();
break;
}
}
}
//每个客户端连接开启一个线程处理
class ServiceThread implements Runnable {
private Socket socket;
ServiceThread(Socket s) {
socket = s;
}
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
byte[] bytes = SocketHelper.readBytes(is);
Log.e("WS", "字节长度:" + bytes.length + "," + new String(bytes, SocketHelper.CHARSET));
String s = SocketHelper.checkBytes(bytes);
Log.e("WS", "校验结果:" + s);
if (SocketHelper.RETURN_OK.equals(s)) {
Intent i = new Intent(MainActivity.ACTION_SOCKET);
i.putExtra(MainActivity.EXTRA_INFO, SocketHelper.getContent(bytes));
sendBroadcast(i);//校验成功将 消息命令 通过广播发送给主界面MainActivity
}
mList.remove(socket);
PrintWriter writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(),SocketHelper.CHARSET), true);
writer.println(s);
writer.flush();
writer.close();
is.close();
socket.close();//记得关闭socket(关闭与客户端的连接)
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
import android.util.Log;
import com.jykj.departure.entity.Notice;
import com.jykj.departure.entity.Pictures;
import com.jykj.departure.entity.Videos;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
/**
* socket协议接口
*/
public class SocketHelper {
public static final String CHARSET = "UTF-8";
private static final byte BEGIN_BYTE=0x02;
private static final byte END_BYTE=0x03;
public static final String SPLITOR = "\\|";//分隔符 "|"
public static final String RETURN_OK = "OK";
private static final String RETURN_ERROR = "ERROR:";
public static final String TYPE_GENERAL="2001";//普通消息
public static final String TYPE_VIDEO_GENERAL = "2051";//视频普通
public static final String TYPE_PICTURE_GENERAL = "2071";//图片普通
//从输入流中读取所有字节
public static byte[] readBytes(InputStream is) throws IOException {
BufferedInputStream bis = new BufferedInputStream(is);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buff = new byte[128];
int len = -1;
boolean flag = true;
while (flag&&(len=bis.read(buff))!=-1){
for(int i=len-1;i>=0;i--){
if(buff[i]==END_BYTE){
flag = false;
len = i+1;
break;
}
}
baos.write(buff,0,len);
}
byte[] b = baos.toByteArray();
baos.close();
//bis.close();//此处如果把bis关闭的话(则会顺带关闭底层is),这样会将socket关闭,这样就不能写出数据了!!!
return b;
}
/**
* 校验 消息格式
* 目前只有两种消息格式 播放次数为0表示停止播放,为-1表示一直播放
* 优先级:数字越大表示优先级越高,<=0表示放到队列末尾,班次通知列表优先级最高
音,发车时间,车牌号,车型名称,经停,晚点
* @param bytes 字节流
* @return OK
*/
public static String checkBytes(byte[] bytes){
if(bytes==null||bytes.length<=0) return RETURN_ERROR+"未读取到数据";
byte b1= bytes[0];//起始标志
byte b2 = bytes[bytes.length-1];//结束标志
if(b1!=BEGIN_BYTE) return RETURN_ERROR+"协议起始标志不对";
if(b2!=END_BYTE) return RETURN_ERROR+"协议结束标志不对";
try {
String content = new String(Arrays.copyOfRange(bytes,1,bytes.length-1),CHARSET);
Log.e("WS","内容:"+content);
if(content.length()<=0) return RETURN_ERROR+"消息内容为空";
String[] ss = content.split(SPLITOR);
Log.e("WS","消息类型:"+ss[0]);
String t = ss[0];
if(TYPE_GENERAL.equals(t)){
if(ss.length<6) return RETURN_ERROR+"文本通知格式需要6个字段";
else return RETURN_OK;
}elseif(TYPE_VIDEO_GENERAL.equals(t)){
if(ss.length<5) return RETURN_ERROR+"视频通知格式需要5个字段";
else return RETURN_OK;
}else if(TYPE_PICTURE_GENERAL.equals(t)){
if(ss.length<6) return RETURN_ERROR+"图片通知格式需要6个字段";
else return RETURN_OK;
} else
return RETURN_ERROR+"消息类型不正确"+t;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return RETURN_ERROR+"不支持的编码类型"+CHARSET;
}
}
//先校验,再调用此方法,获取有效内容(不包含开始结束的两个字节)
public static String getContent(byte[] bytes){
try {
return new String(Arrays.copyOfRange(bytes,1,bytes.length-1),CHARSET);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
}
}
public static Videos parseToVideo(String content){
String[] ss = content.split(SPLITOR);
Videos n = new Videos();
n.setType(ss[0]);
n.setOrder(ss[1].isEmpty()?0:Integer.parseInt(ss[1]));
n.setCount(ss[2].isEmpty()?0:Integer.parseInt(ss[2]));
n.setFull(ss[3].equals("1"));
n.setUrl(ss[4]);
return n;
}
public static Pictures parseToPic(String content){
String[] ss = content.split(SPLITOR);
Pictures n = new Pictures();
n.setType(ss[0]);
n.setOrder(ss[1].isEmpty()?0:Integer.parseInt(ss[1]));
n.setDuration(ss[2].isEmpty()?0:Integer.parseInt(ss[2]));
n.setCount(ss[3].isEmpty()?0:Integer.parseInt(ss[3]));
n.setFull(ss[4].equals("1"));
n.setUrl(ss[5]);
return n;
}
public static Notice parseToNotice(String content){
String[] ss = content.split(SPLITOR);
Notice n = new Notice();
n.setType(ss[0]);
n.setOrder(ss[1].isEmpty()?0:Integer.parseInt(ss[1]));
n.setDuration(ss[2].isEmpty()?0:Integer.parseInt(ss[2]));
n.setCount(ss[3].isEmpty()?0:Integer.parseInt(ss[3]));
n.setSize(ss[4].isEmpty()?0:Integer.parseInt(ss[4]));
n.setContent(ss[5]);
return n;
}
}
读取输入流中的字节需要注意的是在遇到结束标志时结束while循环,否则会一直阻塞,另外输入流bis不能关闭(关闭了会将socket关闭而无法写出数据了),因为还要在SocketService中写出数据,所以流的关闭都放在SocketService中。
public class Notice {
private int order;//优先级
private int duration;//时长,单位秒
private int count;//次数
private String content;//消息内容
private int size;//字体大小
private String type;//通知类型
...省略 get set方法
}
public class Pictures {
private int order;//优先级
private int count;//次数
private String url;//url
private int duration;//时长,单位秒
private boolean full;//是否全屏
private String type;//通知类型
...省略 get set方法
}
public class Videos {
private int order;//优先级
private int count;//次数
private String url;//url
private boolean full;//是否全屏
private String type;//通知类型 2061 长期,2051 短期,请查看SocketHelper定义的类型
...省略 get set方法
}
public class MainActivity extends ListActivity {
...省略很多不相关的代码
public static final String ACTION_SOCKET = "com.abcdefg.socket";
public static final String EXTRA_INFO = "EXTRA_INFO";
private TextView socketTV;
private ScrollView socketScollView;
private VideoView socketVideo;
private ImageView socketImageView;
private View socketRegularView, socketLayoutVideo;
private List picsList=new ArrayList<>();//图片通知
private List videosList=new ArrayList<>();//视频通知
private List noticeList=new ArrayList<>();//消息列表,当检票列表为空时显示消息列表,当二者都为空时,显示副表第一条记录
Handler handler = new Handler();
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(mReceiver);
}
@Override
protected void onResume() {
super.onResume();
registerReceiver(mReceiver, new IntentFilter(ACTION_SOCKET));
startService(new Intent(this, SocketService.class));
}
//播放图片
private void setPicture(String url, boolean needStore){
Uri uri = ApplicationHelper.checkFileExist(ApplicationHelper.getPicDir(this),ApplicationHelper.getNetworkFileName(url));
Log.e("WS","图片数:"+picsList.size()+",图片name:"+ApplicationHelper.getNetworkFileName(url)+",Uri:"+uri);
if(uri!=null){
socketImageView.setImageURI(uri);
return;
}
new DownloadPictureTask(new DownloadPictureTask.OnTaskFinishCallback() {
@Override
public void onTaskFinish(Bitmap result, String exceptionInfo) {
if(exceptionInfo==null){
if(result==null){
Toast.makeText(MainActivity.this,"获取图片异常!",Toast.LENGTH_LONG).show();
handler.removeCallbacks(playRnnable);
handler.postDelayed(playRnnable,100);
return;
}
socketImageView.setImageBitmap(result);
}else {
Toast.makeText(MainActivity.this,"获取图片异常:"+exceptionInfo,Toast.LENGTH_LONG).show();
}
}
},url,needStore,ApplicationHelper.getPicDir(this)).execute();
}
//播放视频
private void playVideo(final String url,boolean needStore){
Uri uri = ApplicationHelper.checkFileExist(ApplicationHelper.getVideoDir(this),ApplicationHelper.getNetworkFileName(url));
Log.e("WS","视频数:"+videosList.size()+",视频name:"+ApplicationHelper.getNetworkFileName(url)+",Uri:"+uri);
if(uri == null){
uri = Uri.parse( url );//网络视频
//下载它
if(needStore){
Log.e("WS","开始下载视频:"+uri);
new Thread(){
@Override
public void run() {
try {
HttpHelper.downloadVideo(url,ApplicationHelper.getVideoDir(MainActivity.this));
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();}
}
//接收广播的Socket消息
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (ACTION_SOCKET.equals(intent.getAction())) {
String content = intent.getStringExtra(EXTRA_INFO);
if (content == null || content.length() <= 0) return;
String[] ss = content.split(SocketHelper.SPLITOR);
if (SocketHelper.TYPE_GENERAL.equals(ss[0])) {//普通或长期
Notice n = SocketHelper.parseToNotice(content);
if(n.getOrder()<=0||noticeList.size()==0) noticeList.add(n);
else {
for(int i=0;iif(n.getOrder()>=noticeList.get(i).getOrder()){
noticeList.add(i,n);
break;
}
}
}
}else if(SocketHelper.TYPE_VIDEO_GENERAL.equals(ss[0])){//视频
//if(socketVideo.isPlaying()) socketVideo.suspend();
Videos v = SocketHelper.parseToVideo(content);
if(v.getOrder()<=0||videosList.size()==0) videosList.add(v);
else {
for(int i=0;iif(v.getOrder()>=videosList.get(i).getOrder()){
videosList.add(i,v);
break;
}
}
}
}else if(SocketHelper.TYPE_PICTURE_GENERAL.equals(ss[0])||SocketHelper.TYPE_PICTURE_LONGTERM.equals(ss[0])){//图片
//长期通知则存储
if(SocketHelper.TYPE_PICTURE_LONGTERM.equals(ss[0])){
Set set = spLongterm.getStringSet(SocketHelper.SP_KEY_LONGTERM_PICTURES,new HashSet());
if(!set.add(content)) return;//重复通知则不处理
spLongterm.edit().putStringSet(SocketHelper.SP_KEY_LONGTERM_PICTURES,set).apply();
}
Pictures v = SocketHelper.parseToPic(content);
if(v.getOrder()<=0||picsList.size()==0) picsList.add(v);
else {
for(int i=0;iif(v.getOrder()>=picsList.get(i).getOrder()){
picsList.add(i,v);
break;
}
}
}
}
//显示消息
handler.removeCallbacks(playRnnable);
handler.postDelayed(playRnnable,200);
}
}
};
//总播放任务,策略:普通文本=长期文本>图片临时=图片长期>视频临时=视频长期
Runnable playRnnable = new Runnable() {
@Override
public void run() {
boolean tb =noticeList.size()>0);//notice
boolean pb = noticeList.size()==0&&picsList.size()>0;//picture
boolean vb = noticeList.size()==0&&picsList.size()==0&&videosList.size()>0;//video
socketLayoutVideo.setVisibility(vb?View.VISIBLE:View.GONE);
if(!vb) socketVideo.suspend();
socketImageView.setVisibility(pb?View.VISIBLE:View.GONE);
socketTV.setVisibility(tb?View.VISIBLE:View.GONE);
socketScollView.setVisibility(!vb&&!tb&&!pb?View.VISIBLE:View.GONE);
if(noticeList.size()>0){
Notice n = noticeList.get(0);
socketTV.setTextSize(n.getSize()>0?n.getSize():NOTICE_TEXT_SIZE);
socketTV.setText(Html.fromHtml(n.getContent()));
socketTV.scrollTo(0,0);
noticeList.remove(0);
if(n.getType().equals(SocketHelper.TYPE_LONGTERM)){
noticeList.add(n);//长期通知保证不清除
}else {
if(n.getCount()>1){
n.setCount(n.getCount()-1);
n.setOrder(0);
noticeList.add(n);
}
}
handler.postDelayed(this,n.getDuration()>0?n.getDuration()*1000:20);
}else if(picsList.size()>0){
Pictures v = picsList.get(0);
socketRegularView.setVisibility(v.isFull()?View.GONE:View.VISIBLE);
String url = v.getUrl();
boolean needStore = v.getType().equals(SocketHelper.TYPE_PICTURE_LONGTERM);
setPicture(url,needStore);
picsList.remove(0);
if(v.getType().equals(SocketHelper.TYPE_PICTURE_LONGTERM)){
picsList.add(v);//长期通知保证不清除
}else {
if(v.getCount()>1){
v.setCount(v.getCount()-1);
v.setOrder(0);
picsList.add(v);
}
}
handler.postDelayed(this,v.getDuration()>0?v.getDuration()*1000:5);
}else if(videosList.size()>0){
Videos v = videosList.get(0);
socketRegularView.setVisibility(v.isFull()?View.GONE:View.VISIBLE);
playVideo(v.getUrl(),v.getType().equals(SocketHelper.TYPE_VIDEO_LONGTERM));
videosList.remove(0);
if(v.getType().equals(SocketHelper.TYPE_VIDEO_LONGTERM)){
videosList.add(v);//长期通知保证不清除
}else {
if(v.getCount()>1){
v.setCount(v.getCount()-1);
v.setOrder(0);
videosList.add(v);
}
}
}else {
setCurrentInfo(mList.size() > 0 ? mList.get(0) : null);
handler.postDelayed(this, 60);//如果只有一项,换页频率可以调成请求频率
}
}
};
}
上面代码比较多,但理清思路后并不复杂,原代码中有9种消息分类代码量更多,上面只是抽取了一部分。比如紧急通知优先级最高,还有一些长期通知类型的文本图片视频需要存储在设备上,每次启动应用后可以自动加载播放。
package com.jykj.departure.util;
import android.app.DownloadManager;
import android.app.DownloadManager.Request;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Environment;
import android.widget.Toast;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Map;
import java.util.Map.Entry;
/**
* 通用的与Http相关的辅助类
*/
public class HttpHelper {
private static String JSESSIONID; //定义一个静态的字段,保存sessionID 例如JSESSIONID=AD5F5C9EEB16C71EC3725DBF209F6178
/**
* 利用android 下载管理器 下载文件
*
* @param context 上下文
* @param fileName 文件名
* @param uri
* http或https
* @return 如果已经下载过返回-1,否则返回Download ID
*/
public static long download(Context context, String fileName, Uri uri) {
Toast.makeText(context, "已经转入后台下载!", Toast.LENGTH_SHORT).show();
DownloadManager manager = (DownloadManager) context
.getSystemService(Context.DOWNLOAD_SERVICE); // 初始化下载管理器
Request request = new Request(uri);// 创建请求
//request.setAllowedNetworkTypes(Request.NETWORK_MOBILE | Request.NETWORK_WIFI);// 设置允许使用的网络类型,这里是移动网络和wifi都可以
request.setNotificationVisibility(Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setAllowedOverRoaming(false);// 漫游
/*request.setDestinationInExternalPublicDir(
Environment.DIRECTORY_DOWNLOADS, fileName);*/
//判断是否有SD卡,如果有设置路径,没有则使用默认路径
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))
request.setDestinationInExternalFilesDir(context,
Environment.DIRECTORY_DOWNLOADS, fileName);
/* File file = new File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),fileName);
request.setDestinationUri(Uri.fromFile(file));*/
return manager.enqueue(request);// 将下载请求放入队列
}
/**
* http POST 请求
*
* @param urlString
* http请求
* @param content
* 请求正文,正文内容其实跟get的URL中'?'后的参数字符串一致
* @return str
* @throws IOException IOException
*/
public static String requestPost(String urlString, String content)
throws IOException {
System.out.println("session:"+JSESSIONID+",URL:"+urlString+"?"+content);
URL url = new URL(urlString);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(6 * 1000);
// Read from the connection. Default is true.
conn.setDoInput(true);
// Output to the connection. Default is
// false, set to true because post
// method must write something to the
// connection
// 设置是否向connection输出,因为这个是post请求,参数要放在
// http正文内,因此需要设为true
conn.setDoOutput(true);
// Post cannot use caches
conn.setUseCaches(false);
// Set the post method. Default is GET
conn.setRequestMethod("POST");
// This method takes effects to every instances of this class. URLConnection.setFollowRedirects是static函数,作用于所有的URLConnection对象。
// connection.setFollowRedirects(true);
// This methods only takes effacts to this instance.
conn.setInstanceFollowRedirects(true);
// Set the content type to urlencoded,because we will write some URL-encoded content to the
// connection. Settings above must be set before connect!
// 配置本次连接的Content-type,配置为application/x-www-form-urlencoded的
// 意思是正文是urlencoded编码过的form参数,下面我们可以看到我们对正文内容使用URLEncoder.encode
// 进行编码
conn.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
if(null != JSESSIONID){
conn.setRequestProperty("Cookie",JSESSIONID);
}
// 连接,从postUrl.openConnection()至此的配置必须要在connect之前完成,
// 要注意的是connection.getOutputStream会隐含的进行connect。
conn.connect();
DataOutputStream out = new DataOutputStream(conn.getOutputStream());
// The URL-encoded contend DataOutputStream.writeBytes将字符串中的16位的unicode字符以8位的字符形式写道流里面
out.writeBytes(content);
out.flush();
out.close(); // flush and close
//System.out.println(conn.getResponseMessage());
BufferedReader reader = new BufferedReader(new InputStreamReader(
conn.getInputStream(), "utf-8"));// 设置编码,否则中文乱码
String line;
StringBuilder sb = new StringBuilder();
while ((line = reader.readLine()) != null) {
sb.append(line);
}
String cookieval = conn.getHeaderField("set-cookie");
System.out.println("set-cookie:"+cookieval);
if(cookieval != null) {
JSESSIONID = cookieval.substring(0, cookieval.indexOf(";"));
}
reader.close();
conn.disconnect();
return sb.toString();
}
/**
* http Post请求 ,参考 {@link #requestPost(String,String) requestPost(String,String)}
*
* @param urlString
* 请求url
* @param map
* 封装了正文内容的map
* @return str
* @throws IOException IOException
*
*/
public static String requestPost(String urlString, Map map)
throws IOException {
if(map==null) return requestPost(urlString);
// 正文,正文内容其实跟get的URL中'?'后的参数字符串一致
// String content =
// "[email protected]"
// + "&activatecode=" + URLEncoder.encode("中国聚龙", "utf-8");
String content = "";
for (Entry en : map.entrySet()) {
String key = en.getKey();
String value = URLEncoder.encode(en.getValue(), "utf-8");
if (!content.isEmpty()) {
content += "&";
}
content += key + "=" + value;
}
return requestPost(urlString, content);
}
/**
* http Post请求,不带content ,参考 {@link #requestPost(String,String) requestPost(String,String)}
* @param urlString 请求url
* @return str
* @throws IOException IOException
*/
public static String requestPost(String urlString)
throws IOException {
return requestPost(urlString, "");
}
/**
* http GET 请求
*
* @param url url
* @return str
* @throws IOException IOException
*/
public static String requestGet(String url) throws IOException {
URL getUrl = new URL(url);
// 根据拼凑的URL,打开连接,URL.openConnection函数会根据URL的类型,
// 返回不同的URLConnection子类的对象,这里URL是一个http,因此实际返回的是HttpURLConnection
HttpURLConnection connection = (HttpURLConnection) getUrl
.openConnection();
connection.setConnectTimeout(6 * 1000);
// 进行连接,但是实际上get request要在下一句的connection.getInputStream()函数中才会真正发到
// 服务器
if(null != JSESSIONID){
connection.setRequestProperty("Cookie", JSESSIONID);
}
connection.connect();
// 取得输入流,并使用Reader读取
BufferedReader reader = new BufferedReader(new InputStreamReader(
connection.getInputStream(), "utf-8"));// 设置编码,否则中文乱码
String line;
StringBuilder sb = new StringBuilder();
while ((line = reader.readLine()) != null) {
sb.append(line);
}
String cookieval = connection.getHeaderField("set-cookie");
System.out.println("set-cookie:"+cookieval);
if(cookieval != null) {
JSESSIONID = cookieval.substring(0, cookieval.indexOf(";"));
}
reader.close();
// 断开连接
connection.disconnect();
return sb.toString();
}
/**
* 读取图片
* @param url 网络图片url
* @param needStore 是否存储 File dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
* @return Bitmap
* @throws IOException IOException
*/
public static Bitmap getBitmap(String url,boolean needStore,File dir) throws IOException {
// 获得连接
HttpURLConnection conn = (HttpURLConnection) new URL(url)
.openConnection();
// 设置超时时间为6000毫秒,conn.setConnectionTiem(0);表示没有时间限制
conn.setConnectTimeout(6000);
// 连接设置获得数据流
conn.setDoInput(true);
// 不使用缓存
conn.setUseCaches(false);
// 这句可有可无,没有影响
conn.connect();
// 得到数据流
InputStream is = conn.getInputStream();
// 解析得到图片
Bitmap bitMap = BitmapFactory.decodeStream(is);
if(needStore){
if(!dir.exists()){
dir.mkdir();
}
OutputStream os = new FileOutputStream(new File(dir,ApplicationHelper.getNetworkFileName(url)));
bitMap.compress(Bitmap.CompressFormat.JPEG,90,os);
os.flush();
os.close();
}
is.close();
return bitMap;
}
/**
* 下载视频文件
* @param url url
* @param dir 下载目录
* @throws IOException io
*/
public static void downloadVideo(String url,File dir) throws IOException {
// 获得连接
HttpURLConnection conn = (HttpURLConnection) new URL(url)
.openConnection();
// 设置超时时间为6000毫秒,conn.setConnectionTiem(0);表示没有时间限制
conn.setConnectTimeout(6000);
// 连接设置获得数据流
conn.setDoInput(true);
// 不使用缓存
conn.setUseCaches(false);
// 这句可有可无,没有影响
conn.connect();
// 得到数据流
InputStream is = conn.getInputStream();
if(!dir.exists()){
dir.mkdir();
}
OutputStream os = new FileOutputStream(new File(dir,ApplicationHelper.getNetworkFileName(url)));
byte[] buff = new byte[4*1024];
int len = -1;
while ((len=is.read(buff))!=-1){
os.write(buff,0,len);
}
os.flush();
os.close();
is.close();
}
}
package com.jykj.departure.util;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri;
import android.os.Environment;
import android.util.Log;
import java.io.File;
import java.util.Calendar;
public class ApplicationHelper {
public static final CharSequence TOAST_NO_NET = "很遗憾,木有网络";
/**
*
* @param context 上下文
* @param packageName 包名
* @return bool
*/
public static boolean checkApkInstalled(Context context, String packageName) {
if (packageName == null || packageName.isEmpty())
return false;
try {
context.getPackageManager().getApplicationInfo(packageName,
PackageManager.GET_UNINSTALLED_PACKAGES);
return true;
} catch (NameNotFoundException e) {
return false;
}
}
/**
* 检查文件在默认下载目录是否已存在
*
* @param fileName 文件名,如 xxx.apk
* @return 如果已经存在,则返回该文件的Uri,否则返回null
*/
public static Uri checkFileExist(String fileName) {
File dir =Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
File file = new File(dir, fileName);
if (file.exists()) {
return Uri.fromFile(file);
}
return null;
}
/**
*
* @param dir 目录
* @param fileName 文件名称
* @return Uri
*/
public static Uri checkFileExist(File dir,String fileName) {
if(dir==null) return null;
File file = new File(dir, fileName);
if (file.exists()) {
return Uri.fromFile(file);
}
return null;
}
//本应用的视频存储目录
public static File getVideoDir(Context context){
return context.getExternalFilesDir(Environment.DIRECTORY_MOVIES);
}
//本应用的图片存储目录
public static File getPicDir(Context context){
return context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
}
//判断某个文件是否为apk文件
public static boolean isApkFile(String fileName){
fileName = fileName.toLowerCase();
int idx = fileName.lastIndexOf(".apk");
return idx >=0;
}
/**
* 获得AndroidManifest.xml中的versionCode
*
* @param context 上下文
* @return int
*/
public static int getLocalVersionCode(Context context) {
try {
PackageInfo pi = context.getPackageManager().getPackageInfo(
context.getPackageName(), 0);
return pi.versionCode;
} catch (NameNotFoundException e) {
e.printStackTrace();
}
return 0;
}
/**
* 获得AndroidManifest.xml中的versionName
*
* @param context 上下文
* @return str
*/
public static String getLocalVersionName(Context context) {
try {
PackageInfo pi = context.getPackageManager().getPackageInfo(
context.getPackageName(), 0);
return pi.versionName;
} catch (NameNotFoundException e) {
e.printStackTrace();
}
return null;
}
/**
* 安装apk文件
*
* @param context 上下文
* @param uri uri
*/
public static void installApk(Context context, Uri uri) {
Intent installIntent = new Intent(Intent.ACTION_VIEW);
installIntent.setDataAndType(uri,
"application/vnd.android.package-archive");
installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(installIntent);
}
/**
* 卸载apk程序
*
* @param context 上下文
* @param uri uri
*/
public static void uninstallApk(Context context, Uri uri, String packageName) {
Uri packageURI = Uri.parse("package:" + packageName);
Intent uninstallIntent = new Intent(Intent.ACTION_DELETE, packageURI);
context.startActivity(uninstallIntent);
}
//删除某文件夹下所有文件
public static void deleteFile(File file) {
if (file.isDirectory()) {
File[] files = file.listFiles();
Log.e("WS","删除的文件夹"+file.getAbsolutePath()+",数量:"+files.length);
for (File f :files){
deleteFile(f);
}
//file.delete();//如要保留文件夹,只删除文件,请注释这行
} else if (file.exists()) {
file.delete();
}
}
}
package com.jykj.departure.task;
import android.app.ProgressDialog;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.support.annotation.NonNull;
import com.jykj.departure.util.HttpHelper;
import java.io.File;
import java.io.IOException;
import java.util.Map;
public class DownloadPictureTask extends AsyncTask<Void, Void, Bitmap> {
private String mUrl;
private OnTaskFinishCallback mOnTaskFinishCallback;
private String exceptionInfo;
private boolean mStore;
private File mDir;
/**
*
* @param onTaskFinishCallback 成功执行任务的回调接口O
* @param url http url
* @param needStore 是否需要存储
*/
public DownloadPictureTask(OnTaskFinishCallback onTaskFinishCallback, @NonNull String url, boolean needStore, File dir) {
mOnTaskFinishCallback = onTaskFinishCallback;
mUrl = url;
mStore = needStore;
mDir = dir;
}
@Override
protected Bitmap doInBackground(Void... params) {
try {
return HttpHelper.getBitmap(mUrl,mStore,mDir);
} catch (IOException e) {
e.printStackTrace();
exceptionInfo = e.getMessage();
return null;
}
}
@Override
protected void onPostExecute(Bitmap result) {
mOnTaskFinishCallback.onTaskFinish(result,
exceptionInfo);
}
/**
* 任务执行完成的回调接口,用于将执行结果返回给调用者
*/
public interface OnTaskFinishCallback {
/**
* 任务执行完成的回调接口,用于将执行结果返回给调用者
* @param result if null 则表示任务执行出现异常
* @param exceptionInfo 出现异常时的异常信息,否则为null
*/
void onTaskFinish(Bitmap result, String exceptionInfo);
}
}
如果觉得上面的代码太过于繁琐,不妨先实现一种消息类型如文本的,后面再慢慢加。
开发过程中肯定是要边调试边开发的,这边省略了客户端的开发,可以从网络上直接下载一大堆的TCP调试工具。下面推荐使用的一款为SocketTestDlg:
SocketTestDlg官网下载:SocketTestDlg官网下载地址
针对本应用,采用16进制发送消息,然后需要一个将utf8转16进制的工具
Utf8ToHex下载:Utf8ToHex下载地址
或者Utf8ToHex下载地址二
下面是一些消息例子
1)普通通知:2001|3|5|2|0|简单的通知hello时间
02\x32\x30\x30\x31\x7C\x33\x7C\x35\x7C\x32\x7C\x30\x7C\xE7\xAE\x80\xE5\x8D\x95\xE7\x9A\x84\xE9\x80\x9A\xE7\x9F\xA5\x68\x65\x6C\x6C\x6F\xE6\x97\xB6\xE9\x97\xB403
2)图片临时通知:2071|1|10|2|1|http://p4.so.qhmsg.com/t01c8f12eb94284bb19.jpg
02\x32\x30\x37\x31\x7C\x31\x7C\x31\x30\x7C\x32\x7C\x31\x7C\x68\x74\x74\x70\x3A\x2F\x2F\x70\x34\x2E\x73\x6F\x2E\x71\x68\x6D\x73\x67\x2E\x63\x6F\x6D\x2F\x74\x30\x31\x63\x38\x66\x31\x32\x65\x62\x39\x34\x32\x38\x34\x62\x62\x31\x39\x2E\x6A\x70\x6703
3)视频临时通知:2051|1|2|0|http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4
02\x32\x30\x35\x31\x7C\x31\x7C\x32\x7C\x30\x7C\x68\x74\x74\x70\x3A\x2F\x2F\x63\x6C\x69\x70\x73\x2E\x76\x6F\x72\x77\x61\x65\x72\x74\x73\x2D\x67\x6D\x62\x68\x2E\x64\x65\x2F\x62\x69\x67\x5F\x62\x75\x63\x6B\x5F\x62\x75\x6E\x6E\x79\x2E\x6D\x70\x3403
《道德经》第二章:
天下皆知美之为美,恶已;皆知善,斯不善矣。有无之相生也,难易之相成也,长短之相刑也,高下之相盈也,音声之相和也,先后之相随,恒也。是以圣人居无为之事,行不言之教,万物作而弗始也,为而弗志也,成功而弗居也。夫唯弗居,是以弗去。
译文: 天下人都知道美之所以为美,那是由于有丑陋的存在。都知道善之所以为善,那是因为有恶的存在。所以有和无互相转化,难和易互相形成,长和短互相显现,高和下互相充实,音与声互相谐和,前和后互相接随——这是永恒的。因此圣人用无为的观点对待世事,用不言的方式施行教化:听任万物自然兴起而不为其创始,有所施为,但不加自己的倾向,功成业就而不自居。正由于不居功,就无所谓失去。