软件开发学习记录(Day5-Day8)

以下包含自己学习过程中大量的心路历程(很菜很菜,只是作为记录随时反省+督促自己的,见笑!)


Day5:想实现服务器数据传回软件并在页面上显示的功能的分析

1.1 layout文件的修改

这一步先把layout.xml布局文件改一改,比较简单

如果报错:This view is not constrained vertically: at runtime it will jump to the top unless you add a vertica
可以选择点击这个地方:请添加图片描述

然后开始补习socket通信的原理。学习的过程中看到不少以前大一下第一次学socket看过的博客,就很感慨,现在都大三了,快两年了,我也没搞懂,这次必拿下!

想要实现服务器与手机端软件的通讯首先要考虑:
①通讯方式
②怎么保证A想与B交流的时候,B知道是A发来的,A能够收到B的回复
③怎么保证实时性

以上三个问题的答案就是:
①依赖socket这种通讯方式
其实网上很多博客都是在讲socket的原理,怎么说呢,大篇幅的文字我看得非常痛苦,对于代码实现不用了解那么细,java里socket都有对应的封装好的包和函数,我们并不是要每一个细节都去复现,我们更重要的是走通这个流程和逻辑,知道什么时候该调用哪个socket函数
②设定一个标识,比如端口和IP
③socket提供了实时性的办法

而整个过程大佬说可以拆分为:(注意我们的目的是实现将服务器传回的数据在手机端上进行显示)

  1. 首先要app主动连接服务器
  2. 然后服务器收到请求后记住客户端IP
  3. 客户端要有个线程时刻监听来自服务器的消息
  4. 服务器有数据传回需要时将数据主动发送给APP(根据之前主动保存的客户端IP)

今天没学完好叭,倒是熟悉了一下layout文件的设置,但是不太理解socket对象怎么处理输入输出流相关的控制函数和对象的,不过java’代码真的好好看啊,呼,明天继续!

Day6:继续尝试

关于连接服务器(提供资源的一端)与客户端(请求资源)互联:也就是客户端通过程序,向服务器发起请求,服务器监听,监听到了:欸这里有个客户端想要资源啊,就把对应资源进行打包准备,然后传输到通道里,传回客户端。

所以整体流程:(假设我们编写的Android Studio就是)客户端想要访问获取服务器上temp.txt文本的内容

  • 按钮监听,每一次按下按钮就是客户端对服务器的一次访问请求

  • 服务器程序一直运行,检测到客户端请求

  • 服务器将准备好的文本信息sendMsg转为输出流放到通道里,然后把通道里的一起发送给客户端

 dataOutputStream.writeUTF(sendMsg);
 dataOutputStream.flush();
  • 客户端接收

socket通信:

1.1 服务端Server

1.1.1 建立一个服务器对象ServerSocket

涉及方法函数:

  • binder:为服务端绑定IP地址和端口号,并开始监听
  • accept:服务端接收客户端请求并返回一个Socket对象,此方法调用后服务端会一直阻塞,知道有请求到达
  • close:关闭建立的服务端对象ServerSocket

1.1.2 通过监听获取一个用于通信的socket对象

这一步用accept执行实现

1.1.3 对通信的内容进行处理

目的是将通信内容进行打包处理得到输入流/输出流的引用对象,通过这两个对象向 Client 端发送/接收数据,进而实现 Socket 通信。

1.1.4 关闭socket连接

通信不可能一直开着,用完就关闭

2.1 客户端Client

2.1.1 初始化socket对象

客户端会选择在新的线程中初始化一个 Socket 对象并设置服务器对应的 IP 和端口号,找到相应的服务端进程。

2.1.2 获取与 Server 端通信的引用

根据通信获取的 Socket 对象,进行封装,得到相应的输入、输出流对象。

2.1.3 对循环接受

(在新线程中) 接受Server服务 端发送过来的消息,并做相应的处理。

1.1.4 关闭socket连接

在合适的时机关闭与 Server 端的 Socket 连接

但是今天实际跑虚拟机的时候发现大问题,点击按钮之后分给虚拟机的内存空间一下子就满了,然后软件就崩溃了,闪退+没反应警告
请添加图片描述
分析一下应该是内存泄露了(后来找bug是因为我原来的代码里面乱用对象,很多时候让他们访问的是一个空对象,然后卡死了!)
请添加图片描述

我还不是很懂business这个文件夹实现的多线程对主线程的影响,我自己写的时候直接在XwActivity的活动程序里写类了,然后想调用构造线程对象的对象把每一次请求生成的socket放到线程池里。emmmm不知道哪里想的不对,明天再试试!冲!
请添加图片描述

Day7:成功啦啦!!!

先对昨天的想法进行更正,重新理思路:
昨天最大的错误就是线程乱用,正常逻辑中应该是每点击一次按钮,就是一条线程去请求跟服务器通信,所以这里完全可以在按钮的监听里对于每一次点击实现一个继承Thread的对象GetMessgae

//监听按钮行为
        btn.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View view) {
    
    
                text.setText("正在获取连接... "+"\n");
                new GetMessage(handler).start();
                text.setText("\n"+text.getText().toString() + "完成!");
            }
        });

而这么做的重要原因有关对安卓主线程的理解:

1. 安卓主线程

对于用户体验而言最直接的就是页面卡不卡,所以可以这么理解,安卓主线程在我们点击手机应用列表的图标后,就会启动,它会去负责页面的调度和绘制,如果我们把对一些消息的响应安排在主线程里,它页面可能没画完,但是按照代码逻辑,它这个时候却要去等待信号的传回,这是非常傻的,然后用户就会觉得这个app就是逊啦。
所以我们需要将一些具体实现的活动安排在后台去,让用户看不到实现细节,感觉上是前端的桌面应用都在正常运行的时候程序就已经传回信息了(而这一效果的实现也就是使用线程的目的,cpu会在后台分化非常小的时间片去执行各种线程,对于用户的体验就是看到设备能在短期之内完成各种事情),说这么多就是要强调——完成想要实现的方法时一定不能跟主线程发生冲突。

而这就涉及到如何调度处理分支线程的信息传回跟主线程的交互——重写handle

2. Android中Handler的使用

先看定义:Handler是Android中引入的一种让开发者参与处理线程中消息循环的机制。

我自己尝试了一下之后,对Handler的理解是:
Hanlder原本是抽象类,但他更像一个容器,当我需要处理一个线程的消息时,我就在对应的线程里申明一个handle类,相当于告诉线程——你要是有什么消息啊,文本啊,你就要记得往这个handle类,你就往里传,handle帮你保管。handle看久了好像憨der啊,但是它一点也不傻!比我聪明多了

主线程里:

//可以看到这里就把主线程的handle传给线程了
new GetMessage(handler).start();

你想要实现的线程里:

public class GetMessage extends Thread {
    
    
    xxxxx
    private Handler handler;

    //重写这个线程的构造方法,传来的参数handle就是主线程里的
    //这里就像是一个消息的接口,使得线程搞到信息之后有途径跟主线程交互了,handle就是敲门砖!
    public GetMessage(Handler handler) {
    
    
        this.handler = handler;
    }
    xxxxx
    //使用方法,实例化一个Messgae对象,把你的receiveMsg存进去
    Message message = Message.obtain();
    //message.what是对这个Messgae对象的一个标识,相当于记录一下这个消息是哪个线程存进handle的
    message.what = ConstConfig.XW_GETMSG_FILE;
    message.obj= receiveMsg;
    //封装好了,那就存进handle,
    handler.sendMessage(message);
    
    xxxxx
    

}

所以感觉Handler实际上也像一个消息队列。别的线程把消息都存进去,然后主线程需要显示啥的时候,就去这个里面找:

{
    
    
        //这个时候的handle里面就有运行了线程保存的数据了,都是Message对象,然后你想干什么,就对对应的对象做处理
        handler = new Handler() {
    
    
            @Override
            public void handleMessage(@NonNull Message msg) {
    
    
                String str = (String) msg.obj;
                //比如我就是把它们显示出来,text是我latout文件里定义的一个控件的id
                
                text.setText(text.getText().toString()+str);
            }
        };
    }

找到了,就拿出对应的Message对象,对其进行处理,处理完之后拿出下一个Message,继续进行处理,周而复始。

3. 服务端代码实例


import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

public class Server {
    
    
 
    //指定端口号
    private static final int PORT = 60099;
    //服务端对象的实例化server
    private ServerSocket server = null;
    //声名服务端发送的信息为sendMsg
    private String sendMsg;
 
    
    public static void main(String[] args) {
    
    
        //实例化一个类对象(这个类里写我们想要完成的动作)
        new Server();
    }
 
    public Server() {
    
    
        try {
    
    
            //1.1.1 根据端口号建立一个服务器对象ServerSocket
            server = new ServerSocket(PORT);
            System.out.println("服务器已启动...");
            //因为服务器暂时没有接受到客户端消息,就赋值null
            Socket acceptClient = null;
            while (true) {
    
    
                //循环检测监听
                acceptClient = server.accept();         
                //1.1.2 通过监听获取一个用于通信的socket对象(也就是说每接受到一个新Socket连接请求,就会新建一个Thread去处理与其之间的通信)
                FileReader fr;
    			try {
    
    
    				fr = new FileReader("temp.txt");
    				BufferedReader br=new BufferedReader(fr);
    	            String line="";
    	            sendMsg="";
    	            while ((line=br.readLine())!=null) {
    
    
    	                sendMsg = sendMsg + line +"\n";
    	            }
    	            br.close();
    	            fr.close();
    				
    			} catch (FileNotFoundException e) {
    
    
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			} catch (IOException e) {
    
    
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
                new Server.Service(acceptClient).start();
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
 
    class Service extends Thread {
    
    
        private Socket socket;
		public Service(Socket socket) {
    
      
			this.socket=socket;
        }

        @Override
        public void run() {
    
    
        	try {
    
    
				OutputStream outputStream = socket.getOutputStream();
				DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
	            dataOutputStream.writeUTF(sendMsg);
	            dataOutputStream.flush();
	            socket.close();
	            
	            
			} catch (IOException e) {
    
    
				// TODO Auto-generated catch block
				e.printStackTrace();
			} 
        }
    }
}

4. 客户端代码示例

重要线程实现代码:

public class GetMessage extends Thread {
    
    

    //指明当前类的 TAG 标识(标签)
    private static final String TAG = "TAG";
    //申明服务器端口和IP
    private static final String HOST = "222.27.87.73";
    private static final int PORT =  60099;
    private BufferedReader in;
    private Handler handler;
    //一个字符串对象
    private String receiveMsg;
    private static LinearLayout linearLayout;

    private  InputStream inputStream;




    public GetMessage(Handler handler) {
    
    
        this.handler = handler;
    }

    private void receiveMsg(String receiveMsg) {
    
    
        if (receiveMsg!= null) {
    
    
                    Message message = Message.obtain();
                    message.what = ConstConfig.XW_GETMSG_FILE;
                    message.obj= receiveMsg;
                    handler.sendMessage(message);
        }
    }

    @Override
    public void run() {
    
    
        //可以考虑在此处添加一个while循环,结合下面的catch语句,实现Socket对象获取失败后的超时重连,直到成功建立Socket连接
        try {
    
    
            //初始化 Socket 对象
            Socket socket = new Socket(HOST, PORT);
            socket.setSoTimeout(60000);
            //获取与 Server 端通信的引用
            inputStream = socket.getInputStream();
            DataInputStream dataInputStream = new DataInputStream(inputStream);

            String receiveMsg = dataInputStream.readUTF();
            receiveMsg(receiveMsg);
            socket.close();

            Message message1 = Message.obtain();
            message1.what = ConstConfig.XW_GETMSG_FILE;
            message1.obj = "完成通信"+"\n";
            handler.sendMessage(message1);



        } catch (Exception e) {
    
    
            //如果Socket对象获取失败,即连接建立失败,会走到这段逻辑
            Log.e(TAG, ("connectService:" + e.getMessage()));
        }
    }
}

5. 效果

我在服务器用temp.txt做实验,其文本内容如下:
请添加图片描述
请添加图片描述
可以看到手机完成了跟服务器的连接,并获得了服务器上temp.txt的文本内容并显示。真感慨啊,两年前就该会的东西直到今天被大佬手把手带着学才跑通,感触很多,书上一眼晃过去的定义原来是这么体现的。

6. 总结一下今天学到的

  1. handle的使用,安卓主线程
  2. 服务器端使用nohup命令的时候要多注意端口是否被占用
  3. java 8很稳定很香,java版本不要太高了!
  4. 服务器运行java程序的相关命令:
    javac ClassName.java(对.java进行编译,得生成字节码文件》class才能运行)
    nohup java ClassName &!(告诉linux这个进程一直运行,不要挂起它,要保护它)
    ps -ef | grep java(查看当前正在运行的Java程序,也就是查看端口的占用情况)
    kill -9 PID(杀死正在运行的程序 PID是上个指令中最左侧的数字)
    java Server > result.txt(可以把java程序中涉及到system.out.print的输出重定向到文件里,否则默认都混在nohup.out文件里,不太好区别)
    scp -P 22022 文件 [email protected](从本地终端上传文件到服务器,在本地终端!本地终端输命令嗷嗷嗷!!!)

最后借用大佬的思路图结尾,简明扼要!
在这里插入图片描述
总之,今天最大的感受就是,java是门好美丽的语言啊~

Day8:创建keystore签名文件

创建一个新的key可以参考博客:创建keystore签名文件
原来就是用这个区分app的,相当于我可以把as上的工程文件release生成apk文件安装在手机上测试,如果key保持一致,安装的时候就是更新覆盖,不一致就需要卸载原软件再安装。

猜你喜欢

转载自blog.csdn.net/KQwangxi/article/details/120832343