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)); } }); } }