要件:
Android 側で TCP サーバーとしてインターフェイスを作成し、クライアントから送信された写真や情報を受け取り、インターフェイス上に表示します。アプリを再度開くときは、最後の画像が存在することを確認してください。
アイデア:
1 TCP サーバーを作成し、実行可能なインターフェイスを継承して実装し、TCP によって受信されたデータを監視するインターフェイス コールバックを作成します。
2 メインインターフェイスは TCP サービスのインターフェイスを監視し、背景画像は ImgView で、読み込みにはビットマップを使用します。
3 画像とローカル データを保存します。テキストには sp ストレージを使用し、画像は SDCard に保存し、ファイルを使用して操作します。
関係する主な知識ポイント:
1TCP
2 スレッドの作成方法とメリット・デメリット
3 ビットマップの使用
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 メソッドにカプセル化され、ソケットの作成とデータの受信が行われます。データ構造は事前に定義されているため、対応する長さのバイト配列を取り出します。 accpetData に格納され、データ型に応じて Int または String に変換されます。最後の入力画像の長さにより、最初の 84 バイトを除いて、後者は画像の内容となり、その後に受信したすべてのバイトになります新しいバイト配列に結合されます。これが画像です。
次に、バイトを int に、バイトを String に変換する方法があります。
次のステップはインターフェイスとコールバックを定義することです. 上記のデータを受信するときに、イメージのバイト配列がなければ、転送が完了したことを意味します. 受信メソッドを呼び出して、受信したデータをページにコールバックします。
2 番目のメイン インターフェイスはデータを受信し、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);
}
});
}
受信したデータはハンドラーを通じてメインスレッドに送信され、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 は画像を表すために使用されるクラスであり、画像をロード、作成、操作、表示するためのさまざまなメソッドと関数を提供します。以下にビットマップの使用法を詳しく説明します。
画像をロードします:
- リソース経由でロード:
BitmapFactory
クラスのメソッドを使用してdecodeResource()
、リソースから画像をロードします。- ファイルからロード:
BitmapFactory
クラスのメソッドを使用してdecodeFile()
、ファイルから画像をロードします。- バイト配列によるロード:
BitmapFactory
クラスのメソッドを使用してdecodeByteArray()
、バイト配列から画像をロードします。- ストリーム経由の読み込み:
BitmapFactory
クラスのメソッドを使用してdecodeStream()
、入力ストリームから画像を読み込みます。イメージを作成します。
Bitmap.createBitmap()
メソッドを使用して空のBitmap
オブジェクトを作成します。Bitmap.createScaledBitmap()
このメソッドを使用して、比例的にスケールされたBitmap
オブジェクトを作成します。- メソッドを使用して、
Bitmap.createBitmap(int width, int height, Bitmap.Config config)
指定したサイズと構成のオブジェクトを作成しますBitmap
。画像の操作と処理:
- トリミングされた画像: この
Bitmap.createBitmap()
メソッドを使用して、新しいトリミングされたBitmap
オブジェクトを作成します。- スケーリングされたイメージ:
Bitmap.createScaledBitmap()
メソッドを使用して、新しいスケーリングされたBitmap
オブジェクトを作成します。- 画像を回転する:
Matrix
クラスのメソッドを使用してpostRotate()
画像を回転し、Bitmap.createBitmap()
そのメソッドを使用して新しい回転Bitmap
オブジェクトを作成します。- フィルター効果の適用:
ColorMatrix
およびColorMatrixColorFilter
クラスを使用して、画像の色を調整し、フィルター効果を適用します。- ピクセル値の変更:
setPixel()
およびgetPixel()
メソッドを使用して、画像のピクセル値を直接変更または取得します。画像の保存と読み取り:
- 画像の保存:オブジェクトを指定した形式の画像ファイルとして保存する
compress()
メソッドを使用します。Bitmap
- Read Image:
decodeFile()
ファイルから画像データをオブジェクトとして読み取るメソッドを使用しますBitmap
。画像表示:
ImageView
コンポーネントを使用します。Bitmap
オブジェクトをImageView
コンポーネントに設定し、setImageBitmap()
メソッドを通じて画像を表示します。- で描画
Canvas
:Canvas
クラスのdrawBitmap()
メソッドを使用してCanvas
、 上に画像を描画します。メモリ管理と最適化:
Bitmap.recycle()
時間内のリサイクル:メソッドを呼び出して、Bitmap
メモリ リソースの解放に間に合うように不要になったオブジェクトをリサイクルします。BitmapFactory.Options
読み込みの最適化:オブジェクトのフィールドを設定することで、inSampleSize
画像のサイズを削減し、メモリ使用量を削減します。- 画像キャッシュ: 画像メモリとディスクに画像キャッシュ ライブラリ (LruCache、DiskLruCache など) を使用します。
画像を保存するための3つの操作ファイルクラス
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
*/
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;
}
}
メイン スレッドのハンドラーでデータを受信する場所でこのメソッドを呼び出して 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();
テキストビューの表示位置:
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
layoutParams.leftMargin = x;
layoutParams.topMargin = y;
timeText.setTextSize(timeSize);