diff --git a/app/build.gradle b/app/build.gradle index 2cd2f51..f00f1f8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -106,6 +106,8 @@ dependencies { implementation 'io.reactivex.rxjava3:rxandroid:3.0.0' // implementation 'com.squareup.okhttp3:okhttp:4.7.0' + //log打印 + implementation 'com.squareup.okhttp3:logging-interceptor:4.7.0' implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' // implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0' @@ -137,4 +139,6 @@ dependencies { implementation 'me.jessyan:autosize:1.2.1' // 吐司框架:https://github.com/getActivity/Toaster implementation 'com.github.getActivity:Toaster:12.6' + //图片裁切 + implementation 'com.github.yalantis:ucrop:2.2.10' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 02dd7a3..b7d3be5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,11 +2,16 @@ + + + + + + - @@ -16,6 +21,7 @@ android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" + android:requestLegacyExternalStorage="true" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> @@ -78,7 +84,9 @@ - + @@ -101,7 +109,7 @@ mLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback() { + @Override + public void onActivityResult(ActivityResult result) { + Log.e(TAG, "onActivityResult: " + result); + if (result != null) { + Intent intent = result.getData(); + if (intent != null && result.getResultCode() == Activity.RESULT_OK) { + + } + } + } + }); + public class BtnClick { + public void takePhoto(View view) { + checkPermission(); + } + + public void openGallery(View view) { + Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); + intent.setType("image/*"); + startActivityForResult(intent, REQUEST_GALLERY); + } + public void write(View view) { if (mTag == null) { Toaster.showLong("NFC卡片未连接"); @@ -231,6 +364,8 @@ public class WriteMessageActivity extends AppCompatActivity { contactInfo.setQq(qq); contactInfo.setTag(tag); contactInfo.setOperation(mOperationId); + contactInfo.setAvatar(mUploadImageUrl); + if (mBinding.rbEncryption.isChecked()) { try { diff --git a/app/src/main/java/com/ttstd/elderlyassistant/activity/write/WriteMessageViewModel.java b/app/src/main/java/com/ttstd/elderlyassistant/activity/write/WriteMessageViewModel.java index 0b63b93..0442be7 100644 --- a/app/src/main/java/com/ttstd/elderlyassistant/activity/write/WriteMessageViewModel.java +++ b/app/src/main/java/com/ttstd/elderlyassistant/activity/write/WriteMessageViewModel.java @@ -1,11 +1,52 @@ package com.ttstd.elderlyassistant.activity.write; -import androidx.databinding.ObservableInt; +import android.util.Log; + +import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; -import com.ttstd.elderlyassistant.R; +import com.ttstd.elderlyassistant.bean.UploadImageResponse; +import com.ttstd.elderlyassistant.network.NetInterfaceManager; + +import java.io.File; + +import io.reactivex.rxjava3.annotations.NonNull; +import io.reactivex.rxjava3.core.Observer; +import io.reactivex.rxjava3.disposables.Disposable; public class WriteMessageViewModel extends ViewModel { + private static final String TAG = "WriteMessageViewModel"; + + public MutableLiveData mUploadImageUrlData = new MutableLiveData<>(); + public void uploadAvatar(File file) { + NetInterfaceManager.getInstance().getDevicesLockedStateObservable(file) +// .compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY)) + .subscribe(new Observer() { + @Override + public void onSubscribe(@NonNull Disposable d) { + Log.e("uploadAvatar", "onSubscribe: "); + } + + @Override + public void onNext(@NonNull UploadImageResponse uploadImageResponse) { + Log.e("uploadAvatar", "onNext: "); + if (uploadImageResponse.isSuccess()) { + String url = uploadImageResponse.getUrl(); + mUploadImageUrlData.setValue(url); + } + } + + @Override + public void onError(@NonNull Throwable e) { + Log.e("uploadAvatar", "onError: " + e.getMessage()); + } + + @Override + public void onComplete() { + Log.e("uploadAvatar", "onComplete: "); + } + }); + } } diff --git a/app/src/main/java/com/ttstd/elderlyassistant/annotations/ImageViewAdapter.java b/app/src/main/java/com/ttstd/elderlyassistant/annotations/ImageViewAdapter.java new file mode 100644 index 0000000..3c18345 --- /dev/null +++ b/app/src/main/java/com/ttstd/elderlyassistant/annotations/ImageViewAdapter.java @@ -0,0 +1,56 @@ +package com.ttstd.elderlyassistant.annotations; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.text.TextUtils; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.databinding.BindingAdapter; + +import com.bumptech.glide.Glide; +import com.ttstd.elderlyassistant.R; + +public class ImageViewAdapter { + @BindingAdapter("android:src") + public static void setSrc(ImageView view, Bitmap bitmap) { + view.setImageBitmap(bitmap); + } + + @BindingAdapter("android:src") + public static void setSrc(ImageView view, int resId) { + view.setImageResource(resId); + } + + @BindingAdapter("imageUrl") + public static void setSrc(ImageView imageView, String url) { + Glide.with(imageView.getContext()) + .load(url) + .error(R.mipmap.ic_launcher) + .centerCrop() + .into(imageView); + } + + /** + * 自定义设置图片属性 - 在匹配时自定义命名空间会被忽略 + */ + @BindingAdapter({"imageUrl", "error"}) + public static void loadImage(ImageView imageView, String url, Drawable error) { + Glide.with(imageView.getContext()) + .load(url) + .error(error) + .into(imageView); + } + + @BindingAdapter({"imageAvatarUrl", "error"}) + public static void loadAvatarImage(ImageView imageView, String url, Drawable error) { + Glide.with(imageView.getContext()) + .load(url) + .error(error) + .into(imageView); + } + + + +} \ No newline at end of file diff --git a/app/src/main/java/com/ttstd/elderlyassistant/bean/ContactInfo.java b/app/src/main/java/com/ttstd/elderlyassistant/bean/ContactInfo.java index 2fc390c..21f614c 100644 --- a/app/src/main/java/com/ttstd/elderlyassistant/bean/ContactInfo.java +++ b/app/src/main/java/com/ttstd/elderlyassistant/bean/ContactInfo.java @@ -15,6 +15,7 @@ public class ContactInfo implements Serializable { public String tag; public String wxid; public String qq; + public String avatar; /** * 1 电话 2 短信 3 微信语音 4 微信视频 5 QQ语音 6 QQ视频 */ @@ -73,6 +74,14 @@ public class ContactInfo implements Serializable { this.qq = qq; } + public String getAvatar() { + return avatar; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + public int getOperation() { return operation; } diff --git a/app/src/main/java/com/ttstd/elderlyassistant/bean/UploadImageResponse.java b/app/src/main/java/com/ttstd/elderlyassistant/bean/UploadImageResponse.java new file mode 100644 index 0000000..260fe1f --- /dev/null +++ b/app/src/main/java/com/ttstd/elderlyassistant/bean/UploadImageResponse.java @@ -0,0 +1,78 @@ +package com.ttstd.elderlyassistant.bean; + +import java.io.Serializable; + +public class UploadImageResponse implements Serializable { + private static final long serialVersionUID = 8945363249461745018L; + + boolean success; + String url; + Data data; + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public Data getData() { + return data; + } + + public void setData(Data data) { + this.data = data; + } + + public class Data implements Serializable { + private static final long serialVersionUID = 2237301789387292709L; + + String filename; + long original_size; + long compressed_size; + float compression_ratio; + + public String getFilename() { + return filename; + } + + public void setFilename(String filename) { + this.filename = filename; + } + + public long getOriginal_size() { + return original_size; + } + + public void setOriginal_size(long original_size) { + this.original_size = original_size; + } + + public long getCompressed_size() { + return compressed_size; + } + + public void setCompressed_size(long compressed_size) { + this.compressed_size = compressed_size; + } + + public float getCompression_ratio() { + return compression_ratio; + } + + public void setCompression_ratio(float compression_ratio) { + this.compression_ratio = compression_ratio; + } + } + + +} diff --git a/app/src/main/java/com/ttstd/elderlyassistant/network/MD5Util.java b/app/src/main/java/com/ttstd/elderlyassistant/network/MD5Util.java new file mode 100644 index 0000000..32fa986 --- /dev/null +++ b/app/src/main/java/com/ttstd/elderlyassistant/network/MD5Util.java @@ -0,0 +1,112 @@ +package com.ailaiban.ai.network; + +import android.annotation.SuppressLint; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class MD5Util { + + public static String packetMD5(String str) { + MessageDigest messageDigest = null; + try { + messageDigest = MessageDigest.getInstance("MD5"); + + messageDigest.reset(); + + messageDigest.update(str.getBytes("UTF-8")); + } catch (NoSuchAlgorithmException e) { + System.out.println("NoSuchAlgorithmException caught!"); + System.exit(-1); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + + byte[] byteArray = messageDigest.digest(); + + StringBuffer md5StrBuff = new StringBuffer(); + + for (int i = 0; i < byteArray.length; i++) { + if (Integer.toHexString(0xFF & byteArray[i]).length() == 1) + md5StrBuff.append("0").append( + Integer.toHexString(0xFF & byteArray[i])); + else + md5StrBuff.append(Integer.toHexString(0xFF & byteArray[i])); + } + + return md5StrBuff.toString(); + } + + @SuppressLint("DefaultLocale") + public static String getUpperMD5Str(String str) { + MessageDigest messageDigest = null; + + try { + messageDigest = MessageDigest.getInstance("MD5"); + + messageDigest.reset(); + + messageDigest.update(str.getBytes("UTF-8")); + } catch (NoSuchAlgorithmException e) { + System.out.println("NoSuchAlgorithmException caught!"); + System.exit(-1); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + + byte[] byteArray = messageDigest.digest(); + + StringBuffer md5StrBuff = new StringBuffer(); + + for (int i = 0; i < byteArray.length; i++) { + if (Integer.toHexString(0xFF & byteArray[i]).length() == 1) + md5StrBuff.append("0").append( + Integer.toHexString(0xFF & byteArray[i])); + else + md5StrBuff.append(Integer.toHexString(0xFF & byteArray[i])); + } + + return md5StrBuff.toString().toUpperCase(); + } + + + /** + * 获取16位的MD5 值得 + * + * @param str + * @return + */ + @SuppressLint("DefaultLocale") + public static String getUpperMD5Str16(String str) { + MessageDigest messageDigest = null; + + try { + messageDigest = MessageDigest.getInstance("MD5"); + + messageDigest.reset(); + + messageDigest.update(str.getBytes("UTF-8")); + } catch (NoSuchAlgorithmException e) { + System.out.println("NoSuchAlgorithmException caught!"); + System.exit(-1); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + + byte[] byteArray = messageDigest.digest(); + + StringBuffer md5StrBuff = new StringBuffer(); + + for (int i = 0; i < byteArray.length; i++) { + if (Integer.toHexString(0xFF & byteArray[i]).length() == 1) + md5StrBuff.append("0").append( + Integer.toHexString(0xFF & byteArray[i])); + else + md5StrBuff.append(Integer.toHexString(0xFF & byteArray[i])); + } + + return md5StrBuff.toString().toUpperCase().substring(8, 24); + } + +} diff --git a/app/src/main/java/com/ttstd/elderlyassistant/network/NetInterfaceManager.java b/app/src/main/java/com/ttstd/elderlyassistant/network/NetInterfaceManager.java index 1dd3103..58380d1 100644 --- a/app/src/main/java/com/ttstd/elderlyassistant/network/NetInterfaceManager.java +++ b/app/src/main/java/com/ttstd/elderlyassistant/network/NetInterfaceManager.java @@ -2,14 +2,28 @@ package com.ttstd.elderlyassistant.network; import android.annotation.SuppressLint; import android.content.Context; +import android.util.Log; + +import com.ttstd.elderlyassistant.bean.UploadImageResponse; +import com.ttstd.elderlyassistant.network.api.UploadPictrueApi; +import com.ttstd.elderlyassistant.network.interceptor.RepeatRequestInterceptor; + +import org.jetbrains.annotations.NotNull; import java.io.File; import java.util.concurrent.TimeUnit; +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; +import io.reactivex.rxjava3.core.Observable; +import io.reactivex.rxjava3.schedulers.Schedulers; import okhttp3.Cache; import okhttp3.ConnectionPool; import okhttp3.Dispatcher; +import okhttp3.MediaType; +import okhttp3.MultipartBody; import okhttp3.OkHttpClient; +import okhttp3.RequestBody; +import okhttp3.logging.HttpLoggingInterceptor; import retrofit2.Retrofit; import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory; import retrofit2.converter.gson.GsonConverterFactory; @@ -29,8 +43,8 @@ public class NetInterfaceManager { private OkHttpClient mScdnClient; //超时时间 - private static final int TIME_OUT = 5; - // 缓存大小 + private static final int TIME_OUT = 30; + // 缓存大小 64M private static final long CACHE_SIZE = 1024 * 1024 * 64; private NetInterfaceManager(Context context) { @@ -73,6 +87,13 @@ public class NetInterfaceManager { builder.writeTimeout(TIME_OUT, TimeUnit.SECONDS);// 设置写入超时时间 builder.readTimeout(TIME_OUT, TimeUnit.SECONDS);// 设置读取数据超时时间 builder.retryOnConnectionFailure(true);// 设置进行连接失败重试 + builder.addInterceptor(new RepeatRequestInterceptor()); + builder.addInterceptor(new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() { + @Override + public void log(@NotNull String s) { + Log.e("HttpLoggingInterceptor", "log: " + s); + } + }).setLevel(HttpLoggingInterceptor.Level.BASIC)); // 设置缓存文件路径 String cacheDirectory = mContext.getExternalCacheDir().getAbsolutePath() + "/OkHttpCache"; Cache cache = new Cache(new File(cacheDirectory), CACHE_SIZE); @@ -110,5 +131,25 @@ public class NetInterfaceManager { return mOkHttpClient; } + public static MultipartBody.Part convertFileToJpegMultipartBody(File file, String key) { + MediaType mediaType = MediaType.Companion.parse("image/jpeg"); + RequestBody requestBody = RequestBody.Companion.create(file, mediaType); + MultipartBody.Part body = MultipartBody.Part.createFormData(key, file.getName(), requestBody); + return body; + } + + public static RequestBody convertObjectToRequestBody(Object param) { + MediaType mediaType = MediaType.Companion.parse("text/plain"); + RequestBody requestBody = RequestBody.Companion.create(String.valueOf(param), mediaType); + return requestBody; + } + + public Observable getDevicesLockedStateObservable(File file) { + MultipartBody.Part body = convertFileToJpegMultipartBody(file, "image"); + return mScdnRetrofit.create(UploadPictrueApi.class) + .uploadImg(body) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()); + } } diff --git a/app/src/main/java/com/ttstd/elderlyassistant/network/UrlAddress.java b/app/src/main/java/com/ttstd/elderlyassistant/network/UrlAddress.java index 053a2ac..9ef94a4 100644 --- a/app/src/main/java/com/ttstd/elderlyassistant/network/UrlAddress.java +++ b/app/src/main/java/com/ttstd/elderlyassistant/network/UrlAddress.java @@ -1,6 +1,10 @@ package com.ttstd.elderlyassistant.network; public class UrlAddress { - public static final String TTSTD = "https://www.ttstd.com:/65535"; + public static final String TTSTD = "https://www.ttstd.com:65535/"; public static final String IMG_SCDN = "https://img.scdn.io/"; + + public static final String SCDN_IMG_UPLOAD = "api/v1.php"; + + } diff --git a/app/src/main/java/com/ttstd/elderlyassistant/network/api/UploadPictrueApi.java b/app/src/main/java/com/ttstd/elderlyassistant/network/api/UploadPictrueApi.java new file mode 100644 index 0000000..10c3634 --- /dev/null +++ b/app/src/main/java/com/ttstd/elderlyassistant/network/api/UploadPictrueApi.java @@ -0,0 +1,18 @@ +package com.ttstd.elderlyassistant.network.api; + +import com.ttstd.elderlyassistant.bean.UploadImageResponse; +import com.ttstd.elderlyassistant.network.UrlAddress; + +import io.reactivex.rxjava3.core.Observable; +import okhttp3.MultipartBody; +import retrofit2.http.Multipart; +import retrofit2.http.POST; +import retrofit2.http.Part; + +public interface UploadPictrueApi { + @Multipart + @POST(UrlAddress.SCDN_IMG_UPLOAD) + Observable uploadImg( + @Part MultipartBody.Part image + ); +} diff --git a/app/src/main/java/com/ttstd/elderlyassistant/network/interceptor/RepeatRequestInterceptor.java b/app/src/main/java/com/ttstd/elderlyassistant/network/interceptor/RepeatRequestInterceptor.java new file mode 100644 index 0000000..a7325ba --- /dev/null +++ b/app/src/main/java/com/ttstd/elderlyassistant/network/interceptor/RepeatRequestInterceptor.java @@ -0,0 +1,107 @@ +package com.ttstd.elderlyassistant.network.interceptor; + +import android.util.Log; + + +import com.ttstd.elderlyassistant.BuildConfig; + +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.util.concurrent.ConcurrentHashMap; + +import okhttp3.Interceptor; +import okhttp3.Protocol; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.ResponseBody; +import okio.Buffer; + +/** + * v1.0 2022-07-15 16:16:52 + */ +public class RepeatRequestInterceptor implements Interceptor { + private static final String TAG = "RepeatInterceptor"; + + private final ConcurrentHashMap requestIdsMap = new ConcurrentHashMap<>(); + public static final String REPEAT_REQUEST_PROTOCOL = "OKHTTP_REPEAT_REQUEST_PROTOCOL"; + + @NotNull + @Override + public Response intercept(@NotNull Chain chain) throws IOException { + Request request = chain.request(); + Response response = chain.proceed(request); + ResponseBody responseBody = response.body(); + + //会消费请求,导致请求多次 + String content = responseBody.string(); +// Response copy = response.newBuilder().body(responseBody).build(); + ResponseBody copy = ResponseBody.create(responseBody.contentType(), content); + if (BuildConfig.DEBUG) { + Log.e(TAG, "请求体返回:| Response: " + request.url() + "\t body: " + content); + } + //相同的请求 + String requestKey = com.ailaiban.ai.network.MD5Util.getUpperMD5Str(request.method() + request.url().toString() + requestBodyToString(request.body())); + long time = System.currentTimeMillis();//请求时间 + try { + if (requestIdsMap.size() > 0 && requestIdsMap.containsKey(requestKey)) { + log("重复请求:", requestKey, request); + //下面这行写了不会抛出onerror +// chain.call().cancel(); + return new Response.Builder() + .protocol(Protocol.get(REPEAT_REQUEST_PROTOCOL)) + .request(request) //multi thread + .build(); + } + requestIdsMap.put(requestKey, time); + log("注册请求:", requestKey, request); +// RepeatRequestInterceptor.Builder builder = request.newBuilder(); +// builder.addHeader("header", jsonObject.toString()); + return response.newBuilder().body(copy).build(); + } catch (IOException e) { + Log.e(TAG, "intercept: " + e.getMessage()); + throw e; + } finally { + if (requestIdsMap.containsKey(requestKey) && requestIdsMap.containsValue(time)) {//请求任务完成删除map中的数据 + requestIdsMap.remove(requestKey); + log("移除请求:", requestKey, request); + } + } + } + + private void log(String action, String requestKey, Request request) { + if (BuildConfig.DEBUG) { + Log.e("REPEAT-REQUEST", action + requestKey + " Method @" + request.method() + " --- " + " URL = " + request.url().encodedPath() + "\t" + bodyToString(request)); + } else { + Log.e("REPEAT-REQUEST", action + requestKey + " Method @" + request.method()); + } + } + + private static String bodyToString(final Request request) { + try { + final Request copy = request.newBuilder().build(); + final Buffer buffer = new Buffer(); + copy.body().writeTo(buffer); + if (buffer.size() > 4096) { + return "-too long"; + } + return buffer.readUtf8(); + } catch (Exception e) { + return "-"; + } + } + + private static String requestBodyToString(RequestBody body) { + try { + final Buffer buffer = new Buffer(); + body.writeTo(buffer); + if (buffer.size() > 4096) { + return "-too long"; + } + return buffer.readUtf8(); + } catch (Exception e) { + return "-"; + } + } +} diff --git a/app/src/main/java/com/ttstd/elderlyassistant/utils/BitmapUtils.java b/app/src/main/java/com/ttstd/elderlyassistant/utils/BitmapUtils.java new file mode 100644 index 0000000..a778cdf --- /dev/null +++ b/app/src/main/java/com/ttstd/elderlyassistant/utils/BitmapUtils.java @@ -0,0 +1,34 @@ +package com.ttstd.elderlyassistant.utils; + +import android.content.Context; +import android.graphics.Bitmap; +import android.os.Environment; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +public class BitmapUtils { + + // 创建图片文件(自动处理目录) + public static File createImageFile(Context context, String fileName) { + // 优先使用外部存储的 Pictures 目录 + File storageDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES); + return new File(storageDir, fileName); + } + + // 保存 Bitmap 到文件 + public static boolean saveBitmapToFile(Bitmap bitmap, File file) { + try (FileOutputStream fos = new FileOutputStream(file)) { + // 根据文件名后缀选择格式(PNG 无损,JPEG 有损) + Bitmap.CompressFormat format = file.getName().endsWith(".png") ? + Bitmap.CompressFormat.PNG : Bitmap.CompressFormat.JPEG; + bitmap.compress(format, 100, fos); // 质量 100 表示最高质量 + fos.flush(); + return true; + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + +} diff --git a/app/src/main/java/com/ttstd/elderlyassistant/utils/FileUtils.java b/app/src/main/java/com/ttstd/elderlyassistant/utils/FileUtils.java new file mode 100644 index 0000000..486767d --- /dev/null +++ b/app/src/main/java/com/ttstd/elderlyassistant/utils/FileUtils.java @@ -0,0 +1,76 @@ +package com.ttstd.elderlyassistant.utils; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.provider.MediaStore; +import android.provider.OpenableColumns; +import android.util.Log; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class FileUtils { + + public static File uriToFile(Context context, Uri uri) throws IOException { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + // Android 10 以下用 MediaStore 查询 + File file = uriToFileLegacy(context, uri); + if (file != null) return file; + } + // Android 10+ 或查询失败时使用沙盒复制 + return uriToFileModern(context, uri); + } + + public static File uriToFileLegacy(Context context, Uri uri) { + String[] projection = {MediaStore.Images.Media.DATA}; + try (Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null)) { + if (cursor != null && cursor.moveToFirst()) { + int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); + String filePath = cursor.getString(columnIndex); + return new File(filePath); + } + } catch (Exception e) { + Log.e("URI_TO_FILE", "Query failed: " + e.getMessage()); + } + return null; + } + + public static File uriToFileModern(Context context, Uri uri) throws IOException { + ContentResolver resolver = context.getContentResolver(); + String fileName = getFileName(resolver, uri); // 获取文件名(见下文辅助方法) + File cacheDir = context.getExternalCacheDir(); // 建议使用缓存目录 + File outputFile = new File(cacheDir, fileName); + + try (InputStream is = resolver.openInputStream(uri); + OutputStream os = new FileOutputStream(outputFile)) { + byte[] buffer = new byte[1024]; + int len; + while ((len = is.read(buffer)) != -1) { + os.write(buffer, 0, len); + } + } + return outputFile; + } + + // 获取文件名辅助方法 + private static String getFileName(ContentResolver resolver, Uri uri) { + String name = null; + if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) { + try (Cursor cursor = resolver.query(uri, null, null, null, null)) { + if (cursor != null && cursor.moveToFirst()) { + name = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); + } + } + } + if (name == null) { + name = uri.getLastPathSegment(); // 备用方案 + } + return name; + } +} diff --git a/app/src/main/res/drawable/ic_default_avatar.xml b/app/src/main/res/drawable/ic_default_avatar.xml new file mode 100644 index 0000000..564710c --- /dev/null +++ b/app/src/main/res/drawable/ic_default_avatar.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/layout/activity_write_message.xml b/app/src/main/res/layout/activity_write_message.xml index e83564e..3a31366 100644 --- a/app/src/main/res/layout/activity_write_message.xml +++ b/app/src/main/res/layout/activity_write_message.xml @@ -17,6 +17,8 @@ + + + + + + + + +