1--安卓多媒体之图片综合篇

零、前言

本篇将涉及: 1.调用系统相机、上传到服务器操作 2.大照片通过采样并压缩尺寸避免OOM 3.media中图片的内容提供者使用方法,增删改查,获取手机所有图片路径 4.显示最近100张照片 5.通过系统图库选择一张加载到界面


实验一:拍一张图片上传到服务器:

1.打开系统系统相机
// 注:Cons.CAMERA_RESULT = 1
 startActivityForResult(new Intent(MediaStore.ACTION_IMAGE_CAPTURE), Cons.CAMERA_RESULT);
2.接收返回的Bitmap
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (resultCode == RESULT_OK) {
        //通过intent获取Bundle中的"data"键的数据(即Bitmap)
        Bitmap bitmap = (Bitmap) data.getExtras().get("data");
        //上传照片逻辑
        doUpload(bitmap);
    }
}
3.基于OKHttp实现上传逻辑

查看源码发现RequestBody.create不止可以接收File,还可以接收字节数组byte[]。思路: 网络权限:<uses-permission android:name="android.permission.INTERNET"/> 1.通过ByteArrayOutputStream拷贝Bitmap的字节 2.通过MultipartBody模拟表单,生成请求对象Request 3.将Request封装为Call对象,并执行Call

/**
 * 模拟表单上传Bitmap:通过MultipartBody
 */
private void doUpload(Bitmap bitmap) {
    //0.准备一个字节数组,准备将Bitmap放入其中
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
    
    //1.获取OkHttpClient对象
    OkHttpClient okHttpClient = new OkHttpClient();
    //2.获取Request对象
    RequestBody fileBody = RequestBody.create(MediaType.parse("application/octet-stream"), baos.toByteArray());
    RequestBody requestBody = new MultipartBody.Builder()
            .setType(MultipartBody.FORM)
            .addFormDataPart("file", "test.jpg", fileBody)
            .build();
    Request request = new Request.Builder()
            .url(Cons.BASE_URL + "upload").post(requestBody).build();
    //3.将Request封装为Call对象,并执行Call
    Call call = okHttpClient.newCall(request);
    call.enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
            Log.e(TAG, "onFailure: " + e);
        }
        @Override
        public void onResponse(Call call, Response response) throws IOException {
            String result = response.body().string();
            Log.e(TAG, "onResponse: " + result);
            runOnUiThread(() -> ToastUtil.showAtOnce(MainActivity.this, result));
        }
    });
}
4.SpringBoot实现服务端代码

客户端完成了,多文件上传的服务端使用SpringBoot很轻松可以实现

/**
 * 多文件上传(包括一个)
 *
 * @param files
 * @return
 */
@PostMapping(value = "/upload")
public @ResponseBody
String uploadImg(@RequestParam("file") List<MultipartFile> files) {
    StringBuilder result = new StringBuilder();
    for (MultipartFile file : files) {
        if (file.isEmpty()) {
            return "false";
        }
        String fileName = file.getOriginalFilename();//获取名字
        String path = "F:/SpringBootFiles/imgs/";
        File dest = new File(path + "/" + fileName);
        if (!dest.getParentFile().exists()) { //判断文件父目录是否存在
            dest.getParentFile().mkdir();
        }
        try {
            file.transferTo(dest); //保存文件
            result.append(fileName + "上传成功!\n");
        } catch (IllegalStateException | IOException e) {
            e.printStackTrace();
            result.append(fileName + "上传失败!\n");
        }
    }
    return result.toString();
}
  • 上传成功:不过仔细一看大小--195*260

手机拍照.png


实验二、Bitmap的采样,加载大图片

内存问题:避免大图片导致的OOM(加载一个9.2M的图片,点两下):

private void bitmapTest() {
    //生成Bitmap对象
    Bitmap bitmap = BitmapFactory.decodeFile(
            Environment.getExternalStorageDirectory() + "/DCIM/Camera/IMG20181018103528.jpg");
    mImageView.setImageBitmap(bitmap);
}

OOM时.png

传说中的OOM.png

1.采样后适量缩小,解决OOM:思路

1)创建Options对象op,对Bitmap做配置 2)只获取Bitmap信息,不分配内存:op.inJustDecodeBounds = true 3)设置压缩率,根据需求自己设置,这里为适应屏幕 4)设置op解析出可显示的Bitmap,使用

private void bitmapTest() {
    String pathName = Environment.getExternalStorageDirectory() + "/DCIM/Camera/IMG20181018103528.jpg";
    //1.创建Options对象op,对Bitmap做配置
    BitmapFactory.Options op = new BitmapFactory.Options();
    //2.只获取Bitmap信息,不分配内存
    op.inJustDecodeBounds = true;
    //以op配置来解析出Bitmap:fakeBitmap由于inJustDecodeBounds=true,无法显示
    Bitmap fakeBitmap = BitmapFactory.decodeFile(pathName, op);
    L.d(op.outWidth + "*" + op.outHeight + L.l());// 6240*8320
    //3.设置压缩率
    //获取屏幕尺寸
    int winH = ScrUtil.getScreenHeight(this);
    int winW = ScrUtil.getScreenWidth(this);
    op.inSampleSize = (int) (op.outHeight > op.outWidth ?
            Math.ceil(op.outHeight / winH) :
            Math.ceil(op.outWidth / winW));
    L.d(op.inSampleSize + L.l());// 4
    op.inJustDecodeBounds = false;
    //4.设置op解析出可显示的Bitmap,使用
    Bitmap realBitmap = BitmapFactory.decodeFile(pathName, op);
    mImageView.setImageBitmap(realBitmap);

加载正常.png


实验三、图片与内容提供者:

media作为手机的三鼎之一,自然是少不了内容提供者来向外界暴漏信息 主要储存在external.db(外部)internal.db(内部)两个数据库中 数据库中图片的主要字段有:

_id:id标识             _data: 图片绝对路径         _size: 图片大小            mime_type: 类型
data_added:添加的时间   data_modifide:最后修改时间  _display_name:显示名称     description:描述
width:宽                height:高

media的内容提供者数据库.png

1.获取内容提供者并添加一条自定义信息的图片
private void insertImg() {
    //1.创建ContentValues对象,记录插入照片信息
    ContentValues values = new ContentValues();
    values.put(MediaStore.Images.Media.DISPLAY_NAME, "张风捷特烈");
    values.put(MediaStore.Images.Media.DESCRIPTION, "天下无双");
    values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
    //2.获取内容提供者,插入(外部图片存储Uri,values),返回插入图片的Uri
    Uri imgFileUri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
    L.d(imgFileUri + L.l());//content://media/external/images/media/1064830
    //3.通过打开图片的意图添加额外信息将imgFileUri发送给系统相机
    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    intent.putExtra(MediaStore.EXTRA_OUTPUT, imgFileUri);
    startActivity(intent);
}
2.通过内容提供者读取刚才插入的图片
 private void readImg() {
     try {
         //1.获取内容提供者,通过刚才的Uri打开输入流
         Uri imgUri = Uri.parse("content://media/external/images/media/1064830");
         InputStream is = getContentResolver().openInputStream(imgUri);
         //2.将图片解码展示
         Bitmap bitmap = BitmapFactory.decodeStream(is);
         mImageView.setImageBitmap(bitmap);
     } catch (FileNotFoundException e) {
         e.printStackTrace();
     }
 }
3.更新描述信息
private void updateImg() {
    ContentValues values = new ContentValues(2);
    values.put(MediaStore.Images.Media.DISPLAY_NAME, "Toly");
    values.put(MediaStore.Images.Media.DESCRIPTION, "Only Me");
    //1.获取内容提供者,通过刚才的Uri打开输入流
    Uri imgUri = Uri.parse("content://media/external/images/media/1064830");
    getContentResolver().update(imgUri, values, null, null);
}
4.查表

既然是内容提供者,玩个表再所难免,借此回顾一下内容提供者的使用 可见上面的修改方法成功

private void queryImg() {
    //1.查询获得游标
    Cursor cursor = getContentResolver().query(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, "_id=1064830",
            null, null, null);
    //2.让游标移动,到尾为止
    while (cursor.moveToNext()) {
        String filePath = cursor.getString(cursor.getColumnIndex("_data"));
        String name = cursor.getString(cursor.getColumnIndex("_display_name"));
        String description = cursor.getString(cursor.getColumnIndex("description"));
        L.d(filePath + L.l());//storage/emulated/0/DCIM/Camera/1539834068417.jpg
        L.d(name + L.l());//Toly   
        L.d(description + L.l());//Only Me   
    }
5.删除
private void deleteImg() {
    Uri imgUri = Uri.parse("content://media/external/images/media/1064830");
    int delete = getContentResolver().delete(imgUri, "_id=1064830", null);
    L.d(delete + L.l());//1 表示删除了1行
}
6.获取所有图片的路径

一共12540张图片,方法耗时:1.289秒,属于耗时操作应该放在子线程 可以获取数据库中的字段,封装一个图片的实体类,以便使用

private ArrayList<String> queryAllImg() {
    ArrayList<String> imgPaths = new ArrayList<>();
    //1.查询获得游标
    Cursor cursor = getContentResolver().query(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, "",
            null, null, null);
    //2.让游标移动,到尾为止
    while (cursor.moveToNext()) {
        String filePath = cursor.getString(cursor.getColumnIndex("_data"));
        imgPaths.add(filePath);
    }
    return imgPaths;
}

查询所有图片.png

实验四、显示最近100张图片

为了简便,使用Picasso来加载图片:详情可见--O2-开源框架使用之Picasso

查询最近100张图片.png

1.获取最近100条数据库记录

排序条件:"date_added desc"表示根据date_added字段倒序查询 将数据盛放在List中,并根据列表元素个数来决定跳出while循环

private ArrayList<String> queryPic100() {
    ArrayList<String> imgPaths = new ArrayList<>();
    //1.查询获得游标
    String queryCol = MediaStore.Images.Media.DATE_ADDED;
    Cursor cursor = getContentResolver().query(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, "",
            null, "date_added desc", null);
    //2.让游标移动,到尾为止
    while (cursor.moveToNext()) {
        if (imgPaths.size() >= 100) {
            break;
        }
        String filePath = cursor.getString(cursor.getColumnIndex("_data"));
        imgPaths.add(filePath);
    }
    return imgPaths;
}
2.RecyclerView的简单使用(布局很简单就免了)

1).创建适配器类和ViewHolder 2).设置RecyclerView样式

/**
 * 适配器
 */
class PicRVAdapter extends RecyclerView.Adapter<PicViewHolder> {
    @NonNull
    @Override
    public PicViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(Pic100Activity.this).inflate(R.layout.item_img, null);
        return new PicViewHolder(view);
    }
    @Override
    public void onBindViewHolder(@NonNull PicViewHolder holder, int position) {
        //使用Picasso加载文件图片
        Picasso.get().setIndicatorsEnabled(true);
        Picasso.get()
                .load(new File(mList.get(position)))//文件
                .resize(200,200)//重设尺寸
                .into(holder.mIv_icon);
    }
    @Override
    public int getItemCount() {
        return mList.size();
    }
}

/**
 * ViewHolder
 */
public class PicViewHolder extends RecyclerView.ViewHolder {
    public final ImageView mIv_icon;
    /**
     * itemView为MyViewHolder中onCreateViewHolder加载的布局
     *
     * @param itemView 条目
     */
    public PicViewHolder(View itemView) {
        super(itemView);
        mIv_icon = itemView.findViewById(R.id.id_iv_item);
    }
}
//1.设置适配器
mIdRvPic100.setAdapter(new PicRVAdapter());
//2.!!创建布局管理器
mGLM = new GridLayoutManager(this, 4, GridLayoutManager.VERTICAL, false);
//3.!!!设置布局管理器
mIdRvPic100.setLayoutManager(mGLM);

实验五、选择图片

 @OnClick(R.id.imageView)
 public void onViewClicked() {
     //打开相册
     Intent picIntent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
     startActivityForResult(picIntent, 0);
 }
 @Override
 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
     super.onActivityResult(requestCode, resultCode, data);
     if (resultCode == RESULT_OK) {
         //获取返回的图片Uri
         Uri picUri = data.getData();
         InputStream is = null;
         try {
             //将Uri开流,形成输入流
             is = getContentResolver().openInputStream(picUri);
             Bitmap bitmap = BitmapFactory.decodeStream(is);
             mImageView.setImageBitmap(bitmap);
         } catch (FileNotFoundException e) {
             e.printStackTrace();
         } finally {
             try {
                 if (is != null) {
                     is.close();
                 }
             } catch (IOException e) {
                 e.printStackTrace();
             }
         }
     }
 }

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Android研究院

强大灵活的WebView代理库-PrimWeb

PrimWeb 是一个代理的WebView基于的 Android WebView 和 腾讯 x5 WebView,容易、灵活使用以及功能非常强大的库,提供了 W...

41530
来自专栏androidBlog

RecycleView下拉刷新控件的封装(包括下拉刷新和加载更多 )

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gdutxiaoxu/article/details/...

53610
来自专栏Android开发小工

优雅地实现RecyclerView的上拉加载

这篇博客是承接上一篇博客--探索Android架构的DataLayer层(DataManager方式)具体实现,其实是上篇博客的一个使用比较普遍的例子,当然如果...

12040
来自专栏刘望舒

打造一个灵活易用的Banner组件

之前做项目时候出于各种考虑,自己开发了Banner组件FBanner,欢迎大家的Star和PR。github上成熟的轮播图库已经有非常多了,比如banner和A...

15350
来自专栏Android干货

项目实战工具类(一):PhoneUtil(手机信息相关)

22150
来自专栏刘望舒

RxBinding使用和源码解析

作者 | juexingzhe 地址 | https://www.jianshu.com/u/ea71bb3770b4 声明 | 本文是 juexingzhe...

484100
来自专栏潇涧技术专栏

Android Text View with Custom Font

本文以自定义TextView为例简单实践下如何自定义View,它能够根据设置的xml属性采用不同的字体显示文字

10210
来自专栏分享达人秀

Intent 属性详解(上)

Android应用将会根据Intent来启动指定组件,至于到底启动哪个组件,则取决于Intent的各属性。本期将详细介绍Intent的各属性值,以及 A...

262100
来自专栏学海无涯

Android开发之Loader与LoaderManager

Loader是什么,有什么作用? 顾名思义就是加载器,简单来说,Loader做了2件事: (1)在单独的线程中读取数据,不会阻塞UI线程 (2)监视数据的更...

36050
来自专栏刘望舒

感受LiveData与ViewModel结合之美

虽说这篇是说LiveData与ViewModel,但是或多或少都有涉及另外一个组件:Lifecycles 。它们连同Room都是在17年谷歌IO大会推出的,当时...

15620

扫码关注云+社区

领取腾讯云代金券