Android从系统调用相册OkHttp上传到服务器(activity数据传递)

Android从系统调用相册OkHttp上传到服务器(activity数据传递)

想写技术博客已经好久,今天终于下定决心坚持一周一个小博客,我是一名安卓工程师,工作刚刚一个月,水平有限,此文章是结合郭霖大神的《第一行代码》以及自己参考网络大神上的一些参考代码结合起来的,在自己项目中进行调用,虽然有些杂乱,但是也算是基本的实现了功能。

下面先来展示一下功能截图

这里写图片描述
这里写图片描述
功能流程相信各位也都能看懂,在第一个注册信息完成后点击下一步后继续完成第二个页面的注册信息,然后将注册信息上传到服务器。

我也是第一次工作第一次自己进行独立开发,所以可能会走不少弯路,但是总算功能能够实现了。
下面来给大家展示一下代码,我会着重将一些重要的代码展示出来

牵扯到的知识点

  • Android调用系统相册及相机(隐性intent)
  • ContentProvider
  • okHttp
  • popupWindow
  • Bundle

代码讲解

为了能够更好的点击btn弹出相机或者相册,我自定义了一个popupWindow,这个如果你不想写,可以百度得到。
>

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_margin="10dp"
        android:orientation="vertical"
        android:paddingBottom="10dp">

        <Button
            android:id="@+id/btn_pop_album"
            android:layout_width="match_parent"
            android:layout_height="45dp"
            android:background="#ffff"
            android:text="本地相册"
            android:textSize="18sp" />

        <Button
            android:id="@+id/btn_pop_camera"
            android:layout_width="match_parent"
            android:layout_height="45dp"
            android:background="#ffff"
            android:text="相机拍摄"
            android:textSize="18sp" />

        <Button
            android:id="@+id/btn_pop_cancel"
            android:layout_width="match_parent"
            android:layout_height="45dp"
            android:layout_marginTop="10dp"
            android:background="#ffff"
            android:text="取消"
            android:textSize="18sp" />
    </LinearLayout>

</RelativeLayout>

>

点击popupWindow调用相机或相册


View popView = View.inflate(this, R.layout.popup_choose_pic, null);
        Button btnPopAlbum = (Button) popView.findViewById(R.id.btn_pop_album);
        Button btnPopCamera = (Button) popView.findViewById(R.id.btn_pop_camera);
        Button btnPopCancel = (Button) popView.findViewById(R.id.btn_pop_cancel);
        //获取屏幕宽高
        int widthPixels = getResources().getDisplayMetrics().widthPixels;
        int heightPixels = getResources().getDisplayMetrics().heightPixels * 1 / 3;
        final PopupWindow popupWindow = new PopupWindow(popView, widthPixels, heightPixels);
        popupWindow.setAnimationStyle(R.style.anim_popup_dir);
        popupWindow.setFocusable(true);
        //点击popup外部消失
        popupWindow.setOutsideTouchable(true);
        //消失时屏幕变为半透明
        popupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
            @Override
            public void onDismiss() {
                WindowManager.LayoutParams params = getWindow().getAttributes();
                params.alpha = 1.0f;
                getWindow().setAttributes(params);
            }
        });
        //出现时屏幕变为透明
        WindowManager.LayoutParams params = getWindow().getAttributes();
        params.alpha = 0.5f;
        getWindow().setAttributes(params);
        popupWindow.showAtLocation(popView, Gravity.BOTTOM, 0, 50);
        btnPopAlbum.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                popupWindow.dismiss();
                //调用相机
                invokeAlbum();
            }
        });
        btnPopCamera.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                popupWindow.dismiss();
                //打开相机
                openCarema();
            }
        });
        btnPopCancel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                popupWindow.dismiss();
            }
        });

可以直接拷贝到xml里面看预览图

下面我们就着重讲解点击头像这个功能逻辑

其实对于调用相册以及相机这几个功能,许多的API我也不是特别了解,也不能详细的跟大家解释一下,在这里我将我拷贝这些代码的时候遇到的坑跟大家说下——-功能摘自《第一行代码第二版》相机相册那个章节。

调用相册

private void invokeAlbum() {
        //动态申请危险时权限,运行时权限
        if (ContextCompat.checkSelfPermission(HgCompleteInfoActivity.this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) !=
                PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(HgCompleteInfoActivity.this, new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
        } else {
            openAlbum();
        }
    }
 private void openAlbum() {
        Intent intent = new Intent("android.intent.action.GET_CONTENT");
        intent.setType("image/*");
        startActivityForResult(intent, CHOOSE_PHOTO);
    }

在这里先进行了动态时权限,这是7.0之后支持的,具体的可以看郭霖的blog,目前着重实现功能
>
>

/**
     * 动态获取到的权限后的重写
     *
     * @param requestCode
     * @param permissions
     * @param grantResults
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode) {
            case 1:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    openAlbum();
                } else {
                    Toast.makeText(HgCompleteInfoActivity.this, "you denied the permission", Toast.LENGTH_SHORT).show();
                }
                break;
            default:
                break;
        }
    }

这里是动态获取到权限后的重写方法,如果不够理解,可以想一下
app安装时软件介绍会解释获取哪些权限,那些权限是属于manifests里注册的,而动态获取权限时,当你第一次打开app时,比如需要获取你的地理位置,你点了拒绝,会弹出上面方法里面case1–>else逻辑。

  @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {

            case CHOOSE_PHOTO:
                if (resultCode == RESULT_OK) {
                    //判断手机系统版本号
                    if (Build.VERSION.SDK_INT >= 19) {
                        handleImageOnKitKat(data);
                    } else {
                        handleImageBeforeKitKat(data);
                    }
                }
                break;
        }
    }
 private void handleImageBeforeKitKat(Intent data) {
        Uri uri = data.getData();
        String imagePath = getImagePath(uri, null);
        displayImage(imagePath);
    }

    private void handleImageOnKitKat(Intent data) {
        String imagePath = null;
        Uri uri = data.getData();
        //Log.i("uri", uri + "");
        if (DocumentsContract.isDocumentUri(HgCompleteInfoActivity.this, uri)) {
            //如果是document类型的uri,则通过document id 处理
            String docId = DocumentsContract.getDocumentId(uri);
            Log.i("type of document", docId);
            if ("com.android.providers.media.documents".equals(uri.getAuthority())) {
                String id = docId.split(":")[1];//解析出数字格式的id
                Log.i("type of document id", id);
                String selection = MediaStore.Images.Media._ID + "=" + id;
                Log.i("selection", selection);
                imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection);
            } else if ("com.android.providers.downloads.documents".equals(uri.getAuthority())) {
                Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(docId));
                imagePath = getImagePath(contentUri, null);
            }
        } else if ("content".equalsIgnoreCase(uri.getScheme())) {
            //如果是content类型的uri,就用普通方式处理
            imagePath = getImagePath(uri, null);
        } else if ("file".equalsIgnoreCase(uri.getScheme())) {
            //如果是file类型的Uri,直接获取图片路径
            imagePath = uri.getPath();
        }
        displayImage(imagePath);
    }
private String getImagePath(Uri uri, String selection) {
        String path = null;
        //通过uri和selection来获取真实的图片路径
        Cursor cursor = getContentResolver().query(uri, null, selection, null, null);
        if (cursor != null) {
            if (cursor.moveToNext()) {
                path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
            }
            cursor.close();
        }
        return path;
    }
 private void displayImage(String imagePath) {
        if (imagePath != null) {
            bitmap = BitmapFactory.decodeFile(imagePath);
            headFile = saveMyBitmap(bitmap, "head");
            //保存file到sp
            saveFile(headFile.getName());
            Glide.with(this).asBitmap().load(headFile).thumbnail(0.1f).into(civHead);
        } else {
            Toast.makeText(HgCompleteInfoActivity.this, "failed to get image ", Toast.LENGTH_SHORT).show();
        }
    }
//将bitmap转化为png格式
    public File saveMyBitmap(Bitmap mBitmap, String prefix) {
        File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
        File file = null;
        try {
            file = File.createTempFile(
                    prefix,  /* prefix */
                    ".jpg",         /* suffix */
                    storageDir      /* directory */
            );
            FileOutputStream fos = new FileOutputStream(file);
            mBitmap.compress(Bitmap.CompressFormat.JPEG, 10, fos);
            fos.flush();
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return file;
    }
    /**
     * 保存file到sp
     *
     * @param fileName
     */
    private void saveFile(String fileName) {
        SharedPreferences sp = getSharedPreferences("image", MODE_PRIVATE);
        SharedPreferences.Editor edit = sp.edit();
        edit.putString(HEAD_KEY, fileName);
        //提交edit
        edit.commit();
        Log.i(TAG, "saveFile: 保存成功" + sp.getString("head", null));
    }

这是阻扰我时间最多的逻辑部分,首先解释一下这些代码的逻辑
>
1.点击获取到的相册显示到我上图中的CircleImageView(记得一定要压缩,不然拍完照返回照片到ImageView会特别的慢,在这里我做的也不够好,所以不展开讲)
2.然后自己写了两个方法,savaFile和savaMyBitmap
3.在savaMyBitmap方法中,首先获取了系统相册的地址,然后每次我拍下照片或者选择照片时,都进行简单的压缩,因为图片要上传到服务器进行审核管理,做成微信头像之类的超级压缩方法有损画质,所以我只是进行了简单的压缩上传最后写成一个文件。(在第二个页面的时候可能会有几个问题,三个ImageView即是三个bitmap,三个File文件。如何判断及正确的显示,我们可以定义成全局变量,因为都是点击事件,所以bitmap会在点击后修改,然后保存下来。
4.在savaFile中,我灵机一动(哈哈),想到了SharedPreferences这个神奇功能,我们没有必要用sp去存file,因为没有那个方法,我们可以将名字存起来啊,edit.putString()中的HEAD_KEY就是我自己定的方法。你可以把sp当成一个map,key是不会变的,但是value会变,所以理论上不管我们点多少次都会改变。
5.这样文件就保存在本地了(可是不知道为啥我找不到,但是上传到服务器里可以显示)。

调用相机

private void openCarema() {
        //创建File对象,用于存储拍照后的照片
        File outputImage = new File(getExternalCacheDir(), "output_image.jpg");
        if (outputImage.exists()) {
            outputImage.delete();
        }
        try {
            outputImage.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (Build.VERSION.SDK_INT >= 24) {
            imageUri = FileProvider.getUriForFile(HgCompleteInfoActivity.this, "com.qryl.qrylyh.activity.login.complete.fileprovider", outputImage);
        } else {
            imageUri = Uri.fromFile(outputImage);
        }
        //启动相机程序
        Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
        intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
        startActivityForResult(intent, TAKE_PHOTO);
    }

这里不多赘述,调用Uri的逻辑功能代码都是郭霖的,我是cv战士。

 @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
            case TAKE_PHOTO:
                if (resultCode == RESULT_OK) {
                    //将拍摄的图片显示出来
                    try {
                        bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
                        headFile = saveMyBitmap(bitmap, "head");
                        //保存file到sp
                        saveFile(headFile.getName());
                        Glide.with(this).asBitmap().load(headFile).thumbnail(0.1f).into(civHead);
                        Log.i("wechat", "压缩后图片的大小" + ("字节码:" + " 宽度为:" + bitmap.getWidth() + " 高度为:" + bitmap.getHeight()));
                        Log.i(TAG, "File:" + headFile.getName() + " 路径:" + headFile.getAbsolutePath());
                    } catch (FileNotFoundException e) {
                        e.printStackTrace();
                    }
                }
                break;
                  }
    }

和上面的方法基本都一样。

使用Bundle以及SharedPreferences的理由

因为我要把第一页的数据全部保存下来,全部在第二个页面点击注册时一起上传,这样的话只是请求一次服务器,所以我想到了sp方法将文件名字存储起来,在第二个页面直接调用File方法把文件取出来即可
而Bundle只是为了区别分开,本来我是打开实现serializable的一个Map工具类集合进行数据的传递,技术有限,老是异常,就想到了用File存储的方法

其他注册信息Bundle方法

  /**
     * 传递数据到下个页面
     */
    private void putExtra() {
        Intent intent = new Intent(HgCompleteInfoActivity.this, CompletePicActivity.class);
        //传递数据
        Bundle bundle = new Bundle();
        //bundle.putByteArray("head", bytes);
        bundle.putString("name", ageDialogText);
        bundle.putString("identity", identityDialogText);
        bundle.putString("gender", genderDialogText);
        bundle.putString("age", ageDialogText);
        bundle.putString("workexperience", workExperienceDialogText);
        bundle.putString("begoodat", beGoodAtWorkDialogText);
        bundle.putString("localservice", null);
        intent.putExtras(bundle);
        startActivity(intent);
    }

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_complete_pic);
        initView();

        Bundle bundle = getIntent().getExtras();

        String name = (String) bundle.get("name");
        String indentity = (String) bundle.get("identity");
        String gender = (String) bundle.get("gender");
        String age = (String) bundle.get("age");
        String workexperience = (String) bundle.get("workexperience");
        String begoodat = (String) bundle.get("begoodat");
        String localservice = (String) bundle.get("localservice");

        //dataMap.put("head", head.toString());
        dataMap.put("name", name);
        dataMap.put("indentity", indentity);
        dataMap.put("gender", gender);
        dataMap.put("age", age);
        dataMap.put("workexperience", workexperience);
        dataMap.put("begoodat", begoodat);
        dataMap.put("localservice", localservice);

    }

这没啥好说的,第二个页面直接取出来,我为了以后后期方便维护,定义了一个HashMap,如果没有此需求可以直接取出来当成全局变量进行调用即可。

调用图片上传的拓展

不知道大家是不是还记得第二个页面有三个上传照片的图片,在刚才我也提过,如何在正确的相框中实现正确的图片,是一个不难但是挺啰嗦的事情

@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
            case TAKE_PHOTO:
                if (resultCode == RESULT_OK) {
                    //将拍摄的图片显示出来
                    try {
                        bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
                        if (choosed_image == R.id.sfz_image) {//身份证
                            //保存file到sp
                            sfzFile = saveMyBitmap(bitmap, "sfz");
                            saveFile(SFZ_KEY, sfzFile.getName());
                            Glide.with(this).asBitmap().load(sfzFile).thumbnail(0.1f).into(sfzImage);
                        } else if (choosed_image == R.id.jkz_image) {//健康证
                            jkzFile = saveMyBitmap(bitmap, "jkz");
                            //保存file到sp
                            saveFile(JKZ_KEY, jkzFile.getName());
                            Glide.with(this).asBitmap().load(jkzFile).thumbnail(0.1f).into(jkzImage);
                        } else if (choosed_image == R.id.zgz_image) {//从业资格证
                            zgzFile = saveMyBitmap(bitmap, "zgz");
                            //保存file到sp
                            saveFile(ZGZ_KEY, zgzFile.getName());
                            Glide.with(this).asBitmap().load(zgzFile).thumbnail(0.1f).into(zgzImage);
                        }
                    } catch (FileNotFoundException e) {
                        e.printStackTrace();
                    }
                }
                break;
            case CHOOSE_PHOTO:
                if (resultCode == RESULT_OK) {
                    //判断手机系统版本号
                    if (Build.VERSION.SDK_INT >= 19) {
                        handleImageOnKitKat(data);
                    } else {
                        handleImageBeforeKitKat(data);
                    }
                }
                break;
        }
    }

这些代码看着很繁琐,其实东西没多少,只是比上面的逻辑多了个if,在这里我定义了一个全局的int变量,将三个相册区别开来,R.id.sfz_image就是一个int值不是吗。这样看起来更加具像化,最好不用数字区分开。savaFile一样。

这样基本的上传照片基本完成,我们可以点击注册把数据整合起来直接上传到服务器了,在这里我用了okHttp框架

在第一行代码里,okHttp只是简单的展示了一下方法,大家可以百度一下各种okHttp方法,因为我们这个项目,定的统一是post请求,而且因为数据类型可能不同(File,String,int),所以我经过半天的不懈努力,看到了一个nb的API:addFormDataPart()

 /**
     * 向服务器发送请求
     */
    private void postData() {
        SharedPreferences pref = getSharedPreferences("image", Context.MODE_PRIVATE);
        String headImage = pref.getString(HEAD_KEY, null);
        String sfzImage = pref.getString(SFZ_KEY, null);
        String jkzImage = pref.getString(JKZ_KEY, null);
        String zgzName = pref.getString(ZGZ_KEY, null);
        //Log.i(TAG, "postData: 头像图片名字" + imageName);
        OkHttpClient client = new OkHttpClient();
        MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM);
        File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
        File headFile = new File(storageDir, headImage);
        File sfzFile = new File(storageDir, sfzImage);
        File jkzFile = new File(storageDir, jkzImage);
        File zgzFile = new File(storageDir, zgzName);
        if (headFile != null) {
            // MediaType.parse() 里面是上传的文件类型。
            RequestBody body = RequestBody.create(MediaType.parse("image/*"), headFile);
            // 参数分别为, 请求key ,文件名称 , RequestBody
            builder.addFormDataPart("txImg", headFile.getName(), body);
        }
        if (sfzFile != null) {
            // MediaType.parse() 里面是上传的文件类型。
            RequestBody body = RequestBody.create(MediaType.parse("image/*"), sfzFile);
            // 参数分别为, 请求key ,文件名称 , RequestBody
            builder.addFormDataPart("sfzImg", sfzFile.getName(), body);
        }
        if (jkzFile != null) {
            // MediaType.parse() 里面是上传的文件类型。
            RequestBody body = RequestBody.create(MediaType.parse("image/*"), jkzFile);
            // 参数分别为, 请求key ,文件名称 , RequestBody
            builder.addFormDataPart("jkzImg", jkzFile.getName(), body);
        }
        if (zgzFile != null) {
            // MediaType.parse() 里面是上传的文件类型。
            RequestBody body = RequestBody.create(MediaType.parse("image/*"), zgzFile);
            // 参数分别为, 请求key ,文件名称 , RequestBody
            builder.addFormDataPart("zgzImg", zgzFile.getName(), body);
        }
        builder.addFormDataPart("loginId", "1");
        //builder.add("sfzImg", "1");
        //builder.add("zgzImg", "1");
        //builder.add("jkzImg", "1");
        builder.addFormDataPart("realName", "sdfdf");
        builder.addFormDataPart("gender", "0");
        builder.addFormDataPart("age", "10");
        builder.addFormDataPart("workYears", "10");
        builder.addFormDataPart("introduce", "sdfsdfsf");
        MultipartBody requestBody = builder.build();
        Request request = new Request.Builder().url("服务器地址").post(requestBody).build();
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.i(TAG, "onFailure: 失败");
                e.printStackTrace();
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                Log.i(TAG, "onResponse: 成功 " + response.body().string());
            }
        });
    }

可以忽略那些没用的提取数据代码

写到后面也比较懒了,因为是周末,晚上还有极限挑战哈哈,所以要抓紧
在这client的回调我觉得用过okHttp的人都能看懂,重要的newCall方法里面的参数,在这里我是用MultipartBody类,大家可以看上面那一段代码的第12行开始

MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM);

这段代码是定义的这是上传的表单类型

RequestBody body = RequestBody.create(MediaType.parse("image/*"), headFile);
            // 参数分别为, 请求key ,文件名称 , RequestBody
            builder.addFormDataPart("txImg", headFile.getName(), body);

image/*是因为,我的图片文件是.jpg格式。
在这里,我打了详细的注释,帮助大家也帮助自己理解。这样,我们在两个页面进行实现注册页面,用了一次请求就将数据上传到了服务器。其实上传的方式有很多种,用流的方式上传图片也是可以的。

第一次博客,因为自己本身是一个刚工作一个月的菜鸟,自己一个人搞安卓端的开发,也算是独立开发了。所以要多走不少弯路,实现一个功能可能会麻烦不少。希望以后好好做一个cv战士。

猜你喜欢

转载自blog.csdn.net/qq_33915851/article/details/77925899
今日推荐