精华内容
下载资源
问答
  • 一个基于WebSocketProtocol协议(rfc6455)的安卓客户端Demo,实现了最基础的Connect/SendMessage/Disconnect三项功能;服务端的实现为(https://github.com/gorilla/websocket);客户端的实现为...
  • GB28181安卓客户端.zip

    2021-01-21 16:33:57
    两款GB28181安卓客户端DEMO, 可以方便测试连接SIP服务器
  • 这个demo简述了服务器的建立与客户端通信的简单操作,与连接数据库的简单操作
  • socket小demo(安卓客户端+java服务器端)
  • 安卓客户端集成微信支付的demo
  • 代码经过测试,可以成功运行。安卓可以发送POST请求到服务器,服务器可以作出响应,并返回数据到安卓客户端。需要的同学请自行下载,请亲打赏一些资源分。
  • ActiveMQ MQTT Android客户端Demo.zip,太多无法一一验证是否可用,程序如果跑不起来需要自调,部分代码功能进行参考学习。
  • 安卓客户端http通信demo,简单的小demo,实现数据传输
  • UDP安卓客户端udp基础知识AndroidStudio完成整个demo第一步:新建一个工程 udp基础知识 在前面的,这里不赘述,这里再对UDP安卓客户端和服务器端做一下说明,UDP本身是不区分服务器端和客户端的,只不过为了方便理解...

    udp基础知识

    在前面的,这里不赘述,这里再对UDP安卓客户端和服务器端做一下说明,UDP本身是不区分服务器端和客户端的,只不过为了方便理解才将它这样区分,其实更好的应该直接说成发送端(客户端)和接收端(服务器端),两台设备在进行通信时,如果通信是双向的,那么每个设备都同时是发送端和接收端。
    当一端作为发送端时,它需要明确知道接收端的IP地址和port号,也就是说在做udp客户端时要说明将要发送信息的目的地址的IP地址和端口号。下面直接来说明整个工程是如何一步一步完成的吧。

    AndroidStudio完成整个demo

    新建一个工程

    打开androidstudio创建一个新的工程

    页面布局代码

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1">
    
            <EditText
                android:id="@+id/ip"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="3"
                android:hint="ip"/>
    
            <EditText
                android:id="@+id/port"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="2"
                android:hint="port"/>
        </LinearLayout>
    
        <EditText
            android:id="@+id/sendtext"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="8"
            android:gravity="left"
            android:text="发送区"/>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:orientation="horizontal">
    
            <Button
                android:id="@+id/send"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:text="发送"/>
    
            <Button
                android:id="@+id/cleantext"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:text="清空发送区" />
    
            <Button
                android:id="@+id/hex"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:text="16进制格式"/>
        </LinearLayout>
    
    </LinearLayout>
    
    

    主活动代码

    package com.example.hh.udpserverdemo;
    
    import android.os.Handler;
    import android.os.Message;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.util.Log;
    import android.view.View;
    import android.widget.Button;
    import android.widget.EditText;
    
    import java.io.IOException;
    import java.lang.ref.WeakReference;
    import java.net.DatagramPacket;
    import java.net.DatagramSocket;
    import java.net.InetSocketAddress;
    import java.net.SocketException;
    
    public class MainActivity extends AppCompatActivity {
        /*UDP相关配置和所使用类对象创建*/
        private int port = 0; //服务器接收端口
        private InetSocketAddress inetSocketAddress = null;
        private DatagramPacket dpReceive = null;
        private DatagramSocket dsReceive = null;
        private byte[] msgReceive = new byte[1024]; //接收缓冲区大小
        private boolean hexString = false;
        private boolean udpServer = false;
    
        /*UI界面相关控件对象创建*/
        private EditText editport;
        private Button openserver,closeserver,hex,strshow,cleanrec;
        private EditText receive;
    
        /*其他使用类的创建*/
        private ButtonClick buttonClick = new ButtonClick(); //按键事件处理
        private MyHandle myHandle = new MyHandle(this);
    
    
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            editport = (EditText)findViewById(R.id.portserver); //开始时布局文件id=port,程序报错。更改
            openserver = (Button)findViewById(R.id.openServer);
            closeserver = (Button)findViewById(R.id.closeServer);
            hex = (Button)findViewById(R.id.tohex);
            strshow = (Button)findViewById(R.id.tostr);
            cleanrec = (Button)findViewById(R.id.cleantxt);
            receive = (EditText)findViewById(R.id.receive);
    
            openserver.setOnClickListener(buttonClick);
            closeserver.setOnClickListener(buttonClick);
            cleanrec.setOnClickListener(buttonClick);
            hex.setOnClickListener(buttonClick);
        }
    
    
    
        private class ButtonClick implements Button.OnClickListener{
    
            @Override
            public void onClick(View view) {
                switch (view.getId()){
                    case R.id.openServer:
                        udpServer = true;
                        port = Integer.parseInt(editport.getText().toString());
                        Thread thread = new Thread(new Runnable() {
                            @Override
                            public void run() {
                                inetSocketAddress = new InetSocketAddress(port);
                                while (udpServer){
                                    try {
                                        dsReceive = new DatagramSocket(inetSocketAddress);
                                        Log.i("服务器", "开启服务器:");
                                    }catch (SocketException e){
                                        e.printStackTrace();
                                    }
    
                                    dpReceive = new DatagramPacket(msgReceive,msgReceive.length);
                                    try {
                                        dsReceive.receive(dpReceive);
                                        Log.i("服务器", "接收到数据包长:"+dpReceive.getLength());
                                        if (hexString){
                                            String rec = byte2hex(dpReceive.getData(),dpReceive.getLength());
                                            Message message = new Message();
                                            message.what = 1;
                                            message.obj = rec;
                                            myHandle.sendMessage(message);
                                        }else {
                                            String rec = new String(dpReceive.getData(),dpReceive.getOffset(),
                                                    dpReceive.getLength());
                                            Message message = new Message();
                                            message.what = 1;
                                            message.obj = rec;
                                            myHandle.sendMessage(message);
                                        }
                                    } catch (IOException e) {
                                        e.printStackTrace();
                                    }
                                }
                                dsReceive.close();
                            }
                        });
                        thread.start();
                        break;
    
                    case R.id.closeServer:
                        udpServer = false;
                        break;
    
                    case R.id.cleantxt:
                        receive.setText("");
                        break;
    
                    case R.id.tohex:
                        hexString = true;
                        break;
    
                    case R.id.tostr:
                        hexString = false;
                        break;
                }
            }
        }
    
        private class MyHandle extends Handler{
            private final WeakReference<MainActivity> mActivity;
            public MyHandle(MainActivity activity){
                mActivity = new WeakReference<MainActivity>(activity);
            }
    
            @Override
            public void handleMessage(Message msg) {
                MainActivity activity = mActivity.get();
                if (activity != null){
                    switch (msg.what){
                        case 1:
                            String str  = msg.obj.toString();
                            receive.append(str);
                            Log.i("UI", "接收到信息并显示(字符形式)");
                            break;
                    }
                }
            }
        }
    
        /**
         * 字节数组转换为十六进制字符串
         *
         * @param b
         *            byte[] 需要转换的字节数组
         * @return String 十六进制字符串
         */
        public static  String byte2hex(byte b[],int length) {
            if (b == null) {
                throw new IllegalArgumentException(
                        "Argument b ( byte array ) is null! ");
            }
            String hs = "";
            String stmp = "";
            for (int n = 0; n < length; n++) {
                stmp = Integer.toHexString(b[n] & 0xff);
                if (stmp.length() == 1) {
                    hs = hs + "0" + stmp;
                } else {
                    hs = hs + stmp;
                }
            }
            return hs.toUpperCase();
        }
    }
    
    

    权限声明

    <uses-permission android:name="android.permission.INTERNET"/>
    

    运行效果

    PC端接收:
    在这里插入图片描述
    安卓端发送:
    在这里插入图片描述

    相关链接

    本文工程demo下载,提取码:8lpd
    安卓接收端demo

    展开全文
  • //////////////////////////2016/01/31/////////////////////// ////////////////////////by XBW//////////////////////////// //////////////////////环境 eclipse///////...终于搞到安卓通信了,用了socket套接字进

    //2016/01/31///

    by XBW

    //环境 eclipse///

    终于搞到安卓通信了,用了socket套接字进行了一下小测试,把安卓客户端跟java服务器端代码共享了;

    安卓4.0以后socket通信时,socket的建立连接等不能直接在主进程中,需要新开进程。于是又学习了一下线程,不过简单的socket连接测试线程用的还不是很明显,因为我没写UI,UI也不用更新;

    先看一下安卓客户端的代码;

    布局没写,直接用了新建的activity_main.xml,

    config.java为配置项,

    package Client_Config;
    
    public class config {
    
    	public static String ip="139.129.40.202";
    	public static int port=3333;
    }
    

    MainActivity.java中只开了线程

    package com.example.socket;
    
    import public_class.ClientThread;
    import android.app.Activity;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;
    import android.util.Log;
    import android.view.Menu;
    import android.view.MenuItem;
    import android.view.View;
    import android.widget.Button;
    
    public class MainActivity extends Activity {
    	
    	private ClientThread clientThread;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            getMenuInflater().inflate(R.menu.main, menu);
            return true;
        }
    
        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            int id = item.getItemId();
            if (id == R.id.action_settings) {
            	clientThread=new ClientThread();
            	clientThread.start();
                return true;
            }
            return super.onOptionsItemSelected(item);
        }
    }
    

    ClientThread.java为处理线程

    package public_class;
    
    import java.io.BufferedWriter;
    import java.io.IOException;
    import java.io.OutputStreamWriter;
    import java.net.InetSocketAddress;
    import java.net.Socket;
    import java.net.SocketAddress;
    
    import com.example.socket.MainActivity;
    
    import Client_Config.config;
    import android.os.Handler;
    import android.os.Looper;
    import android.os.Message;
    import android.util.Log;
    
    public class ClientThread extends Thread{
    
    	private SocketAddress socketAddress;
    	private Socket socket;
    	void connect()
    	{
    		socketAddress=new InetSocketAddress(config.ip, config.port);
    		socket=new Socket();
    		try
    		{
    			socket.connect(socketAddress,5000);
    			Log.w("反馈:","连接成功");
    		}catch(IOException e)
    		{
    			Log.w("反馈:","连接超时");
    		}
    	}
    	public void run()
    	{
    		connect();
    	}
    }
    


    最后需要配置一下AndroidManifest.xml的权限;

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.socket"
        android:versionCode="1"
        android:versionName="1.0" >
    
        <uses-sdk
            android:minSdkVersion="8"
            android:targetSdkVersion="21" />
    	<uses-permission android:name="android.permission.INTERNET"></uses-permission>
    	<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"></uses-permission>
        <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>
    


    下面是java服务器端代码

    Client.java

    package server;
    
    import java.io.BufferedReader;
    import java.io.BufferedWriter;
    import java.io.DataInputStream;
    import java.io.DataOutputStream;
    import java.io.EOFException;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.OutputStreamWriter;
    import java.io.PrintWriter;
    import java.net.Socket;
    import java.net.SocketException;
    import java.text.DateFormat;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.Iterator;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    
    public class Client implements Runnable {
    
        private Socket s = null;
        private InputStream ips =null;
        private boolean bConnected = false;
        private String sendmsg=null;
        private String getnameString=null;
        public List<Map<Object, Object>> getList;
        public Client (Socket s) {
           this.s = s;
           try {
             ips = s.getInputStream();
             bConnected = true;
           } catch(IOException e) {
                 e.printStackTrace();
              }
        }
        public void run() {
            try {
               while (bConnected) {
                   InputStreamReader ipsr = new InputStreamReader(ips);
                   BufferedReader br = new BufferedReader(ipsr);
                   String ss = "";
                   while((ss = br.readLine()) != null)
                   System.out.println(ss);
                }
            } catch (SocketException e) {
                System.out.println("client is closed!");
                Myserver.rizhi(" "+(config.RL++)+". "+"client is closed!");
                Myserver.clients.remove(this);
            } catch (EOFException e) {
                  System.out.println("client is closed!");
                  Myserver.rizhi(" "+(config.RL++)+". "+"client is closed!");
                  Myserver.clients.remove(this);
               }
               catch (IOException e) {
                  e.printStackTrace();
               }
              finally {
                try {
                  if (s != null) 
                  {
                	  s.close();
                	  ips.close();
                  }
                  else
                	  System.out.println("与服务器断开连接");
                } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
        }
     }

    config.java

    package server;
    
    public class config {
    	//日志记录
    		public static int RL=0;
    }
    


    Myserver.java

    package server;
    
    import java.awt.AWTException;
    import java.awt.Image;
    import java.awt.MenuItem;
    import java.awt.PopupMenu;
    import java.awt.SystemTray;
    import java.awt.Toolkit;
    import java.awt.TrayIcon;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.io.File;
    import java.io.IOException;
    import java.io.RandomAccessFile;
    import java.io.UnsupportedEncodingException;
    import java.net.BindException;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.net.URLDecoder;
    import java.util.ArrayList;
    import java.util.List;
    
    import javax.swing.JOptionPane;
    
    public class Myserver {
    	
    	public static boolean Server_judge=false;
    	public static boolean Server_started=false;
    	public static ServerSocket socket = null;
    	public static String path="";
    	public static List<Client> clients = new ArrayList<Client>();
    	public static void main(String [] args)
    	{
    		path = new Myserver().getClass().getProtectionDomain().getCodeSource().getLocation().getPath();
    		try {
    			path = URLDecoder.decode(path,"utf-8");
    		} catch (UnsupportedEncodingException e) {
    			// TODO 自动生成的 catch 块
    			e.printStackTrace();
    		}
    		if (path.endsWith(".jar"))
    		   path = path.substring(0, path.lastIndexOf("/") + 1);
    		while(!Server_judge)
    		{
    			String inputport = JOptionPane.showInputDialog("请输入该服务器使用的端口:3333");
        		int port = Integer.parseInt(inputport);
        		new Myserver().start(port);
        		if(Server_started)
        		{		
        			hide();
        			jiankong();
        		}
    		}
    	}
    	
    	public void start(int port) {
            try {
               socket = new ServerSocket(port);
               System.out.println("服务器启动成功");         
               JOptionPane.showMessageDialog(null, "服务器启动成功");
               Server_started = true;
               Server_judge=true;
            } catch (BindException e) {
                  System.out.println(" 端口已经被占用");
                  JOptionPane.showMessageDialog(null, "端口已经被占用");
               }
              catch (IOException e) {
                 e.printStackTrace();
              }      
       }
    	public static void hide()
        {
        	 if(SystemTray.isSupported()){//判断当前平台是否支持托盘功能  
        		 //创建托盘实例  
                 SystemTray tray = SystemTray.getSystemTray();  
                 //创建托盘图标:1.显示图标Image 2.停留提示text 3.弹出菜单popupMenu 4.创建托盘图标实例  
                //1.创建Image图像  
                 Image image = Toolkit.getDefaultToolkit().getImage(path+"//images//icon.png");  
                 //2.停留提示text  
                 String text = "聊天服务器";  
                 //3.弹出菜单popupMenu  
                 PopupMenu popMenu = new PopupMenu();  
                 MenuItem itmOpen = new MenuItem("打开日志");  
                 itmOpen.addActionListener(new ActionListener(){  
                     public void actionPerformed(ActionEvent e) {  
                    	 try
                    	 {   String basepath=path+"Server_Log.txt"; 
                    		 Runtime runtime= Runtime.getRuntime();
                    		 runtime.exec("notepad.exe "+basepath.substring(1));
                    	 }catch(IOException e1)
                    	 {
                    		 e1.printStackTrace();
                    		 JOptionPane.showMessageDialog(null, "日志不存在或路径错误,应在"+path+"文件中建立Server_Log.txt");
                    	 }              	
                     }                 
                 });  
                 MenuItem itmExit = new MenuItem("退出");  
                 itmExit.addActionListener(new ActionListener(){  
                     public void actionPerformed(ActionEvent e) {  
                    	 System.exit(0); 
                     }  
                 });  
                 popMenu.add(itmOpen);  
                 popMenu.add(itmExit);  
                 //创建托盘图标  
                 TrayIcon trayIcon = new TrayIcon(image,text,popMenu);  
                 //将托盘图标加到托盘上  
                 try {  
                     tray.add(trayIcon);  
                 } catch (AWTException e1) {  
                     e1.printStackTrace();  
                 }  
             }  	            
        }
    	public static void jiankong()
        {
        	try {
                while (Server_started) {
                    Socket s = socket.accept();
                    Client c = new Client (s);
                    System.out.println("一个用户连接进来");
                    rizhi(" "+(config.RL++)+". "+"一个用户连接进来");
                    new Thread(c).start();
                    clients.add(c);
                }
             } catch (IOException e) {
                   e.printStackTrace();
                }
                finally {
                   try {
                      socket.close();
                   } catch (IOException e) {
                         e.printStackTrace();
                      }
                }
        }
    	public static void rizhi(String s)
        {
        	String toAppend = s;
        	try {
        		// 打开一个随机访问文件流,按读写方式
        		RandomAccessFile randomFile = new RandomAccessFile(path+"//Server_Log.txt", "rw");
        		// 文件长度,字节数
        		long fileLength = randomFile.length();
        		// 将写文件指针移到文件尾。
        		randomFile.seek(fileLength);
        		String c=new String(toAppend.getBytes("GBK"),"ISO8859_1");
        		randomFile.writeBytes(c+"\r\n");
        		randomFile.close();
        		} catch (IOException e) {
        		e.printStackTrace();
        		}
        }
    }
    


                     源码下载


    展开全文
  • 激光推送客户端demo.zip,太多无法一一验证是否可用,程序如果跑不起来需要自调,部分代码功能进行参考学习。
  • 最近自己实现了基于安卓客户端百度云推送消息的DEMO,在这里给大家讲讲具体怎么实现。 首先大家要弄清楚什么是云推送? 百度云推送(Push)是一站式APP信息推送平台,为企业和开发者提供免费的消息推送服务,...

    最近自己实现了基于安卓客户端百度云推送消息的DEMO,在这里给大家讲讲具体怎么实现。

    首先大家要弄清楚什么是云推送?

    百度云推送(Push)是一站式APP信息推送平台,为企业和开发者提供免费的消息推送服务,开发者可以通过云推送向用户精准推送通知和自定义消息以提升用户留存率和活跃度。

    云推送适用于什么场景?

    根据用户的活跃情况、设备属性、地理位置等,通过云推送平台,主动、及时地向您的用户发起交互,向其推送

    聊天消息、日程提醒、活动预告、动态、新版本更新等。

    首先需要注册成为百度开发者用户,再填写一些相关信息就好了。注册成功后,进入百度云推送平台。右上角有登录按钮,登录成功后如下所示:


    接下来在上面的页面点击用户名下面的创建应用,进入如下所示页面:


    右上角有创建新应用,点击它后自己创建一个即可,很简单,这里就不教大家了,如果有问题直接评论或者私信我。

    创建成功后如上图所示,会有你的应用名称,APPID,应用平台,状态和操作显示在如上页面。点击右边的应用配置可以看到你的应用的api key和secret key等信息。如下所示:



    到这里之后,就成功了一大半了,你已经有自己的api key了。这个很重要,这个DEMO需要用到你的api key。

    之后去百度云推送平台上下载Android客户端SDK。下载后解压到你的电脑上,解压后的文件夹如下:


    接下里将Demo文件夹导入你的Eclipse中。Android Studio的话自己看docs文件夹里的文档,很详细,有说到怎么解决。导入你的Eclipse后将其设置为安卓项目,UTF-8也去配置一下。选中工程右键点击属性,里面可以将其设置和配置成功。实在不会的话百度或者问问身边的技术大神。如下所示:


    之后将你在百度云推送平台上创建的应用的api key在项目的清单配置文件里设置为你自己的api key就可以了。如下所示:


    我在MyPushMessageReceiver.java这个类里修改了onNotificationClicked()方法。修改的地方如下:


    这个方法功能是:当接收到通知后点击通知会在主界面上显示你的推送的消息的描述。具体演示图见下面的图示,这里不给出。其他的类我使用这个sdk的时候没有变,下面讲如何推送消息到手机上:

    首先在百度云推送平台上创建通知,如下:


    在上面写上你自己的消息的标题和消息描述,如下:


    这就已经创建消息通知成功了!接下来点击确定发送,如下:


    点击后你的手机就会接收到通过控制台发送过来的通知了。如下:


    手机成功接收后,百度云推送平台也会在推送列表显示你推送的消息:


    demo的在真机上运行后。MyPushMessageReceiver是Push消息处理receiver。首先调PushManager.startWork对push server发起绑定请求,这个过程是异步的,绑定请求的结果通过onbind返回。在更新显示界面(PushDemoActivity.java)会显示如下响应的结果:


    创建消息后发送,并且手机成功接收后界面更新显示(PushDemoActivity.java)如下:


    点击手机接收的消息通知后,界面更新显示(PushDemoActivity.java)如下:


    到这里就通过百度的控制台完成了消息的推送功能。

    当然如果要把sdk添加到自己的安卓项目里也很好办,教你们一个办法,先把百度的sdk文档和百度的demo弄懂、弄透,文档讲解的很详细,大家要好好的去看,去思考。这种大公司的sdk很不错,很多值得你学习的地方。如果还有不懂的评论或者私信我。

    每天进步一点点!



    展开全文
  • 这是一个类似搜狐安卓客户端的搜索界面文字滚动的效果demo.rar,太多无法一一验证是否可用,程序如果跑不起来需要自调,部分代码功能进行参考学习。
  • Material-WeCenter是一个第三方的WeCenter安卓客户端,最初为作者们为大学官方论坛开发。本客户端依赖由ifLab维护的WeCenterMobile-Api。 Screenshot Demo Releases Usage 使用方法 功能介绍 实现介绍 Support...
  • Android项目源码仿淘宝安卓客户端

    热门讨论 2016-05-20 21:42:09
    本项目是一个仿淘宝安卓客户端demo源码,主要实现了:商品的基本展示、宝贝详情,图片展示的放大缩小功能、界面之间切换的动画、购物车多项删除、弹窗的动画效果、首页广告的轮播效果、获得本机具有传感器的列表、...
  • 的二次开发,针对 Android 平台做了一层封装,目的为了简化客户端的使用,算是 WebSocket 与业务层的一个中间桥接层。 如果觉得还不错的话,欢迎关注我的个人公众号,我会不定期发一些干货文章~

    介绍

    WebSocket 3.0 版本经过这段时间的开发终于完成并且通过测试,相比较于 2.0 版本有了很大的改动,程序的健壮性与扩展性有了很大的提高。

    实际上,以前的版本很大程度上都是为了应付公司业务而做的,顺手开源出来,但我发现随着使用者越来越多,问题也逐渐凸显出来,再加上经过前段时间的学习技术上有了长足的进步,就想着把这个给重构一遍。

    其实做这个技术上并没有什么技术上的难点,但是要做开源,要给别人用,就会有很大的挑战。不仅要考虑到程序的健壮性,还要考虑如何用最简单的方式,给用户提供更多的功能,并且兼顾到可扩展性。

    我之前花了很长时间研究过设计模式相关的东西,也读了一些框架的源码,所以我的技术也一直更偏向于架构设计方向,这个框架中也用到了很多设计模式相关知识点。

    3.0 版本的开发时间也不过一个月左右,但实际上我在开发之前就花了很久考虑如何设计架构,因为核心实现方式变了,所以几乎一切都是从零开始。最终选定了现在的方案,也是现阶段我能想到的最佳方案。

    3.0 版本的改动

    最主要的变动是核心实现方式从 Service 变更为独立线程,解决了新版本 Android 系统启动 Service 的问题以及可以准确控制连接的启动与断开。

    所以因为核心方式变了也就没有 BaseWebSocketActivity 以及相关概念,所有对 WebSocket 相关的操作都是通过 WebSocketHandler 来实现的。

    现在 WebSocketHandler 是个很重要的概念,我们无论是 WebSocket 的初始化、创建连接、断开连接、数据收发等等都要使用它来实现,其中具体的方法列表点此查看文档

    如果您还在使用 2.0 版本,那么请点击这里查看 2.0 版本

    如何集成

    这一点与以前一样,也有两种使用方式。

    Gradle 方式集成

    在对应 model 的 build.gradle 中添加依赖:

    implementation 'com.github.0xZhangKe:WebSocketDemo:3.0.1'
    

    然后 sync 一下,如果出现类似的错误:

    Failed to resolve: com.github.0xZhangKe:WebSocketDemo:3.0.1
    

    那意味着你还没添加 Github 的仓库,到项目根目录中的 build.gradle 中添加如下代码:

    maven { url = 'https://jitpack.io' }
    

    第二种集成方式

    这个就很简单了,直接把 websocketlib 中的代码拷贝到自己的项目中就行,具体怎么做就看你的个人喜好。

    开始使用

    此时你已经把框架集成到项目中了,再经过简单的几步配置即可使用。

    基本配置

    首先,最基本的,我们要配置 WebSocket 连接地址,要说明的是,关于 WebSocket 的相关配置都在 WebSocketSetting 中。
    我们通过如下的代码设置连接地址:

    WebSocketSetting setting = new WebSocketSetting();
    //连接地址,必填,例如 wss://localhost:8080
    setting.setConnectUrl("your connect url");
    

    除了连接地址之外,WebSocketSetting 中还提供了很多相关配置,我挑几个重要的说一下。

    //设置连接超时时间
    setting.setConnectTimeout(10 * 1000);
    
    //设置心跳间隔时间
    setting.setConnectionLostTimeout(60);
    
    //设置断开后的重连次数,可以设置的很大,不会有什么性能上的影响
    setting.setReconnectFrequency(40);
    
    //设置 Headers
    setting.setHttpHeaders(header);
    
    //设置消息分发器,接收到数据后先进入该类中处理,处理完再发送到下游
    setting.setResponseProcessDispatcher(new AppResponseDispatcher());
    //接收到数据后是否放入子线程处理,只有设置了 ResponseProcessDispatcher 才有意义
    setting.setProcessDataOnBackground(true);
    
    //网络状态发生变化后是否重连,
    //需要调用 WebSocketHandler.registerNetworkChangedReceiver(context) 方法注册网络监听广播
    setting.setReconnectWithNetworkChanged(true);
    

    上面基本上包含了我们常用的一些配置了,详细介绍可查看文档,或者直接问我。

    初始化与连接

    设置好之后就可直接开始连接啦,上面说过连接使用 WebSocketHandler 来操作,具体如下:

    //通过 init 方法初始化默认的 WebSocketManager 对象
    WebSocketManager manager = WebSocketHandler.init(setting);
    //启动连接
    manager.start();
    

    我这里提供一个可以用来测试 WebSocket 的地址:

    wss://echo.websocket.org
    

    这是国外一个专门用来测试 WebSocket 的网站,同样也支持在线测试

    我们对 WebSocket 的连接管理、数据收发,本质上是使用 WebSocketManager 来实现。
    上面的 WebSocketHandler.init(setting) 方法也是为了获取一个默认的 WebSocketManager 对象。

    此时默认的 WebSocketManager 已经初始化并且正在连接了,一般来说都是启动 APP 同时建立 WebSocket 连接,所以建议上述配置及初始化代码放在 Application 中运行。

    后面我们需要使用 WebSocketManager 收发数据、管理连接时直接通过下面的代码即可获取到实例:

    //通过此方法获取默认的 WebSocketManager 对象
    WebSocketManager manager = WebSocketHandler.getDefault();
    

    数据收发

    当我们初始化完成后,即可使用默认的 WebSocketManager 来进行发送数据与接收数据。
    WebSocketManager 中提供了一系列的 send 方法用于发送数据:

    //发送 String 数据
    void send(String text);
    //发送 byte[] 数据
    void send(byte[] bytes);
    //发送 ByteBuffer 数据
    void send(ByteBuffer byteBuffer);
    

    除了上述三个常规的发送数据方法外,还提供了用于发送 ping/pong 的方法:

    //发送 ping
    void sendPing();
    //发送 pong
    void sendPong();
    //发送 pong
    void sendPong(PingFrame pingFrame);
    

    以及两个可自定义的帧数据发送方法:

    //发送 Framedata
    void sendFrame(Framedata framedata);
    //发送 Framedata 集合
    void sendFrame(Collection<Framedata> frameData);
    

    上面的几个发送数据的方法基本上囊括了所有应用场景,那么说完了发送数据再来说接收数据。

    数据的接收通过对 WebSocketManager 添加 SocketListener 监听器来实现。
    我们通过如下代码添加数据接收监听器:

    manager.addListener(socketListener);
    

    SocketListener 中的回调方法较多,为了节省篇幅我就挑两个重要的讲一下:

    // 数据发送失败
    void onSendDataError(ErrorResponse errorResponse);
    //接收到文本消息
    <T> void onMessage(String message, T data);
    //接收到二进制消息
    <T> void onMessage(ByteBuffer bytes, T data);
    

    第一个发送失败方法 onSendDataError 指的是 WebSocket 未连接或其他愿意导致数据未发送成功,ErrorResponse 中包含了失败的原因。

    onMessage(String, T) 方法显然是接收到 String 类型消息的回调,那泛型 T 是什么意思呢?T 是消息分发器中处理完成后返回的数据,具体后面会介绍。

    onMessage(ByteBuffer, T) 方法类似上面说的,只不过收到的是 ByteBuffer 类型的数据。

    另外还有一点,因为 SocketListener 接口中的方法比较多,大多数场景下我们是不需要使用这么多方法的,所以我又提供了一个 SimpleListener 抽象基类,里面实现了 SocketListener 中的所有方法,你可以按需使用。

    那么到这里关于数据的接收就说完啦。

    消息处理分发器

    消息分发器在这里是个很重要的概念,这是用来在接收到消息后进行预处理,然后再回调给各个接收点的中间件。
    这里放一张流程图帮助理解:

    消息处理分发器

    那么关于消息处理器应该如何使用呢,其实非常简单,我在上面配置信息那里也讲到了 IResponseDispatcher 接口。
    首先需要定义一个实现了该接口的类,然后创建一个该类的实例,在 WebSocket 配置时调用 setting.setResponseProcessDispatcher 方法将该实例设置进去即可。

    关于他的使用场景,具体而言,我们在接收到数据时应该对数据进行统一的处理判断,然后再将其发送到下游接收点,处理数据时会将数据转换为统一的数据结构,具体的结构根据公司业务有所不同。

    我们主要关注其中两个方法:

    // 接收到文本消息
    void onMessage(String message, ResponseDelivery delivery);
    //接收到二进制消息
    void onMessage(ByteBuffer byteBuffer, ResponseDelivery delivery);
    

    这里出现了一个陌生的概念:ResponseDelivery

    ResponseDelivery 是数据发射器,其继承上述的 SocketListener 接口,另外有提供了几个其他的方法,用它可以将数据发送到各个接收点,我们在处理完数据之后需要使用它把数据发送出去。
    与上面 IResponseDispatcher 中两个方法对应的,这里也有几个方法,同样我也只挑几个重点:

    //接收到文本消息
    <T> void onMessage(String message, T data);
    //接收到二进制消息
    <T> void onMessage(ByteBuffer bytes, T data);
    //数据发送失败
    void onSendDataError(ErrorResponse errorResponse);
    

    我们在 IResponseDispatcher 中处理完数据后,就通过上面的几个方法发送出去,这里我主要说一下 onMessage 方法中的泛型 T。

    我们在处理数据时可能会将数据先转成一个 JSON,或者转成一个实体类,然后判断 code 值等等,一切都 ok 再发送出去,如果发现需要重新登录可能就直接跳到登录页面去并且拦截掉该条消息。
    那么这里的泛型 T 对应的就是处理后的数据,我们可以把数据发送出去,后面就省得再做一次转换。
    关于这个的具体使用案例可以点击这里查看

    另外,消息处理器默认是不会开启的,接收到消息直接发送到各个接收点。

    对多个 WebSocket 连接的支持

    上面我经常提到一个概念就是默认的 WebSocket 连接,那除了默认的还有别的了吗?当然是有的了,考虑到要连接多个 WebSocket 的场景,3.0 版本特地对此做了支持。
    WebSocketHandler.init(WebSocketSetting) 方法用来初始化默认的连接,同时还有另一个初始化方法:

    /**
     * 通过唯一标识符新建一个 WebSocket 连接
     *
     * @param key     该 WebSocketManager 的唯一标识符,
     *                后面需要通过这个 key 来获取到对应的 WebSocketManager
     * @param setting 该连接的相关设置参数
     */
    WebSocketManager initGeneralWebSocket(String key, WebSocketSetting setting)
    

    除了需要一个 WebSocketSetting 之外还需要一个 String 类型的 key,对于除了默认 WebSocket 连接之外的连接,这里用 key 来标识,每一个 key 对应一个连接,内部使用一个散列表维护。
    当我们初始化完成后需要收发数据时通过如下代码获取 WebSocketManager 实例即可:

    /**
     * 获取 WebSocketManager 对象
     *
     * @param key 该 WebSocketManager 的 key
     * @return 可能为空,代表该 WebSocketManager 对象不存在或已移除
     */
    WebSocketManager getWebSocket(String key);
    

    获取到 WebSocketManager 之后其它操作就跟上面说的完全一样了。
    使用完记得及时关闭连接并移除。

    心跳机制

    WebSocket 通过发送 ping/pong 来确保连接的可用,客户端发送 ping 帧,服务端响应 pong 帧。
    框架中默认的心跳间隔是 60 秒一次,我们可以通过如下代码设置间隔时间:

    //设置心跳间隔时间
    setting.setConnectionLostTimeout(50);
    

    特别的,如果不需要这个心跳机制可以将参数设置为 0 关闭心跳。

    声明

    本框架是基于 Java-WebSocket 的二次开发,针对 Android 平台做了一层封装,目的为了简化客户端的使用,算是 WebSocket 与业务层的一个中间桥接层。


    如果觉得还不错的话,欢迎关注我的个人公众号,我会不定期发一些干货文章~

    公众号二维码

    展开全文

空空如也

空空如也

1 2 3 4 5 ... 13
收藏数 244
精华内容 97
关键字:

安卓客户端demo