项目实践总结---电子名牌

news/2025/2/7 23:08:23/

需求

Android端写一个界面,作为TCP服务端,接受客户端发来的图片以及一些信息,显示在界面上。再次打开APP的时候保证上一次图片存在。

思路

1 编写一个TCP服务端,继承runnable接口的方式去实现,然后写一个接口回调监听TCP接受的数据。

2 主界面监听TCP服务的接口,背景图是一个ImgView,加载使用Bitmap

3 保存图片以及本地数据:文字类的使用sp存储,图片保存在SDCard下,使用File类操作

主要涉及的知识点

1 TCP

2 线程的创建方法以及优缺点

3 Bitmap的使用

4 Android中操作SD卡

实现以及总结

一 TCP服务

package com.example.namebrand.network;import android.util.Log;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;/*** 2023/05/13*/
public class TCPServer implements Runnable {private static final String TAG = "TCPServer";private String chaSet = "UTF-8";private int port;private boolean isListen = true;public TCPServer(int port) {this.port = port;}byte[] imgBytes;@Overridepublic void run() {try {ServerSocket serverSocket = new ServerSocket(port);Log.d(TAG, "run:等待客户端连接... ");//serverSocket.setSoTimeout(2000);if (isListen) {Socket socket = serverSocket.accept();Log.d(TAG, "run: 客户端已连接");if (socket != null) {acceptData(socket);}}} catch (IOException e) {e.printStackTrace();}}private void  acceptData(Socket socket) {InputStream is;OutputStream os;int postX = 0;int postY = 0;int size = 0;int reserveOne = 0;int reserveTwo = 0;String reserve = "";int color = 0;long pngLen = 0L;int i = 0;byte[] bytesBuffer = null;int bufferPos = 0;int rcvLen = 0;try {is = socket.getInputStream();os = socket.getOutputStream();byte[] bytes = new byte[1024 * 4];while (!socket.isClosed() && !socket.isInputShutdown()) {while ((rcvLen = is.read(bytes)) != -1) {if (rcvLen > 0) {if (i == 0) {byte[] content = new byte[bytes.length];System.arraycopy(bytes, 0, content, 0, bytes.length);String res = new String(content, chaSet);String trim = res.trim(); //打印的时候去掉多余部分Log.d(TAG, "accept: len: " + rcvLen + " content:" + trim);postX = getInt(bytes, 0, 4);postY = getInt(bytes, 4, 4);size = getInt(bytes, 8, 4);reserveOne = getInt(bytes, 12, 4);reserveTwo = getInt(bytes, 16, 4);reserve = getString(bytes, 20, 56);color = getInt(bytes, 76, 4);pngLen = getInt(bytes, 80, 4);if (pngLen > 0) {bytesBuffer = new byte[1024 * 1024 * 5];}i++;}//再无图片数据if (pngLen <= 0) {if (onReceiveListener != null) {onReceiveListener.receive(postX, postY, size, reserveOne, reserveTwo, reserve, color, pngLen,imgBytes);}i = 0;} else {System.arraycopy(bytes, 0, bytesBuffer, bufferPos, rcvLen);bufferPos += rcvLen;Log.d(TAG, "accept: bufferPos:" + bufferPos + "pngLen:" + pngLen + "rvcLen:" + rcvLen);if (bufferPos >= pngLen + 84) {imgBytes = new byte[bufferPos - 84];System.arraycopy(bytesBuffer, 84, imgBytes, 0, bufferPos - 84);if (onReceiveListener != null) {onReceiveListener.receive(postX, postY, size, reserveOne, reserveTwo, reserve, color, pngLen,imgBytes);}bufferPos = 0;pngLen = 0;i = 0;}}}}}socket.close();Log.d(TAG, "accept: 断开连接");} catch (IOException e) {e.printStackTrace();}}//byte转intpublic int getInt(byte[] srcBytes, int srcPos, int length) {byte[] bytes = new byte[length];System.arraycopy(srcBytes, srcPos, bytes, 0, length);int anInt = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getInt();return anInt;}//byte转Stringpublic String getString(byte[] srcBytes, int srcPos, int length) {byte[] bytes = new byte[length];System.arraycopy(srcBytes, srcPos, bytes, 0, length);String str = "解析错误";try {str = new String(bytes, 0, length, chaSet);} catch (UnsupportedEncodingException e) {e.printStackTrace();}return str.trim();}private onReceiveListener onReceiveListener;public interface onReceiveListener {void receive(int nPostX, int nPostY, int nSize, int nReserveOne, int nReserveTwo, String reserve, int color, long pngLen,byte[] bytes);}public void setOnReceiveListener(onReceiveListener onReceiveListener) {this.onReceiveListener = onReceiveListener;}
}

这里使用的是Runnable接口来进行创建线程,主要逻辑操作都封装在run方法中,创建一个socket,然后接收数据,由于提前定义好了数据结构,然后我在accpetData中取出对应长度的字节数组,并且根据数据类型转换成Int或者String,由于最后传入的时图片的长度,那么除了前面的84字节,后面的都是图片的内容,然后就把后面接收到的所有字节拼接成一个新的字节数组,这就是图片。

然后就是byte转int和byte转String的方法。

接下来就是定义接口和回调,在上面接收数据的时候,如果再没有图片字节数组,就代表已经传递完毕,调用receive方法将已经接收的数据回调到页面。

二 主界面接收数据以及更新UI

在程序启动后,开启进行接收数据的监听:

    @Overrideprotected void onResume() {super.onResume();tcpServer.setOnReceiveListener(new TCPServer.onReceiveListener() {@Overridepublic void receive(int nPostX, int nPostY, int nSize, int nReserveOne, int nReserveTwo, String reserve, int color, long pngLen, byte[] bytes) {Log.d(TAG, "receive\n postX:" + nPostX + "\npostY:" + nPostY + "\nSize:" + nSize + "\nnReserveOne:"+ nReserveOne + "\nnReserveTwo:" + nReserveTwo + "\nreserve:" + reserve + "\ncolor:" + color + "\npngLen:" + pngLen + "\nbytes:" + bytes);//子线程中获取收到的信息 用handler发送给主线程BrandBean brandBean = new BrandBean(nPostX, nPostY, nSize, nReserveOne, nReserveTwo, reserve, color, bytes);Message message = Message.obtain();message.what = 0;Bundle bundle = new Bundle();bundle.putParcelable("brand", brandBean);message.setData(bundle);handler.sendMessage(message);}});}

接收到的数据通过handler发送给主线程,更新UI操作

    //主线程用handler接收数据  更新UI(背景+时间)final Handler handler = new Handler() {@Overridepublic void handleMessage(@NonNull Message msg) {super.handleMessage(msg);switch (msg.what) {case 0:Bundle bundle = msg.getData();BrandBean brand = bundle.getParcelable("brand");//更新UIUpdateBg(brand.getTimeX(), brand.getTimeY(), brand.getTimeSize(), brand.getReceiveOne(), brand.getReceiveTwo(), brand.getReceive(), brand.getTimeColor(), brand.getBgImg());}}};

接下来是字节数组转图片,我用的是Bitmap类来进行操作。来介绍下Bitmap,在 Android 中,Bitmap是用于表示图像的类,提供了各种方法和功能来加载、创建、操作和显示图像。下面是对 Bitmap的使用进行详细解释:

  1. 加载图像:

    • 通过资源加载:使用 BitmapFactory 类的 decodeResource() 方法从资源中加载图像。
    • 通过文件加载:使用 BitmapFactory 类的 decodeFile() 方法从文件中加载图像。
    • 通过字节数组加载:使用 BitmapFactory 类的 decodeByteArray() 方法从字节数组中加载图像。
    • 通过流加载:使用 BitmapFactory 类的 decodeStream() 方法从输入流中加载图像。
  2. 创建图像:

    • 使用 Bitmap.createBitmap() 方法创建空白的 Bitmap 对象。
    • 使用 Bitmap.createScaledBitmap() 方法创建按比例缩放的 Bitmap 对象。
    • 使用 Bitmap.createBitmap(int width, int height, Bitmap.Config config) 方法创建指定尺寸和配置的 Bitmap 对象。
  3. 图像操作和处理:

    • 裁剪图像:使用 Bitmap.createBitmap() 方法创建裁剪后的新 Bitmap 对象。
    • 缩放图像:使用 Bitmap.createScaledBitmap() 方法创建缩放后的新 Bitmap 对象。
    • 旋转图像:使用 Matrix 类的 postRotate() 方法旋转图像,并使用 Bitmap.createBitmap() 方法创建旋转后的新 Bitmap 对象。
    • 应用滤镜效果:使用 ColorMatrixColorMatrixColorFilter 类来调整图像颜色和应用滤镜效果。
    • 修改像素值:使用 setPixel()getPixel() 方法直接修改或获取图像的像素值。
  4. 图像存储和读取:

    • 保存图像:使用 compress() 方法将 Bitmap 对象保存为指定格式的图像文件。
    • 读取图像:使用 decodeFile() 方法从文件中读取图像数据为 Bitmap 对象。
  5. 图像显示:

    • 使用 ImageView 组件:将 Bitmap 对象设置给 ImageView 组件,通过 setImageBitmap() 方法显示图像。
    • 使用 Canvas 绘制:使用 Canvas 类的 drawBitmap() 方法在 Canvas 上绘制图像。
  6. 内存管理和优化:

    • 及时回收:通过调用 Bitmap.recycle() 方法及时回收不再需要的 Bitmap 对象,释放内存资源。
    • 优化加载:通过设置 BitmapFactory.Options 对象的 inSampleSize 字段来减小图像的尺寸,降低内存占用。
    • 图像缓存:使用图像缓存库(如 LruCache、DiskLruCache)进行图像的内存和

三 操作文件类 保存图片

操作SD卡的相关代码如下:

    /*** 获取SD卡下程序的缓存路径*/public static File getCacheDir(Context context, String name) {File cacheDir;if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {cacheDir = context.getExternalCacheDir();  // 外部存储 需要手动清理 /sdcard/Android/data/<package_name>/cache/} else {cacheDir = context.getCacheDir();  // 应用内内存 /data/data/<package_name>/cache/}if (cacheDir != null) {if (name != null) {File cacheFile = new File(cacheDir.getAbsolutePath() + "/" + name);//当前cache所在的路径Log.d(TAG, "getCacheDir:33 "+cacheFile);if (createDir(cacheFile)) {cacheDir = cacheFile;Log.d(TAG, "getCacheDir: "+cacheDir);} else {cacheDir = null;}}} else {return null;}return cacheDir;}/*** 创建指定文件夹** @param cacheDir* @return*/private static boolean createDir(File cacheDir) {if (cacheDir == null) {return false;}if (cacheDir.exists() && cacheDir.isDirectory()) {return true;} else {return cacheDir.mkdir();}}

操作文件类写好了,bitmap进行图片的存储和读取也知道对应的方法了,下面就是进行文件夹下的图片的存储和读取:

    /*** 向文件夹写入bitmap*/public static boolean writeBitmap(File path, Bitmap bitmap) {FileOutputStream fs = null;Log.d(TAG, "writeBitmap: "+path);try {fs = new FileOutputStream(path.getAbsolutePath());bitmap.compress(Bitmap.CompressFormat.PNG, 100, fs); //将bitmap压缩成PNG形式写入文件夹fs.flush();//刷新输出流 确保缓冲区的数据及时写入 不会丢失return true;} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} finally {if (fs != null) {try {fs.close();} catch (IOException e) {e.printStackTrace();}}}return false;}/*** 从指定文件夹下读取bitmap*/public static Bitmap readBitmap(File file) {if (!file.exists()) {return null;} else {BitmapFactory.Options options = new BitmapFactory.Options();options.inPreferredConfig = Bitmap.Config.ARGB_8888;Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());return bitmap;}}

在主线程的handler中接收数据进行更新UI的地方调用该方法即可,注意UI更新要在主线程中进行

        new Thread(new Runnable() {@Overridepublic void run() {Bitmap fileBitmap = getFileBitmap(IMAGE_BG);runOnUiThread(new Runnable() {@Overridepublic void run() {imgBg.setImageBitmap(fileBitmap);}});}}).start();


http://www.ppmy.cn/news/73939.html

相关文章

【离散数学】置换群和伯恩赛德定理编程题

1&#xff1a;置换的轮换表示 给出一个置换&#xff0c;写出该置换的轮换表示。比如 (1 2 3 4 5 6 7 8 9) (3 1 6 2 9 7 8 4 5) 表示为(1 3 6 7 8 4 2)(5 9) 输入&#xff1a; 置换后的序列 输出&#xff1a; 不相杂的轮换乘积&#xff0c;每行表示一个轮换&#xff08;轮换的起…

Docker 简介

文章目录 Docker 简介1. 什么是 Docker1. Docker 简介2. 打包、分发、部署3. Docker 优点4. Docker 通常用来做什么 2. 三大组件1. 镜像&#xff08;Image&#xff09;2. 容器&#xff08;Container&#xff09;3. Registry 与 仓库&#xff08;Repository&#xff09; 3. Dock…

如何借助Kafka持久化存储K8S事件数据?

大家应该对 Kubernetes Events 并不陌生&#xff0c;特别是当你使用 kubectl describe 命令或 Event API 资源来了解集群中的故障时。 $ kubectl get events15m Warning FailedCreate …

基于FPGA的超声波测距——数码管显示

文章目录 前言一、超声波模块介绍1、产品特点2、超声波模块的时序图 二、系统设计1、系统框图2、源码3、RTL视图4、效果 三、总结四、参考资料 前言 环境&#xff1a; 1、Quartus18.1 2、vscode 3、板子型号&#xff1a;EP4CE6F17C8N 4、超声波模块&#xff1a;HC_SR04 要求&am…

【Java编程系列】Springcloud-gateway自带限流方案实践篇

1、前言 作为一个后端开发&#xff0c;对于后端服务的安全性方面&#xff0c;一定要有足够的考虑。近期的开发工作中&#xff0c;有一个实现分享外部链接的需求点&#xff0c;个人认为这一块会有安全隐患。比如&#xff0c;因为这个分享的外链会被用户无限制点开查看&#xff0…

java 定时器实现原理(时间轮算法)

Java 定时器是 Java 编程语言提供的一种机制&#xff0c;用于在预定时间间隔内执行给定任务。它允许您针对一些重要的应用程序需求创建大量定时任务&#xff0c;例如自动化备份、日志记录、数据清理等。在本文中&#xff0c;我们将深入探讨 Java 定时器实现的原理&#xff0c;以…

csgo搬砖人必知:未来csgo饰品会一路走低吗?市场回暖到底还要多久?

csgo搬砖人必知&#xff1a;未来csgo饰品会一路走低吗&#xff1f;市场回暖到底还要多久&#xff1f; 最后一届巴黎major终于落下帷幕&#xff0c;Vitality小蜜蜂2-0战胜GL成功赢下本次Major冠军&#xff0c;也是首次夺得Major冠军&#xff01;有人欢喜有人忧啊&#xff0c;cs…

坦克大战进阶--发射子弹

坦克大战进阶–发射子弹 1. 坦克大战0.3 1.1 分析 利用线程基础的知识&#xff0c;把坦克大战再次进阶一下&#xff1a;当我们按下J键&#xff0c;坦克就能够发射一颗子弹。 1.2 思路 当发射一颗子弹后&#xff0c;就相当于启动一个线程Mytank 有子弹的对象&#xff0c;当…