android - JPG图片转换HDR图片,heic格式

android - JPG图片转换HDR图片,heic格式

复制代码
minSdk 28   //以前是24,heifwriter:heifwriter:1.1.0-alpha01 要求最新28
复制代码
<!--读写文件-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
复制代码
android:requestLegacyExternalStorage="true"
复制代码
//HDR图片(heic)
implementation "androidx.heifwriter:heifwriter:1.1.0-alpha01"

//图片选择
implementation 'io.github.lucksiege:pictureselector:v2.7.3-rc08'
复制代码
package com.example.androidkotlindemo2.hdr;

import android.Manifest;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.heifwriter.HeifWriter;

import com.bumptech.glide.Glide;
import com.example.androidkotlindemo2.R;
import com.luck.picture.lib.PictureSelector;
import com.luck.picture.lib.config.PictureConfig;
import com.luck.picture.lib.config.PictureMimeType;
import com.luck.picture.lib.entity.LocalMedia;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * 处理HDR(heic)图片
 * 参考:https://developer.android.com/reference/androidx/heifwriter/HeifWriter
 */
public class HDRMainActivity extends AppCompatActivity {

    private ImageView mImg1;
    private TextView mTv1;
    private Button mBtn1;
    private ImageView mImg2;
    private TextView mTv2;
    private Button mBtn2;
    private LocalMedia imgUrl = null;

    private Context context = this;

    private ImageView mImg3;
    private TextView mTv3;
    private Button mBtn3;
    private String destination1;

    private static final int PERMISSION_REQUEST_CODE = 100;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.hdr_main);
        mImg1 = findViewById(R.id.img1);
        mTv1 = findViewById(R.id.tv1);
        mBtn1 = findViewById(R.id.btn1);
        mImg2 = findViewById(R.id.img2);
        mTv2 = findViewById(R.id.tv2);
        mBtn2 = findViewById(R.id.btn2);
        mImg3 = findViewById(R.id.img3);
        mTv3 = findViewById(R.id.tv3);
        mBtn3 = findViewById(R.id.btn3);

        findViewById(R.id.btn11).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 检查并申请权限
                checkAndRequestPermissions();
            }
        });

        mBtn1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //默认有权限
                checkPic();
            }
        });
        mBtn2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                jpg2Heif();
            }
        });
        mBtn3.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                heif2Jpg();
            }
        });

    }

    private void checkAndRequestPermissions() {
        // 需要申请的权限列表
        String[] permissions = {
                Manifest.permission.READ_EXTERNAL_STORAGE,
                Manifest.permission.WRITE_EXTERNAL_STORAGE
        };

        List<String> permissionsToRequest = new ArrayList<>();

        // 检查每个权限是否已授予
        for (String permission : permissions) {
            if (ContextCompat.checkSelfPermission(this, permission)
                    != PackageManager.PERMISSION_GRANTED) {
                permissionsToRequest.add(permission);
            }
        }

        // 如果有权限需要申请
        if (!permissionsToRequest.isEmpty()) {
            ActivityCompat.requestPermissions(this,
                    permissionsToRequest.toArray(new String[0]),
                    PERMISSION_REQUEST_CODE);
        } else {
            // 所有权限都已授予
            onPermissionsGranted();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        if (requestCode == PERMISSION_REQUEST_CODE) {
            boolean allGranted = true;

            // 检查所有权限是否都被授予
            for (int grantResult : grantResults) {
                if (grantResult != PackageManager.PERMISSION_GRANTED) {
                    allGranted = false;
                    break;
                }
            }

            if (allGranted) {
                onPermissionsGranted();
            } else {
                onPermissionsDenied();
            }
        }
    }

    private void onPermissionsGranted() {
        // 权限已授予,执行相关操作
        Toast.makeText(this, "存储权限已授予", Toast.LENGTH_SHORT).show();
        // 这里可以调用需要权限的方法
        //accessExternalStorage();
    }

    private void onPermissionsDenied() {
        // 处理权限被拒绝的情况
        if (shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE) ||
                shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {

            // 用户拒绝了权限,但没有选择"不再询问"
            showPermissionRationaleDialog();
        } else {
            // 用户拒绝了权限并选择了"不再询问"
            showGoToSettingsDialog();
        }
    }

    private void showPermissionRationaleDialog() {
        new AlertDialog.Builder(this)
                .setTitle("需要存储权限")
                .setMessage("应用需要存储权限来读取和保存文件")
                .setPositiveButton("确定", (dialog, which) -> {
                    // 再次请求权限
                    checkAndRequestPermissions();
                })
                .setNegativeButton("取消", null)
                .show();
    }

    private void showGoToSettingsDialog() {
        new AlertDialog.Builder(this)
                .setTitle("权限被永久拒绝")
                .setMessage("您已永久拒绝存储权限,请在设置中手动授予权限")
                .setPositiveButton("去设置", (dialog, which) -> {
                    // 跳转到应用设置页面
                    Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                    Uri uri = Uri.fromParts("package", getPackageName(), null);
                    intent.setData(uri);
                    startActivity(intent);
                })
                .setNegativeButton("取消", null)
                .show();
    }

    private void heif2Jpg() {
        // Using HEIFWriter from Google
        // https://developer.android.com/reference/androidx/heifwriter/HeifWriter

       /* val heif = HEIF()
        // Step 1: Load file
        heif.load("HEIC.heic")
        // Step 2: Check type of HEIF format, cause it have many types: Still Image, Grid Image ... (Apple is using GridImage type for that format)
        when (heif.primaryImage) {
            is GridImageItem -> {}
            is IdentityImageItem -> {}
            is OverlayImageItem -> {}
            is HEVCImageItem -> {}
            is AVCImageItem -> {}
        }

        // Step 3: In-case this is GridImageItem
        // Get size width, height
        val originalWidth = primaryImage.size.width
        val originalHeight = primaryImage.size.height
        // Getting original rotation degree of Original Image file
        val rotationDegree =
                (heif.itemProperties.findLast { it is RotateProperty } as? RotateProperty)?.rotation?.value
                ?: 0
        // Apple is using 48 tiles and join to 1 images, parse of its and then convert its to HEVC bitstream by FFMPEG
        for (rowIndex in 0 until primaryImage.rowCount) {
            for (columnIndex in 0 until primaryImage.columnCount) {
                // Getting the tile image based column / row
                val hevcImageItem = primaryImage.getImage(columnIndex, rowIndex) as HEVCImageItem
                // Getting decoder config and then using mobile-ffmpeg to write to HEVC bitstream in local storage.
             ....
            }

            // After getting 48 tiles as bitstream files, join its and merge into 1 files by ffmpeg.
           ....
            // Loading Color profiles and attach it.
           ....
            // Convert BitStream to JPG.JPEG.PNG file by FFMPEG
           ....
            // Loading Exifdata and attach it.
               ....
            // Delete temp files and finish convert.
        }*/

    }

    private void jpg2Heif() {
        Bitmap bitmap;
        bitmap = BitmapFactory.decodeFile(imgUrl.getRealPath());
        Log.e("cxx", "获取bitmap成功");
        int imageHeight = bitmap.getHeight();
        int imageWidth = bitmap.getWidth();
        Log.e("cxx", "获取宽高");
//        destination1 = getExternalFilesDir("/").getAbsolutePath() + "/photo2.heic";
        Random random = new Random();
        int fileName2 = Integer.valueOf(random.nextInt(Integer.MAX_VALUE));
        String destinations = Environment.getExternalStorageDirectory() + "/Pictures/"+fileName2+".heic";
        Log.e("cxx", "生成路径:" + destinations);
        try {
            HeifWriter heifWriter = new HeifWriter.Builder(destinations, imageWidth, imageHeight, HeifWriter.INPUT_MODE_BITMAP).setQuality(90).build();
            heifWriter.start();
            heifWriter.addBitmap(bitmap);
            heifWriter.stop(0);
            heifWriter.close();
            Log.e("cxx", "生成完毕:");
            long mbSize = getFileSize(destinations);
            mTv2.setText(destinations+"--大小:"+mbSize);
            Glide.with(context).load(destinations).into(mImg2);
            Log.e("cxx", "加载完毕");

            File f = new File(destinations);
            // 通知图库更新
            Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
            Uri uri = Uri.fromFile(f);
            intent.setData(uri);
            context.sendBroadcast(intent);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    

    private void checkPic() {
        //maxSelectNum()选择图片的数量
        PictureSelector.create(this)
                .openGallery(PictureMimeType.ofImage())
                .imageEngine(GlideEngine.createGlideEngine())
                .maxSelectNum(1)
                .forResult(PictureConfig.CHOOSE_REQUEST);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK) {
            switch (requestCode) {
                case PictureConfig.CHOOSE_REQUEST: {
                    List<LocalMedia> result = PictureSelector.obtainMultipleResult(data);
                    if (result.size() <= 0) {
                        return;
                    }
                    imgUrl = result.get(0);
                    long mbSize = getFileSize(imgUrl.getRealPath());
                    mTv1.setText(imgUrl.getFileName() + "--大小:" + mbSize+"mb");
                    Glide.with(this).load(result.get(0).getPath()).into(mImg1);
                    
                }
            }
        }
    }

    private long getFileSize(String realPath) {
        File file = new File(realPath);
        long size = 0;
        long mbSize = 0;
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(file);
            size = fis.available();
            mbSize = size/1024/1024;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return  mbSize;
    }
    
}
复制代码
package com.example.androidkotlindemo2.hdr;

import android.app.Activity;
import android.content.Context;
import android.content.ContextWrapper;

public class ImageLoaderUtils {

    public static boolean assertValidRequest(Context context) {
        if (context instanceof Activity) {
            Activity activity = (Activity) context;
            return !isDestroy(activity);
        } else if (context instanceof ContextWrapper){
            ContextWrapper contextWrapper = (ContextWrapper) context;
            if (contextWrapper.getBaseContext() instanceof Activity){
                Activity activity = (Activity) contextWrapper.getBaseContext();
                return !isDestroy(activity);
            }
        }
        return true;
    }

    private static boolean isDestroy(Activity activity) {
        if (activity == null) {
            return true;
        }
        return activity.isFinishing() || activity.isDestroyed();
    }

}
复制代码
package com.example.androidkotlindemo2.hdr;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.PointF;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.widget.ImageView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;

import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.BitmapImageViewTarget;
import com.bumptech.glide.request.target.ImageViewTarget;

import com.example.androidkotlindemo2.R;
import com.luck.picture.lib.engine.ImageEngine;
import com.luck.picture.lib.listener.OnImageCompleteCallback;
import com.luck.picture.lib.tools.MediaUtils;
import com.luck.picture.lib.widget.longimage.ImageSource;
import com.luck.picture.lib.widget.longimage.ImageViewState;
import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView;

public class GlideEngine implements ImageEngine {

    /**
     * 加载图片
     *
     * @param context
     * @param url
     * @param imageView
     */
    @Override
    public void loadImage(@NonNull Context context, @NonNull String url, @NonNull ImageView imageView) {
        if (!ImageLoaderUtils.assertValidRequest(context)) {
            return;
        }
        Glide.with(context)
                .load(url)
                .into(imageView);
    }

    /**
     * 加载网络图片适配长图方案
     * # 注意:此方法只有加载网络图片才会回调
     *
     * @param context
     * @param url
     * @param imageView
     * @param longImageView
     * @param callback      网络图片加载回调监听 {link after version 2.5.1 Please use the #OnImageCompleteCallback#}
     */
    @Override
    public void loadImage(@NonNull Context context, @NonNull String url,
                          @NonNull ImageView imageView,
                          SubsamplingScaleImageView longImageView, OnImageCompleteCallback callback) {
        if (!ImageLoaderUtils.assertValidRequest(context)) {
            return;
        }
        Glide.with(context)
                .asBitmap()
                .load(url)
                .into(new ImageViewTarget<Bitmap>(imageView) {
                    @Override
                    public void onLoadStarted(@Nullable Drawable placeholder) {
                        super.onLoadStarted(placeholder);
                        if (callback != null) {
                            callback.onShowLoading();
                        }
                    }

                    @Override
                    public void onLoadFailed(@Nullable Drawable errorDrawable) {
                        super.onLoadFailed(errorDrawable);
                        if (callback != null) {
                            callback.onHideLoading();
                        }
                    }

                    @Override
                    protected void setResource(@Nullable Bitmap resource) {
                        if (callback != null) {
                            callback.onHideLoading();
                        }
                        if (resource != null) {
                            boolean eqLongImage = MediaUtils.isLongImg(resource.getWidth(),
                                    resource.getHeight());
                            longImageView.setVisibility(eqLongImage ? View.VISIBLE : View.GONE);
                            imageView.setVisibility(eqLongImage ? View.GONE : View.VISIBLE);
                            if (eqLongImage) {
                                // 加载长图
                                longImageView.setQuickScaleEnabled(true);
                                longImageView.setZoomEnabled(true);
                                longImageView.setDoubleTapZoomDuration(100);
                                longImageView.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_CROP);
                                longImageView.setDoubleTapZoomDpi(SubsamplingScaleImageView.ZOOM_FOCUS_CENTER);
                                longImageView.setImage(ImageSource.cachedBitmap(resource),
                                        new ImageViewState(0, new PointF(0, 0), 0));
                            } else {
                                // 普通图片
                                imageView.setImageBitmap(resource);
                            }
                        }
                    }
                });
    }

    /**
     * 加载网络图片适配长图方案
     * # 注意:此方法只有加载网络图片才会回调
     *
     * @param context
     * @param url
     * @param imageView
     * @param longImageView
     * @ 已废弃
     */
    @Override
    public void loadImage(@NonNull Context context, @NonNull String url,
                          @NonNull ImageView imageView,
                          SubsamplingScaleImageView longImageView) {
        if (!ImageLoaderUtils.assertValidRequest(context)) {
            return;
        }
        Glide.with(context)
                .asBitmap()
                .load(url)
                .into(new ImageViewTarget<Bitmap>(imageView) {
                    @Override
                    protected void setResource(@Nullable Bitmap resource) {
                        if (resource != null) {
                            boolean eqLongImage = MediaUtils.isLongImg(resource.getWidth(),
                                    resource.getHeight());
                            longImageView.setVisibility(eqLongImage ? View.VISIBLE : View.GONE);
                            imageView.setVisibility(eqLongImage ? View.GONE : View.VISIBLE);
                            if (eqLongImage) {
                                // 加载长图
                                longImageView.setQuickScaleEnabled(true);
                                longImageView.setZoomEnabled(true);
                                longImageView.setDoubleTapZoomDuration(100);
                                longImageView.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_CROP);
                                longImageView.setDoubleTapZoomDpi(SubsamplingScaleImageView.ZOOM_FOCUS_CENTER);
                                longImageView.setImage(ImageSource.cachedBitmap(resource),
                                        new ImageViewState(0, new PointF(0, 0), 0));
                            } else {
                                // 普通图片
                                imageView.setImageBitmap(resource);
                            }
                        }
                    }
                });
    }

    /**
     * 加载相册目录
     *
     * @param context   上下文
     * @param url       图片路径
     * @param imageView 承载图片ImageView
     */
    @Override
    public void loadFolderImage(@NonNull Context context, @NonNull String url, @NonNull ImageView imageView) {
        if (!ImageLoaderUtils.assertValidRequest(context)) {
            return;
        }
        Glide.with(context)
                .asBitmap()
                .load(url)
                .override(180, 180)
                .centerCrop()
                .sizeMultiplier(0.5f)
                .placeholder(R.drawable.ic_launcher_background)
                .into(new BitmapImageViewTarget(imageView) {
                    @Override
                    protected void setResource(Bitmap resource) {
                        RoundedBitmapDrawable circularBitmapDrawable =
                                RoundedBitmapDrawableFactory.
                                        create(context.getResources(), resource);
                        circularBitmapDrawable.setCornerRadius(8);
                        imageView.setImageDrawable(circularBitmapDrawable);
                    }
                });
    }


    /**
     * 加载gif
     *
     * @param context   上下文
     * @param url       图片路径
     * @param imageView 承载图片ImageView
     */
    @Override
    public void loadAsGifImage(@NonNull Context context, @NonNull String url,
                               @NonNull ImageView imageView) {
        if (!ImageLoaderUtils.assertValidRequest(context)) {
            return;
        }
        Glide.with(context)
                .asGif()
                .load(url)
                .into(imageView);
    }

    /**
     * 加载图片列表图片
     *
     * @param context   上下文
     * @param url       图片路径
     * @param imageView 承载图片ImageView
     */
    @Override
    public void loadGridImage(@NonNull Context context, @NonNull String url, @NonNull ImageView imageView) {
        if (!ImageLoaderUtils.assertValidRequest(context)) {
            return;
        }
        Glide.with(context)
                .load(url)
                .override(200, 200)
                .centerCrop()
                .placeholder(R.drawable.ic_launcher_background)
                .into(imageView);
    }


    private GlideEngine() {
    }

    private static GlideEngine instance;

    public static GlideEngine createGlideEngine() {
        if (null == instance) {
            synchronized (GlideEngine.class) {
                if (null == instance) {
                    instance = new GlideEngine();
                }
            }
        }
        return instance;
    }
}
复制代码
hdr_main.xml布局
复制代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
        <ImageView
            android:layout_width="120dp"
            android:layout_height="120dp"
            android:layout_gravity="center_horizontal"
            android:id="@+id/img1"
            android:layout_marginTop="20dp"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:text="名字:无"
            android:layout_marginTop="20dp"
            android:id="@+id/tv1"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="选择照片"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="20dp"
            android:id="@+id/btn1"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="选择照片- 先授权"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="20dp"
            android:textColor="@color/red"
            android:textSize="22sp"
            android:id="@+id/btn11"/>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
        <ImageView
            android:layout_width="120dp"
            android:layout_height="120dp"
            android:layout_gravity="center_horizontal"
            android:id="@+id/img2"
            android:layout_marginTop="20dp"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:text="名字:无"
            android:layout_marginTop="20dp"
            android:id="@+id/tv2"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="转换格式成heif - heic - HDR图片 "
            android:textAllCaps="false"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="20dp"
            android:id="@+id/btn2"/>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
        <ImageView
            android:layout_width="120dp"
            android:layout_height="120dp"
            android:layout_gravity="center_horizontal"
            android:id="@+id/img3"
            android:layout_marginTop="20dp"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:text="名字:无"
            android:layout_marginTop="20dp"
            android:id="@+id/tv3"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="heif转jpg"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="20dp"
            android:id="@+id/btn3"/>
    </LinearLayout>

</LinearLayout>
</androidx.core.widget.NestedScrollView>
相关推荐
诸神黄昏EX11 小时前
Android Build系列专题【篇四:编译相关语法】
android
雨白14 小时前
优雅地处理协程:取消机制深度剖析
android·kotlin
leon_zeng014 小时前
更改 Android 应用 ID (ApplicationId) 后遭遇记
android·发布
2501_9160074715 小时前
iOS 混淆工具链实战,多工具组合完成 IPA 混淆与加固(iOS混淆|IPA加固|无源码混淆|App 防反编译)
android·ios·小程序·https·uni-app·iphone·webview
Jeled17 小时前
Retrofit 与 OkHttp 全面解析与实战使用(含封装示例)
android·okhttp·android studio·retrofit
ii_best20 小时前
IOS/ 安卓开发工具按键精灵Sys.GetAppList 函数使用指南:轻松获取设备已安装 APP 列表
android·开发语言·ios·编辑器
2501_9159090620 小时前
iOS 26 文件管理实战,多工具组合下的 App 数据访问与系统日志调试方案
android·ios·小程序·https·uni-app·iphone·webview
limingade21 小时前
手机转SIP-手机做中继网关-落地线路对接软交换呼叫中心
android·智能手机·手机转sip·手机做sip中继网关·sip中继
RainbowC021 小时前
GapBuffer高效标记管理算法
android·算法