feat:完成文件上传SDK集成(腾讯Cos),移除七牛云文件存储SDK

feat:内置的图片选择框架(Matisse),增加返回图片宽高信息,以及Android33权限适配
fix:修复部分场景Android33权限适配问题
This commit is contained in:
Max
2024-01-16 19:06:46 +08:00
parent 9bd8136f92
commit 1333250224
29 changed files with 941 additions and 1062 deletions

View File

@@ -20,7 +20,6 @@ import com.bumptech.glide.request.target.ViewTarget;
import com.bytedance.hume.readapk.HumeSDK;
import com.chuhai.utils.MetaDataUtils;
import com.coorchice.library.utils.LogUtils;
import com.facebook.stetho.Stetho;
import com.hjq.toast.ToastUtils;
import com.mob.MobSDK;
import com.mob.moblink.MobLink;
@@ -256,7 +255,6 @@ public class XChatApplication extends BaseApp {
ViewTarget.setTagId(R.id.tag_glide);
init(channel);
initStetho(context);
//生命周期监听
if (lifeManager == null) {
@@ -551,18 +549,4 @@ public class XChatApplication extends BaseApp {
}
GlobalHandleManager.get().unInit();
}
/**
* 初始化Stetho网络调试
*/
private static void initStetho(Context context) {
if (Env.isDebug()) {
Stetho.initialize(
Stetho.newInitializerBuilder(context)
.enableDumpapp(Stetho.defaultDumperPluginsProvider(context))
.enableWebKitInspector(Stetho.defaultInspectorModulesProvider(context))
.build()
);
}
}
}

View File

@@ -4,6 +4,7 @@ import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.Build
import android.view.View
import com.netease.nim.uikit.StatusBarUtil
import com.nnbc123.app.R
@@ -87,8 +88,7 @@ class AddSkillActivity : BaseBindingActivity<ActivitySkillEditBinding>() {
.append("\n2.").append(ResUtil.getString(R.string.permission_denied_tips_image))
.toString()
val params = arrayOf(
Manifest.permission.RECORD_AUDIO,
Manifest.permission.WRITE_EXTERNAL_STORAGE
Manifest.permission.RECORD_AUDIO
)
if (RequestPermissionPromptDialog.isNeedPrompt() && !PermissionHelper.isAllGranted(
rxPermissions,

View File

@@ -6,6 +6,8 @@ import android.content.pm.PackageManager;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.os.Build;
import android.text.TextUtils;
import com.nnbc123.app.takephoto.app.TakePhoto;
@@ -23,7 +25,7 @@ import java.util.ArrayList;
*/
public class PermissionManager {
public enum TPermission {
STORAGE(Manifest.permission.WRITE_EXTERNAL_STORAGE),
STORAGE((Build.VERSION.SDK_INT >= 33) ? Manifest.permission.READ_MEDIA_IMAGES : Manifest.permission.WRITE_EXTERNAL_STORAGE),
CAMERA(Manifest.permission.CAMERA);
String stringValue;

View File

@@ -38,6 +38,8 @@ public class CustomItem implements Parcelable, Serializable {
*/
private int fileType;
private String format;
private int width;
private int height;
public CustomItem(String path, int fileType) {
this(path, fileType, "jpeg");
@@ -50,12 +52,16 @@ public class CustomItem implements Parcelable, Serializable {
this.path = path;
this.fileType = fileType;
this.format = format;
this.width = 0;
this.height = 0;
}
protected CustomItem(Parcel in) {
this.path = in.readString();
this.fileType = in.readInt();
this.format = in.readString();
this.width = in.readInt();
this.height = in.readInt();
}
public boolean isImage() {
@@ -104,6 +110,22 @@ public class CustomItem implements Parcelable, Serializable {
this.format = format;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
@Override
public int describeContents() {
return 0;
@@ -114,5 +136,7 @@ public class CustomItem implements Parcelable, Serializable {
dest.writeString(this.path);
dest.writeInt(this.fileType);
dest.writeString(this.format);
dest.writeInt(this.width);
dest.writeInt(this.height);
}
}

View File

@@ -22,6 +22,7 @@ import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.provider.MediaStore;
import androidx.annotation.Nullable;
import com.zhihu.matisse.MimeType;
@@ -46,8 +47,10 @@ public class Item implements Parcelable {
public final Uri uri;
public final long size;
public final long duration; // only for video, in ms
public final int width;
public final int height;
private Item(long id, String mimeType, long size, long duration) {
private Item(long id, String mimeType, long size, long duration,int width,int height) {
this.id = id;
this.mimeType = mimeType;
Uri contentUri;
@@ -62,14 +65,18 @@ public class Item implements Parcelable {
this.uri = ContentUris.withAppendedId(contentUri, id);
this.size = size;
this.duration = duration;
this.width = width;
this.height = height;
}
public Item(String mimeType, Uri uri, long size, long duration) {
public Item(String mimeType, Uri uri, long size, long duration, int width, int height) {
this.id = 1;
this.mimeType = mimeType;
this.uri = uri;
this.size = size;
this.duration = duration;
this.width = width;
this.height = height;
}
private Item(Parcel source) {
@@ -78,13 +85,44 @@ public class Item implements Parcelable {
uri = source.readParcelable(Uri.class.getClassLoader());
size = source.readLong();
duration = source.readLong();
width = source.readInt();
height = source.readInt();
}
public static Item valueOf(Cursor cursor) {
return new Item(cursor.getLong(cursor.getColumnIndex(MediaStore.Files.FileColumns._ID)),
cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE)),
cursor.getLong(cursor.getColumnIndex(MediaStore.MediaColumns.SIZE)),
cursor.getLong(cursor.getColumnIndex("duration")));
return new Item(getCursorLong(cursor, MediaStore.Files.FileColumns._ID, 0),
getCursorString(cursor, MediaStore.MediaColumns.MIME_TYPE, ""),
getCursorLong(cursor, MediaStore.MediaColumns.SIZE, 0),
getCursorLong(cursor, "duration", 0),
getCursorInt(cursor, MediaStore.MediaColumns.WIDTH, 0),
getCursorInt(cursor, MediaStore.MediaColumns.HEIGHT, 0));
}
private static long getCursorLong(Cursor cursor, String key, long defaultValue) {
int index = cursor.getColumnIndex(key);
long value = defaultValue;
if (index >= 0) {
value = cursor.getLong(index);
}
return value;
}
private static String getCursorString(Cursor cursor, String key, String defaultValue) {
int index = cursor.getColumnIndex(key);
String value = defaultValue;
if (index >= 0) {
value = cursor.getString(index);
}
return value;
}
private static int getCursorInt(Cursor cursor, String key, int defaultValue) {
int index = cursor.getColumnIndex(key);
int value = defaultValue;
if (index >= 0) {
value = cursor.getInt(index);
}
return value;
}
@Override
@@ -99,6 +137,8 @@ public class Item implements Parcelable {
dest.writeParcelable(uri, 0);
dest.writeLong(size);
dest.writeLong(duration);
dest.writeInt(width);
dest.writeInt(height);
}
public Uri getContentUri() {
@@ -134,7 +174,9 @@ public class Item implements Parcelable {
&& (uri != null && uri.equals(other.uri)
|| (uri == null && other.uri == null))
&& size == other.size
&& duration == other.duration;
&& duration == other.duration
&& width == other.width
&& height == other.height;
}
@Override
@@ -147,6 +189,8 @@ public class Item implements Parcelable {
result = 31 * result + uri.hashCode();
result = 31 * result + Long.valueOf(size).hashCode();
result = 31 * result + Long.valueOf(duration).hashCode();
result = 31 * result + Integer.valueOf(width).hashCode();
result = 31 * result + Integer.valueOf(height).hashCode();
return result;
}
}

View File

@@ -39,6 +39,8 @@ public class AlbumMediaLoader extends CursorLoader {
MediaStore.MediaColumns.DISPLAY_NAME,
MediaStore.MediaColumns.MIME_TYPE,
MediaStore.MediaColumns.SIZE,
MediaStore.MediaColumns.WIDTH,
MediaStore.MediaColumns.HEIGHT,
"duration"};
// === params for album ALL && showSingleMediaType: false ===
@@ -143,7 +145,7 @@ public class AlbumMediaLoader extends CursorLoader {
return result;
}
MatrixCursor dummy = new MatrixCursor(PROJECTION);
dummy.addRow(new Object[]{Item.ITEM_ID_CAPTURE, Item.ITEM_DISPLAY_NAME_CAPTURE, "", 0, 0});
dummy.addRow(new Object[]{Item.ITEM_ID_CAPTURE, Item.ITEM_DISPLAY_NAME_CAPTURE, "", 0, 0, 0, 0});
return new MergeCursor(new Cursor[]{dummy, result});
}

View File

@@ -165,6 +165,9 @@ public class SelectedItemCollection {
CustomItem customItem = new CustomItem();
customItem.setPath(PathUtils.getPath(mContext, item.getContentUri()));
customItem.setFileType(item.isGif() ? 1 : 0);
customItem.setWidth(item.width);
customItem.setHeight(item.height);
customItem.setFormat(item.mimeType);
paths.add(customItem);
}
return paths;

View File

@@ -21,6 +21,8 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.TypedArray;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.net.Uri;
@@ -250,6 +252,9 @@ public class MatisseActivity extends AppCompatActivity implements
for (Item item : selected) {
selectedUris.add(item.getContentUri());
CustomItem customItem = new CustomItem();
customItem.setFormat(item.mimeType);
customItem.setWidth(item.width);
customItem.setHeight(item.height);
customItem.setFileType(item.isGif() ? 1 : 0);
customItem.setPath(PathUtils.getPath(this, item.getContentUri()));
selectedPaths.add(customItem);
@@ -280,6 +285,7 @@ public class MatisseActivity extends AppCompatActivity implements
CustomItem customItem = new CustomItem();
customItem.setFileType(0);
customItem.setPath(path);
trySetImageInfo(customItem);
selectedPath.add(customItem);
Intent result = new Intent();
result.putParcelableArrayListExtra(EXTRA_RESULT_SELECTION, selected);
@@ -298,6 +304,9 @@ public class MatisseActivity extends AppCompatActivity implements
ArrayList<CustomItem> selectedPath = new ArrayList<>(1);
Item item = selected.get(0);
CustomItem customItem = new CustomItem();
customItem.setFormat(item.mimeType);
customItem.setWidth(item.width);
customItem.setHeight(item.height);
customItem.setFileType(2);
customItem.setPath(item.uri.toString());
selectedPath.add(customItem);
@@ -309,6 +318,26 @@ public class MatisseActivity extends AppCompatActivity implements
}
}
// 补充图片信息
private void trySetImageInfo(CustomItem customItem) {
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
Bitmap bitmap = BitmapFactory.decodeFile(customItem.getPath(), options);
if (customItem.getWidth() <= 0) {
customItem.setWidth(options.outWidth);
}
if (customItem.getHeight() <= 0) {
customItem.setHeight(options.outHeight);
}
if (customItem.getFormat() == null || customItem.getFormat().length() == 0) {
customItem.setFormat(options.outMimeType);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void updateBottomToolbar() {
int selectedCount = mSelectedCollection.count();
if (selectedCount == 0) {
@@ -521,20 +550,16 @@ public class MatisseActivity extends AppCompatActivity implements
@Override
public void capture() {
// if (mMediaStoreCompat != null) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
if (ContextCompat.checkSelfPermission(this, (Build.VERSION.SDK_INT >= 33) ? Manifest.permission.READ_MEDIA_IMAGES : Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
&& ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED
&& ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
mMediaStoreCompat.dispatchCaptureIntent(this, REQUEST_CODE_CAPTURE);
// startActivityForResult(new Intent(this, CameraActivity.class)
// .putExtra(ConstantValue.KEY_TYPE, mSpec.type), REQUEST_CODE_CAPTURE);
} else {
ActivityCompat.requestPermissions(this, new String[]{
Manifest.permission.WRITE_EXTERNAL_STORAGE,
(Build.VERSION.SDK_INT >= 33) ? Manifest.permission.READ_MEDIA_IMAGES : Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.RECORD_AUDIO,
Manifest.permission.CAMERA}, 10);
}
// }
}
@Override

View File

@@ -4,6 +4,7 @@ import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
@@ -158,7 +159,7 @@ public class BigPhotoActivity extends BaseActivity implements OnFragmentOptionLi
if (item == null) {
return;
}
checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
checkPermission((Build.VERSION.SDK_INT >= 33) ? Manifest.permission.READ_MEDIA_IMAGES : Manifest.permission.WRITE_EXTERNAL_STORAGE)
.compose(bindToLifecycle())
.subscribe(aBoolean -> {
if (aBoolean) {

View File

@@ -48,9 +48,9 @@ public class PublishPresenter extends BaseMvpPresenter<IPublishView> {
private MiniWorldChooseInfo miniWorldChooseInfo = new MiniWorldChooseInfo();
public void publishDy(List<String> list, long worldId, String content, boolean isOriginalImage) {
public void publishDy(List<DynamicMedia> list, long worldId, String content, boolean isOriginalImage) {
publishBody = new PublishBody();
List<String> uploadList = new ArrayList<>(list);
List<DynamicMedia> uploadList = new ArrayList<>(list);
int type = ListUtils.isListEmpty(uploadList) ? 0 : 2;
publishBody.setType(type);
publishBody.setUid(AuthModel.get().getCurrentUid());
@@ -77,7 +77,7 @@ public class PublishPresenter extends BaseMvpPresenter<IPublishView> {
});
}
private Single<String> uploadImage(List<String> imagePaths) {
private Single<String> uploadImage(List<DynamicMedia> imagePaths) {
upload(imagePaths);
return Single.create(emitter ->
mImageUploadSubscribe = Observable.interval(500, TimeUnit.MILLISECONDS)
@@ -91,22 +91,31 @@ public class PublishPresenter extends BaseMvpPresenter<IPublishView> {
}));
}
private void upload(List<String> imagePaths) {
private void upload(List<DynamicMedia> imagePaths) {
if (imagePaths == null || imagePaths.size() == 0) {
return;
}
String file = imagePaths.get(0);
DynamicMedia item = imagePaths.get(0);
String file = item.getLocalFilePath();
Single.create((SingleOnSubscribe<String>) e -> {
long fileLength = JXFileUtils.getFileLength(file);
LogUtil.print("文件大小:" + fileLength);
String compressFile = null;
if (!isOriginalImage && fileLength > ImageUploadConfig.MAX_FILE_SIZE) {
compressFile = JXImageUtils.compressImagePxAndQuality(
JXImageUtils.CompressResult result = JXImageUtils.compressImagePxAndQuality(
file, DirectoryHelper.get().getDynamicDir(),
"dynamic_" + System.currentTimeMillis() + ".jpg",
ImageUploadConfig.EXPECT_MIN_WIDTH,
ImageUploadConfig.EXPECT_COMPRESS_SIZE);
if (result != null) {
compressFile = result.getPath();
if (result.getWidth() > 0 && item.getWidth() != result.getWidth()) {
item.setWidth(result.getWidth());
}
if (result.getHeight() > 0 && item.getHeight() != result.getHeight()) {
item.setHeight(result.getHeight());
}
}
LogUtil.print("压缩后:" + compressFile);
}
if (!TextUtils.isEmpty(compressFile)) {
@@ -118,21 +127,22 @@ public class PublishPresenter extends BaseMvpPresenter<IPublishView> {
}
})
.compose(RxHelper.handleSchedulers())
.flatMap((Function<String, SingleSource<DynamicMedia>>)
path -> FileModel.get().uploadFileReturnImageInfo(path))
.flatMap((Function<String, SingleSource<String>>)
path -> FileModel.get().uploadFile(path))
.compose(bindUntilEvent(PresenterEvent.DESTROY))
.subscribe(new DontWarnObserver<DynamicMedia>() {
.subscribe(new DontWarnObserver<String>() {
@Override
public void acceptThrowable(DynamicMedia media, Throwable throwable) {
super.acceptThrowable(media, throwable);
public void acceptThrowable(String url, Throwable throwable) {
super.acceptThrowable(url, throwable);
if (throwable != null) {
if (mImageUploadSubscribe != null) {
mImageUploadSubscribe.dispose();
}
dealUploadFileError(throwable);
} else {
LogUtil.print("上传成功:", media);
publishBody.addDynamicMedia(media);
LogUtil.print("上传成功:", url);
item.setResUrl(url);
publishBody.addDynamicMedia(item);
imagePaths.remove(0);
upload(imagePaths);
}

View File

@@ -338,7 +338,7 @@ public class PublishActivity extends BaseMvpActivity<IPublishView, PublishPresen
tvPublish.setEnabled(false);
getDialogManager().showProgressDialog(context);
getMvpPresenter().publishDy(
ObjectTypeHelper.customToStringList(uploadList),
ObjectTypeHelper.customToMediaList(uploadList),
getMvpPresenter().getWorldId(), etContent.getText().toString(), isOriginalImage);
}
@@ -443,6 +443,7 @@ public class PublishActivity extends BaseMvpActivity<IPublishView, PublishPresen
public void onPublishFailed(Throwable throwable) {
getDialogManager().dismissDialog();
toast(throwable.getMessage());
updatePublishStatus();
}
@Override

View File

@@ -22,6 +22,22 @@ public class ObjectTypeHelper {
return resultList;
}
public static List<DynamicMedia> customToMediaList(List<CustomItem> paramsList) {
List<DynamicMedia> resultList = new ArrayList<>();
if (paramsList == null) {
return resultList;
}
for (CustomItem item : paramsList) {
DynamicMedia media = new DynamicMedia();
media.setLocalFilePath(item.getPath());
media.setWidth(item.getWidth());
media.setHeight(item.getHeight());
media.setFormat(item.getFormat());
resultList.add(media);
}
return resultList;
}
public static List<CustomItem> stringToCustomList(List<String> paramsList) {
List<CustomItem> resultList = new ArrayList<>();
if (paramsList == null) {
@@ -40,6 +56,8 @@ public class ObjectTypeHelper {
}
for (DynamicMedia media : paramsList) {
CustomItem item = new CustomItem();
item.setWidth(media.getWidth());
item.setHeight(media.getHeight());
item.setPath(media.getResUrl());
item.setFileType(CustomItem.UNKOWN);
if (media.isJpgOrPng()) {

View File

@@ -97,6 +97,7 @@ dependencies {
implementation 'com.google.firebase:firebase-analytics-ktx'
api 'com.android.billingclient:billing:6.0.1'
api 'com.qcloud.cos:cos-android:5.9.23'
}
repositories {
mavenCentral()

View File

@@ -2,21 +2,17 @@ package com.nnbc123.core.file;
import android.text.TextUtils;
import com.netease.nim.uikit.common.util.log.LogUtil;
import com.qiniu.android.common.FixedZone;
import com.qiniu.android.storage.Configuration;
import com.qiniu.android.storage.UploadManager;
import com.chuhai.utils.AppUtils;
import com.chuhai.utils.PathUtils;
import com.nnbc123.core.file.cos.CosClient;
import com.nnbc123.core.file.cos.CosToken;
import com.nnbc123.core.base.BaseModel;
import com.nnbc123.core.bean.response.ServiceResult;
import com.nnbc123.core.community.bean.DynamicMedia;
import com.nnbc123.core.exception.ErrorThrowable;
import com.nnbc123.core.utils.net.RxHelper;
import com.nnbc123.library.net.rxnet.RxNet;
import org.json.JSONObject;
import java.io.File;
import java.util.UUID;
import io.reactivex.Single;
import retrofit2.http.GET;
@@ -27,119 +23,49 @@ public class FileModel extends BaseModel implements IFileModel {
}
private FileModel() {
Configuration config = new Configuration.Builder()
.zone(FixedZone.zone2) // 设置区域不指定会自动选择。指定不同区域的上传域名、备用域名、备用IP。
.build();
uploadManager = new UploadManager(config);
}
public static FileModel get() {
return Helper.INSTANCE;
}
private UploadManager uploadManager;
private final Api api = RxNet.create(Api.class);
private CosToken cosToken;
private Single<CosToken> getCosToken() {
if (cosToken != null && cosToken.isValid()) {
return Single.just(cosToken);
} else {
return api.getCosToken().compose(RxHelper.handleSchedulers())
.compose(RxHelper.handleBeanData()).map(cosToken -> {
FileModel.get().cosToken = cosToken;
return cosToken;
});
}
}
@Override
public Single<String> uploadFile(String path) {
File file;
if (TextUtils.isEmpty(path) || !((file = new File(path)).exists())) {
return Single.error(new ErrorThrowable(path + " 为空或者该文件不存在!"));
}
File finalFile = file;
return api.getUploadToken()
.compose(RxHelper.handleSchedulers())
.compose(RxHelper.handleBeanData())
.flatMap(uploadToken -> Single.create(singleEmitter ->
uploadManager.put(finalFile, uploadToken.getKey(), uploadToken.getToken(),
(key, info, response) -> {
if (info.isOK()) {
try {
String imgUrl = response.getString("path");
singleEmitter.onSuccess(imgUrl);
} catch (Exception e) {
singleEmitter.onError(e);
String outName = UUID.randomUUID().toString() + PathUtils.INSTANCE.getSuffixType(finalFile.getName());
return getCosToken().flatMap(token -> CosClient.INSTANCE.upload(AppUtils.getApp(), finalFile, outName, token).map(cosXmlResult -> cosXmlResult.accessUrl));
}
} else {
singleEmitter.onError(new Throwable(info.error));
}
}, null)));
}
@Override
public Single<DynamicMedia> uploadFileReturnImageInfo(String path) {
return uploadFileReturnImageInfo(path, null);
}
@Override
public Single<DynamicMedia> uploadFileReturnImageInfo(String path, String qiniuName) {
File file;
if (TextUtils.isEmpty(path) || !((file = new File(path)).exists())) {
return Single.error(new ErrorThrowable(path + " 为空或者该文件不存在!"));
}
File finalFile = file;
return api.getUploadToken()
.compose(RxHelper.handleSchedulers())
.compose(RxHelper.handleBeanData())
.flatMap(uploadToken -> Single.create(singleEmitter ->
uploadManager.put(finalFile, uploadToken.getKey(), uploadToken.getToken(),
(key, info, response) -> {
if (info.isOK()) {
try {
LogUtil.print("上传成功");
LogUtil.print(response);
DynamicMedia media = responseToMeia(response);
if (media != null) {
singleEmitter.onSuccess(media);
return;
}
} catch (Exception e) {
e.printStackTrace();
}
singleEmitter.onError(new Throwable("qiniu json error"));
} else {
singleEmitter.onError(new Throwable(info.error));
}
}, null)));
}
@Override
public Single<String> downloadFile(String url) {
return null;
}
private DynamicMedia responseToMeia(JSONObject response) {
DynamicMedia media = new DynamicMedia();
try {
String imgNamePath = response.getString("path");
media.setResUrl(imgNamePath);
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
try {
media.setFormat(response.getString("format"));
media.setWidth(response.getInt("w"));
media.setHeight(response.getInt("h"));
} catch (Exception ex) {
ex.printStackTrace();
}
LogUtil.print("七牛上传", media);
return media;
}
interface Api {
/**
* 获取钱包
*
* @return
*/
@GET("/qiniu/upload/getUploadToken")
Single<ServiceResult<UploadToken>> getUploadToken();
@GET("/tencentCos/getToken")
Single<ServiceResult<CosToken>> getCosToken();
}
}

View File

@@ -23,8 +23,4 @@ public interface IFileModel extends IModel {
*/
Single<String> downloadFile(String url);
Single<DynamicMedia> uploadFileReturnImageInfo(String path);
Single<DynamicMedia> uploadFileReturnImageInfo(String path, String qiniuName);
}

View File

@@ -0,0 +1,113 @@
package com.nnbc123.core.file.cos
import android.content.Context
import android.util.Log
import com.nnbc123.core.Env
import com.tencent.cos.xml.CosXmlService
import com.tencent.cos.xml.CosXmlServiceConfig
import com.tencent.cos.xml.exception.CosXmlClientException
import com.tencent.cos.xml.exception.CosXmlServiceException
import com.tencent.cos.xml.listener.CosXmlResultListener
import com.tencent.cos.xml.model.CosXmlRequest
import com.tencent.cos.xml.model.CosXmlResult
import com.tencent.cos.xml.transfer.COSXMLUploadTask
import com.tencent.cos.xml.transfer.TransferConfig
import com.tencent.cos.xml.transfer.TransferManager
import io.reactivex.Single
import java.io.File
/**
* Created by Max on 2024/1/16 11:09
* Desc:
**/
object CosClient {
private var cosXmlClient: CosXmlService? = null
private var credentialProvider: CosCredentialProvider? = null
private fun getCosXmlClient(context: Context, token: CosToken): CosXmlService {
var client = this.cosXmlClient
if (client != null && credentialProvider != null) {
credentialProvider?.updateCredentials(token.toCredential())
return client
}
// 创建 CosXmlServiceConfig 对象,根据需要修改默认的配置参数
val serviceConfig: CosXmlServiceConfig = CosXmlServiceConfig.Builder()
.setRegion(token.region)
.isHttps(true) // 使用 HTTPS 请求, 默认为 HTTP 请求
.builder()
val credentials = token.toCredential()
credentialProvider = CosCredentialProvider(credentials)
// 初始化 COS Service获取实例
client = CosXmlService(
context,
serviceConfig, credentialProvider
)
cosXmlClient = client
return client
}
/**
* 上传文件
* @param outName 远端文件名
*/
fun upload(
context: Context,
file: File,
outName: String,
token: CosToken
): Single<CosXmlResult> {
if (Env.isDebug()) {
Log.e("CosClient", "upload file:${file.absolutePath} outName:${outName}")
}
return Single.create {
uploadFile(context, file, outName, token).apply {
setCosXmlResultListener(object : CosXmlResultListener {
override fun onSuccess(request: CosXmlRequest?, result: CosXmlResult) {
if (Env.isDebug()) {
Log.e("CosClient", "upload onSuccess result:${result.accessUrl}")
}
it.onSuccess(result)
}
override fun onFail(
request: CosXmlRequest?,
clientException: CosXmlClientException?,
serviceException: CosXmlServiceException?
) {
if (Env.isDebug()) {
Log.e("CosClient", "upload onFail clientException:$clientException")
Log.e("CosClient", "upload onFail serviceException:$serviceException")
}
it.onError(CosException(clientException, serviceException))
}
})
}
}
}
/**
* 上传文件
* @param outName 远端文件名
*/
private fun uploadFile(
context: Context,
file: File,
outName: String,
token: CosToken
): COSXMLUploadTask {
val cosXmlService = getCosXmlClient(context, token)
// 初始化 TransferConfig这里使用默认配置如果需要定制请参考 SDK 接口文档
val transferConfig = TransferConfig.Builder().build()
// 初始化 TransferManager
val transferManager = TransferManager(
cosXmlService,
transferConfig
)
return transferManager.upload(
token.bucket, outName,
file.absolutePath, null
)
}
}

View File

@@ -0,0 +1,20 @@
package com.nnbc123.core.file.cos
import com.tencent.qcloud.core.auth.BasicLifecycleCredentialProvider
import com.tencent.qcloud.core.auth.QCloudLifecycleCredentials
/**
* Created by Max on 2024/1/16 11:28
* Desc:
**/
class CosCredentialProvider(private var credentials: QCloudLifecycleCredentials) :
BasicLifecycleCredentialProvider() {
fun updateCredentials(credentials: QCloudLifecycleCredentials) {
this.credentials = credentials
}
override fun fetchNewCredentials(): QCloudLifecycleCredentials {
return credentials
}
}

View File

@@ -0,0 +1,21 @@
package com.nnbc123.core.file.cos
import com.tencent.cos.xml.exception.CosXmlClientException
import com.tencent.cos.xml.exception.CosXmlServiceException
/**
* Created by Max on 2024/1/16 15:49
* Desc:
**/
class CosException : Exception {
var clientException: CosXmlClientException? = null
var serviceException: CosXmlServiceException? = null
constructor(
clientException: CosXmlClientException?,
serviceException: CosXmlServiceException?
) : super(clientException ?: serviceException) {
this.clientException = clientException
this.serviceException = serviceException
}
}

View File

@@ -0,0 +1,43 @@
package com.nnbc123.core.file.cos
import com.nnbc123.core.utils.CurrentTimeUtils
import com.tencent.qcloud.core.auth.QCloudLifecycleCredentials
import com.tencent.qcloud.core.auth.SessionQCloudCredentials
/**
* Created by Max on 2024/1/16 11:44
* Desc:
**/
data class CosToken(
val tmpSecretId: String?,
val tmpSecretKey: String?,
val sessionToken: String?,
val bucket: String?,
val region: String?,
val startTime: Long?,
val expireTime: Long?,
) {
/**
* 是否有效
*/
fun isValid(): Boolean {
if (expireTime == null) {
return false
}
val currentTime = CurrentTimeUtils.getCurrentTime() / 1000
// 预留一点安全时长
var safeTime = 30
if (safeTime >= (expireTime - (startTime ?: 0))) {
safeTime = 0
}
return currentTime <= (expireTime - safeTime)
}
fun toCredential(): QCloudLifecycleCredentials {
return SessionQCloudCredentials(
tmpSecretId ?: "", tmpSecretKey ?: "",
sessionToken ?: "", startTime ?: 0, expireTime ?: 0
)
}
}

View File

@@ -1,10 +0,0 @@
package com.nnbc123.core.utils;
/**
* create by lvzebiao @2019/11/20
*/
public class UploadUtils {
}

View File

@@ -2,6 +2,8 @@ package com.nnbc123.core.community.bean;
import android.text.TextUtils;
import kotlin.jvm.Transient;
/**
* create by lvzebiao @2019/11/21
*/
@@ -21,6 +23,17 @@ public class DynamicMedia {
private int width;
private int height;
@Transient
private String localFilePath;
public String getLocalFilePath() {
return localFilePath;
}
public void setLocalFilePath(String localFilePath) {
this.localFilePath = localFilePath;
}
/**
* 是否是网络图片
* @return -
@@ -38,14 +51,14 @@ public class DynamicMedia {
if (TextUtils.isEmpty(format)) {
return false;
}
return format.toLowerCase().equals("jpeg") || format.toLowerCase().equals("jpg") || format.toLowerCase().equals("png");
return format.toLowerCase().contains("jpeg") || format.toLowerCase().contains("jpg") || format.toLowerCase().contains("png");
}
public boolean isGif() {
if (TextUtils.isEmpty(format)) {
return false;
}
return format.toLowerCase().equals("gif");
return format.toLowerCase().contains("gif");
}
//<editor-fold defaultstate="collapsed" desc="delombok">

View File

@@ -121,8 +121,6 @@ dependencies {
api "com.orhanobut:logger:${loggerVersion}"
api "com.qiniu:qiniu-android-sdk:${qiniu}"
api "org.greenrobot:eventbus:${eventbusVersion}"
api "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
@@ -143,10 +141,6 @@ dependencies {
api 'com.google.android.exoplayer:exoplayer:2.18.1'
// 网络请求chrome数据调试
api 'com.facebook.stetho:stetho:1.5.1'
api 'com.facebook.stetho:stetho-okhttp3:1.5.1'
api 'com.geyifeng.immersionbar:immersionbar:3.2.2'
api files("libs/alicloud-android-utdid-2.6.0.jar")

View File

@@ -1,338 +0,0 @@
/*
* Copyright (C) 2015 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.nnbc123.library.net.rxnet.interceptor;
import static okhttp3.internal.platform.Platform.INFO;
import androidx.annotation.NonNull;
import java.io.EOFException;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import okhttp3.Connection;
import okhttp3.Headers;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.internal.http.HttpHeaders;
import okhttp3.internal.platform.Platform;
import okio.Buffer;
import okio.BufferedSource;
import okio.GzipSource;
/**
* An OkHttp interceptor which logs request and response information. Can be applied as an
* {@linkplain OkHttpClient#interceptors() application interceptor} or as a {@linkplain
* OkHttpClient#networkInterceptors() network interceptor}. <p> The format of the logs created by
* this class should not be considered stable and may change slightly between releases. If you need
* a stable logging format, use your own interceptor.
*/
public final class HttpLoggingInterceptor implements Interceptor {
private static final Charset UTF8 = Charset.forName("UTF-8");
private final Logger logger;
private volatile Set<String> headersToRedact = Collections.emptySet();
private volatile Level level = Level.NONE;
public HttpLoggingInterceptor() {
this(Logger.DEFAULT);
}
public HttpLoggingInterceptor(Logger logger) {
this.logger = logger;
}
/**
* Returns true if the body in question probably contains human readable text. Uses a small sample
* of code points to detect unicode control characters commonly used in binary file signatures.
*/
static boolean isPlaintext(Buffer buffer) {
try {
Buffer prefix = new Buffer();
long byteCount = buffer.size() < 64 ? buffer.size() : 64;
buffer.copyTo(prefix, 0, byteCount);
for (int i = 0; i < 16; i++) {
if (prefix.exhausted()) {
break;
}
int codePoint = prefix.readUtf8CodePoint();
if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
return false;
}
}
return true;
} catch (EOFException e) {
return false; // Truncated UTF-8 sequence.
}
}
private static boolean bodyHasUnknownEncoding(Headers headers) {
String contentEncoding = headers.get("Content-Encoding");
return contentEncoding != null
&& !contentEncoding.equalsIgnoreCase("identity")
&& !contentEncoding.equalsIgnoreCase("gzip");
}
public void redactHeader(String name) {
Set<String> newHeadersToRedact = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
newHeadersToRedact.addAll(headersToRedact);
newHeadersToRedact.add(name);
headersToRedact = newHeadersToRedact;
}
public Level getLevel() {
return level;
}
/**
* Change the level at which this interceptor logs.
*/
public HttpLoggingInterceptor setLevel(Level level) {
if (level == null) throw new NullPointerException("level == null. Use Level.NONE instead.");
this.level = level;
return this;
}
@NonNull
@Override
public Response intercept(Chain chain) throws IOException {
Level level = this.level;
Request request = chain.request();
if (level == Level.NONE) {
return chain.proceed(request);
}
boolean logBody = level == Level.BODY;
boolean logHeaders = logBody || level == Level.HEADERS;
synchronized (this) {
RequestBody requestBody = request.body();
boolean hasRequestBody = requestBody != null;
Connection connection = chain.connection();
String requestStartMessage = "--> "
+ request.method()
+ ' ' + request.url()
+ (connection != null ? " " + connection.protocol() : "");
if (!logHeaders && hasRequestBody) {
requestStartMessage += " (" + requestBody.contentLength() + "-byte body)";
}
logger.log(requestStartMessage);
if (logHeaders) {
if (hasRequestBody) {
// Request body headers are only present when installed as a network interceptor. Force
// them to be included (when available) so there values are known.
if (requestBody.contentType() != null) {
logger.log("Content-Type: " + requestBody.contentType());
}
if (requestBody.contentLength() != -1) {
logger.log("Content-Length: " + requestBody.contentLength());
}
}
Headers headers = request.headers();
for (int i = 0, count = headers.size(); i < count; i++) {
String name = headers.name(i);
// Skip headers from the request body as they are explicitly logged above.
if (!"Content-Type".equalsIgnoreCase(name) && !"Content-Length".equalsIgnoreCase(name)) {
logHeader(headers, i);
}
}
if (!logBody || !hasRequestBody) {
logger.log("--> END " + request.method());
} else if (bodyHasUnknownEncoding(request.headers())) {
logger.log("--> END " + request.method() + " (encoded body omitted)");
} else if (requestBody.isDuplex()) {
logger.log("--> END " + request.method() + " (duplex request body omitted)");
} else {
Buffer buffer = new Buffer();
requestBody.writeTo(buffer);
Charset charset = UTF8;
MediaType contentType = requestBody.contentType();
if (contentType != null) {
charset = contentType.charset(UTF8);
}
logger.log("");
if (isPlaintext(buffer)) {
logger.log(buffer.readString(charset));
logger.log("--> END " + request.method()
+ " (" + requestBody.contentLength() + "-byte body)");
} else {
logger.log("--> END " + request.method() + " (binary "
+ requestBody.contentLength() + "-byte body omitted)");
}
}
}
}
long startNs = System.nanoTime();
Response response;
try {
response = chain.proceed(request);
} catch (Exception e) {
logger.log("<-- HTTP FAILED: " + e);
throw e;
}
synchronized (this) {
long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
ResponseBody responseBody = response.body();
long contentLength = responseBody.contentLength();
String bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length";
logger.log("<-- "
+ response.code()
+ (response.message().isEmpty() ? "" : ' ' + response.message())
+ ' ' + response.request().url()
+ " (" + tookMs + "ms" + (!logHeaders ? ", " + bodySize + " body" : "") + ')');
if (logHeaders) {
Headers headers = response.headers();
for (int i = 0, count = headers.size(); i < count; i++) {
logHeader(headers, i);
}
if (!logBody || !HttpHeaders.hasBody(response)) {
logger.log("<-- END HTTP");
} else if (bodyHasUnknownEncoding(response.headers())) {
logger.log("<-- END HTTP (encoded body omitted)");
} else {
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE); // Buffer the entire body.
Buffer buffer = source.getBuffer();
Long gzippedLength = null;
if ("gzip".equalsIgnoreCase(headers.get("Content-Encoding"))) {
gzippedLength = buffer.size();
try (GzipSource gzippedResponseBody = new GzipSource(buffer.clone())) {
buffer = new Buffer();
buffer.writeAll(gzippedResponseBody);
}
}
Charset charset = UTF8;
MediaType contentType = responseBody.contentType();
if (contentType != null) {
charset = contentType.charset(UTF8);
}
if (!isPlaintext(buffer)) {
logger.log("");
logger.log("<-- END HTTP (binary " + buffer.size() + "-byte body omitted)");
return response;
}
if (contentLength != 0) {
logger.log("");
logger.log(buffer.clone().readString(charset));
}
if (gzippedLength != null) {
logger.log("<-- END HTTP (" + buffer.size() + "-byte, "
+ gzippedLength + "-gzipped-byte body)");
} else {
logger.log("<-- END HTTP (" + buffer.size() + "-byte body)");
}
}
}
}
return response;
}
private void logHeader(Headers headers, int i) {
String value = headersToRedact.contains(headers.name(i)) ? "██" : headers.value(i);
logger.log(headers.name(i) + ": " + value);
}
public enum Level {
/**
* No logs.
*/
NONE,
/**
* Logs request and response lines.
*
* <p>Example:
* <pre>{@code
* --> POST /greeting http/1.1 (3-byte body)
*
* <-- 200 OK (22ms, 6-byte body)
* }</pre>
*/
BASIC,
/**
* Logs request and response lines and their respective headers.
*
* <p>Example:
* <pre>{@code
* --> POST /greeting http/1.1
* Host: example.com
* Content-Type: plain/text
* Content-Length: 3
* --> END POST
*
* <-- 200 OK (22ms)
* Content-Type: plain/text
* Content-Length: 6
* <-- END HTTP
* }</pre>
*/
HEADERS,
/**
* Logs request and response lines and their respective headers and bodies (if present).
*
* <p>Example:
* <pre>{@code
* --> POST /greeting http/1.1
* Host: example.com
* Content-Type: plain/text
* Content-Length: 3
*
* Hi?
* --> END POST
*
* <-- 200 OK (22ms)
* Content-Type: plain/text
* Content-Length: 6
*
* Hello!
* <-- END HTTP
* }</pre>
*/
BODY
}
public interface Logger {
/**
* A {@link Logger} defaults output appropriate for the current platform.
*/
Logger DEFAULT = message -> Platform.get().log(INFO, message, null);
void log(String message);
}
}

View File

@@ -2,13 +2,11 @@ package com.nnbc123.library.net.rxnet.manager;
import android.content.Context;
import com.facebook.stetho.okhttp3.StethoInterceptor;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.nnbc123.library.BuildConfig;
import com.nnbc123.library.net.rxnet.converter.GsonConverterFactory;
import com.nnbc123.library.net.rxnet.https.HttpsUtils;
import com.nnbc123.library.net.rxnet.interceptor.HttpLoggingInterceptor;
import com.nnbc123.library.net.rxnet.utils.RxNetLog;
import java.io.IOException;
@@ -29,6 +27,7 @@ import okhttp3.Cache;
import okhttp3.ConnectionPool;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
@@ -56,16 +55,9 @@ public final class RxNetManager {
mBuilder = new OkHttpClient.Builder();
if (RxNetLog.DEBUG) {
//正式环境千万不要加这玩意,为了方便日志查看,拦截器里面加了synchronized关键字,接口请求是串行的
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
@Override
public void log(String message) {
RxNetLog.d("OKHttp-------%s", message);
}
});
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
mBuilder.addInterceptor(loggingInterceptor);
mBuilder.addNetworkInterceptor(new StethoInterceptor());
}
for (Interceptor interceptor : interceptors) {

View File

@@ -19,6 +19,7 @@ import android.graphics.RectF;
import android.media.ExifInterface;
import android.util.Log;
import com.nnbc123.library.utils.LogUtil;
import com.nnbc123.library.utils.StringUtils;
import com.nnbc123.library.utils.file.JXFileUtils;
@@ -690,8 +691,7 @@ public class JXImageUtils {
return baos.toByteArray();
}
public static String compressImagePxAndQuality(String inPath, File outDir, String outFileName, int expectWidth, long expectSize) {
public static CompressResult compressImagePxAndQuality(String inPath, File outDir, String outFileName, int expectWidth, long expectSize) {
try {
if (outDir == null) {
return null;
@@ -708,7 +708,7 @@ public class JXImageUtils {
ExifInterface exif = new ExifInterface(inPath);
String orientation = exif.getAttribute(ExifInterface.TAG_ORIENTATION);
LogUtil.print("原图:" + inPath);
LogUtil.print("原图旋转角度:" + getPicRotate(inPath));
// LogUtil.print("原图旋转角度:" + getPicRotate(inPath));
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
bmOptions.inPreferredConfig = Config.RGB_565;
@@ -746,13 +746,15 @@ public class JXImageUtils {
fos.close();
if (thumbnailFile.exists()) {
String compressPath = thumbnailFile.getPath();
if (orientation != null) {
exif = new ExifInterface(compressPath);
if (orientation != null) {
exif.setAttribute(ExifInterface.TAG_ORIENTATION, orientation);
exif.saveAttributes();
}
LogUtil.print("压缩图旋转角度:" + getPicRotate(compressPath));
return compressPath;
int width = exif.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, 0);
int height = exif.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, 0);
// LogUtil.print("压缩图旋转角度:" + getPicRotate(compressPath));
return new CompressResult(compressPath, width, height, "image/jpeg");
}
} catch (Exception ex) {
Log.e("mouse_debug", "压缩失败...");
@@ -814,5 +816,33 @@ public class JXImageUtils {
return degree;
}
public static class CompressResult {
private String path;
private int width;
private int height;
private String format;
public CompressResult(String path, int width, int height, String format) {
this.path = path;
this.width = width;
this.height = height;
this.format = format;
}
public String getPath() {
return path;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public String getFormat() {
return format;
}
}
}

View File

@@ -5,8 +5,9 @@ import android.net.Uri;
import android.os.Environment;
import android.text.TextUtils;
import androidx.core.text.TextUtilsCompat;
import com.nnbc123.library.utils.FP;
import com.qiniu.android.utils.StringUtils;
import com.nnbc123.library.common.application.BaseApp;
import com.nnbc123.library.common.util.Logger;
@@ -877,41 +878,4 @@ public class FileHelper {
}
return content;
}
/**
* 文件转换成字符串
*
* @param filePath 文件路径
* @return 字符串内容
*/
public static String getTxtFileContent(String filePath) {
String content = "";
if (!StringUtils.isNullOrEmpty(filePath)) {
File file = new File(filePath);
if (file.isFile()) {
FileInputStream inputStream = null;
try {
inputStream = new FileInputStream(file);
String line;
StringBuilder sb = new StringBuilder();
BufferedReader buffReader = new BufferedReader(new InputStreamReader(inputStream));
while ((line = buffReader.readLine()) != null) {
sb.append(line).append("\n");
}
content = sb.toString();
} catch (Exception e) {
Logger.error(TAG, "getTxtFileContent read fail, e = " + e);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (Exception ignore) {
}
}
}
}
}
return content;
}
}

View File

@@ -45,12 +45,8 @@ import java.io.File
* 路径 工具类 By https://github.com/Blankj/AndroidUtilCode -> PathUtils.java
* Created by Max on 2018/12/12.
*/
class PathUtils private constructor() {
init {
throw UnsupportedOperationException("u can't instantiate me...")
}
object PathUtils {
companion object {
/**
* Return the path of /system.
*
@@ -491,5 +487,33 @@ class PathUtils private constructor() {
pathA + File.separator + pathB
}
}
/**
* 获取后缀名称
* @param path 路径
* @return 后缀格式 .mp4 .gif 等
*/
fun getSuffixType(path: String): String? {
if (path.isEmpty()) {
return null
}
val dotIndex = path.indexOfLast {
'.' == it
}
val separatorIndex = path.indexOfLast {
'/' == it
}
if (dotIndex >= 0 && dotIndex > separatorIndex) {
val suffix = path.substring(dotIndex)
val askIndex = suffix.indexOfLast {
'?' == it
}
return if (askIndex >= 0) {
suffix.substring(0, askIndex)
} else {
suffix
}
}
return null
}
}

View File

@@ -1,25 +0,0 @@
package com.chuhai.utils
import android.os.SystemClock
/**
* Created by Max on 2023/10/24 15:11
* Desc:服务器时间
*/
object ServiceTime {
// 服务器时间与系统开机时间的时差
private var serviceTimeDiff: Long? = null
val time
get() = if (serviceTimeDiff == null) System.currentTimeMillis()
else SystemClock.elapsedRealtime() + serviceTimeDiff!!
/**
* 刷新服务器时间
*/
fun refreshServiceTime(time: Long) {
//serviceTimeDiff = 服务器时间 - 此刻系统启动时间
serviceTimeDiff = time - SystemClock.elapsedRealtime()
}
}

View File

@@ -5,6 +5,7 @@ import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.text.TextUtils;
@@ -589,7 +590,7 @@ public class WatchMessagePictureActivity extends UI {
}
protected String[] getNeedPermissions() {
return new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE};
return new String[]{(Build.VERSION.SDK_INT >= 33) ? Manifest.permission.READ_MEDIA_IMAGES : Manifest.permission.WRITE_EXTERNAL_STORAGE};
}
}