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

需求

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;

    @Override
    public void run() {
        try {
            ServerSocket serverSocket = new ServerSocket(port);
            Log.d(TAG, "run:等待客户端连接... ");
            //serverSocket.setSoTimeout(2000);
            while (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转int
    public 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转String
    public 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

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

    @Override
    protected void onResume() {
        super.onResume();
        tcpServer.setOnReceiveListener(new TCPServer.onReceiveListener() {
            @Override
            public 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() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 0:
                    Bundle bundle = msg.getData();
                    BrandBean brand = bundle.getParcelable("brand");
                    //更新UI
                    UpdateBg(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() {
            @Override
            public void run() {
                Bitmap fileBitmap = getFileBitmap(IMAGE_BG);
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        imgBg.setImageBitmap(fileBitmap);
                    }
                });

            }
        }).start();

text View的显示位置:

 RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
        layoutParams.leftMargin = x;
        layoutParams.topMargin = y;
        timeText.setTextSize(timeSize);

猜你喜欢

转载自blog.csdn.net/m0_56366502/article/details/130800319