gradle添加依赖:
implementation 'com.squareup.okhttp3:okhttp:4.9.3'
ifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.myapplication">
<!-- 互联网 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 存储卡 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApplication"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="5dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:text="用户名:"
android:textColor="@color/black"
android:textSize="17sp" />
<EditText
android:id="@+id/et_username"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/editext_selector"
android:gravity="left|center"
android:hint="请输入用户名"
android:maxLength="11"
android:textColor="@color/black"
android:textSize="17sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:text="密 码:"
android:textColor="@color/black"
android:textSize="17sp" />
<EditText
android:id="@+id/et_password"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/editext_selector"
android:gravity="left|center"
android:hint="请输入密码"
android:inputType="numberPassword"
android:maxLength="6"
android:textColor="@color/black"
android:textSize="17sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp" >
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:text="头 像:"
android:textColor="@color/black"
android:textSize="17sp" />
<ImageView
android:id="@+id/iv_face"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_marginLeft="5dp"
android:scaleType="fitXY"
android:src="@drawable/add_pic" />
</LinearLayout>
<Button
android:id="@+id/btn_register"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="注册"
android:textColor="@color/black"
android:textSize="17sp" />
<TextView
android:id="@+id/tv_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/black"
android:textSize="17sp" />
</LinearLayout>
BitmapUtil
package com.example.myapplication;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.net.Uri;
import android.os.Environment;
import android.util.Log;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.ByteBuffer;
public class BitmapUtil {
private final static String TAG = "BitmapUtil";
// 把位图数据保存到指定路径的图片文件
public static void saveImage(String path, Bitmap bitmap) {
// 根据指定的文件路径构建文件输出流对象
try (FileOutputStream fos = new FileOutputStream(path)) {
// 把位图数据压缩到文件输出流中
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, fos);
} catch (Exception e) {
e.printStackTrace();
}
}
// 把位图数据保存到指定路径的图片文件
public static void saveImage(String path, ByteBuffer buffer) {
try (FileOutputStream fos = new FileOutputStream(path)) {
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
fos.write(data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
}
}
// 获得旋转角度之后的位图对象
public static Bitmap getRotateBitmap(Bitmap bitmap, float rotateDegree) {
Matrix matrix = new Matrix(); // 创建操作图片用的矩阵对象
matrix.postRotate(rotateDegree); // 执行图片的旋转动作
// 创建并返回旋转后的位图对象
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
bitmap.getHeight(), matrix, false);
}
// 获得比例缩放之后的位图对象
public static Bitmap getScaleBitmap(Bitmap bitmap, double scaleRatio) {
Matrix matrix = new Matrix(); // 创建操作图片用的矩阵对象
matrix.postScale((float)scaleRatio, (float)scaleRatio);
// 创建并返回缩放后的位图对象
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
bitmap.getHeight(), matrix, false);
}
// 获得自动缩小后的位图对象
public static Bitmap getAutoZoomImage(Context ctx, Uri uri) {
Log.d(TAG, "getAutoZoomImage uri="+uri.toString());
Bitmap zoomBitmap = null;
// 打开指定uri获得输入流对象
try (InputStream is = ctx.getContentResolver().openInputStream(uri)) {
// 从输入流解码得到原始的位图对象
Bitmap originBitmap = BitmapFactory.decodeStream(is);
int ratio = originBitmap.getWidth()/2000+1;
// 获得比例缩放之后的位图对象
zoomBitmap = getScaleBitmap(originBitmap, 1.0/ratio);
} catch (Exception e) {
e.printStackTrace();
}
return zoomBitmap;
}
// 获得自动缩小后的位图对象
public static Bitmap getAutoZoomImage(Bitmap origin) {
int ratio = origin.getWidth()/2000+1;
// 获得并返回比例缩放之后的位图对象
return getScaleBitmap(origin, 1.0/ratio);
}
// 获得自动缩小后的图片路径
public static String getAutoZoomPath(Context ctx, Uri uri) {
Log.d(TAG, "getAutoZoomPath uri="+uri.toString());
String imagePath = String.format("%s/%s.jpg",
ctx.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString(),
DateUtil.getNowDateTime());
// 打开指定uri获得输入流对象
try (InputStream is = ctx.getContentResolver().openInputStream(uri)) {
// 从输入流解码得到原始的位图对象
Bitmap originBitmap = BitmapFactory.decodeStream(is);
int ratio = originBitmap.getWidth()/1000+1;
// 获得比例缩放之后的位图对象
Bitmap zoomBitmap = getScaleBitmap(originBitmap, 1.0/ratio);
saveImage(imagePath, zoomBitmap);
} catch (Exception e) {
e.printStackTrace();
}
return imagePath;
}
}
activity:
package com.example.myapplication;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public class MainActivity extends AppCompatActivity
{
private final static String TAG = "OkhttpUploadActivity";
public final static String URL_REGISTER = NetConst.HTTP_PREFIX + "register";
private EditText et_username; // 声明一个编辑框对象
private EditText et_password; // 声明一个编辑框对象
private TextView tv_result; // 声明一个文本视图对象
private ImageView iv_face; // 声明一个图像视图对象
private int CHOOSE_CODE = 3; // 只在相册挑选图片的请求码
private List<String> mPathList = new ArrayList<>(); // 头像文件的路径列表
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et_username = findViewById(R.id.et_username);
et_password = findViewById(R.id.et_password);
iv_face = findViewById(R.id.iv_face);
tv_result = findViewById(R.id.tv_result);
iv_face.setOnClickListener(v -> {
// 创建一个内容获取动作的意图(准备跳到系统相册)
Intent albumIntent = new Intent(Intent.ACTION_GET_CONTENT);
albumIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false); // 是否允许多选
albumIntent.setType("image/*"); // 类型为图像
startActivityForResult(albumIntent, CHOOSE_CODE); // 打开系统相册
});
findViewById(R.id.btn_register).setOnClickListener(v -> uploadFile());
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
if (resultCode == RESULT_OK && requestCode == CHOOSE_CODE) { // 从相册返回
mPathList.clear();
if (intent.getData() != null) { // 从相册选择一张照片
// 把指定Uri的图片复制一份到内部存储空间,并返回存储路径
String imagePath = saveImage(intent.getData());
Log.i(TAG, imagePath); // /storage/emulated/0/Android/data/com.example.myapplication/files/Download/image%3A1000096252.jpg
mPathList.add(imagePath);
}
}
}
// 把指定Uri的图片复制一份到内部存储空间,并返回存储路径
private String saveImage(Uri uri) {
String uriStr = uri.toString();
String imageName = uriStr.substring(uriStr.lastIndexOf("/")+1);
String imagePath = String.format("%s/%s.jpg", getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString(), imageName);
// 获得自动缩小后的位图对象
Bitmap bitmap = BitmapUtil.getAutoZoomImage(this, uri);
// 把位图数据保存到指定路径的图片文件
BitmapUtil.saveImage(imagePath, bitmap);
iv_face.setImageBitmap(bitmap);
return imagePath;
}
// 执行文件上传动作
private void uploadFile() {
if (mPathList.size() <= 0) {
Toast.makeText(this, "请选择待上传的用户头像", Toast.LENGTH_SHORT).show();
return;
}
// 创建分段内容的建造器对象
MultipartBody.Builder builder = new MultipartBody.Builder();
String username = et_username.getText().toString();
String password = et_password.getText().toString();
if (!TextUtils.isEmpty(username)) {
// 往建造器对象添加文本格式的分段数据
builder.addFormDataPart("username", username);
builder.addFormDataPart("password", password);
}
for (String path : mPathList) { // 添加多个附件
File file = new File(path); // 根据文件路径创建文件对象
Log.i(TAG, file.toString()); // /storage/emulated/0/Android/data/com.example.myapplication/files/Download/image%3A1000096252.jpg
// 往建造器对象添加图像格式的分段数据
builder.addFormDataPart("image", file.getName(), RequestBody.create(file, MediaType.parse("image/*")) // 带三个输入参数的addFormDataPart方法,它的第一个参数是文件类型,第二个参数是文件名称,第三个参数是文件体,该方法用于传输文件
);
}
RequestBody body = builder.build(); // 根据建造器生成请求结构
OkHttpClient client = new OkHttpClient(); // 创建一个okhttp客户端对象
// 创建一个POST方式的请求结构
Request request = new Request.Builder().post(body).url(URL_REGISTER).build();
Call call = client.newCall(request); // 根据请求结构创建调用对象
// 加入HTTP请求队列。异步调用,并设置接口应答的回调方法
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) { // 请求失败
// 回到主线程操纵界面
runOnUiThread(() -> tv_result.setText("调用注册接口报错:\n"+e.getMessage()));
}
@Override
public void onResponse(Call call, final Response response) throws IOException { // 请求成功
String resp = response.body().string();
// 回到主线程操纵界面
runOnUiThread(() -> tv_result.setText("调用注册接口返回:\n"+resp));
}
});
}
}