【目录】
(一)上传图片到服务器一 ---------------------------------Android代码
(二)上传图片到服务器二---------------------------------Android 系统7.0以上调用相机兼容问题
(三)上传图片到服务器三 -----------------------------------后台服务器代码
一、相关知识
①Android权限申请
②网络访问框架OKHttp
③内存溢出问题:图片压缩
④Android 系统7.0以上调用系统相机无效
⑤有关图片上传过程中遇到的内存溢出问题
二、效果展示
二、代码
①HTML
1 <LinearLayout 2 android:layout_width="match_parent" 3 android:layout_height="wrap_content" 4 android:orientation="vertical" 5 android:background="@color/white" 6 > 7 <android.support.v7.widget.RecyclerView 8 android:id="@+id/rvPic" 9 android:layout_width="wrap_content" 10 android:layout_height="match_parent" 11 android:layout_gravity="center_horizontal"> 12 13 </android.support.v7.widget.RecyclerView> 14 15 <TextView 16 android:id="@+id/tvNum" 17 android:layout_width="wrap_content" 18 android:layout_height="wrap_content" 19 android:text="0/8" 20 android:textColor="#666666" 21 android:layout_gravity="right|bottom" 22 android:paddingRight="@dimen/dp_10"/> 23 24 25 </LinearLayout> 26 <Button 27 28 android:id="@+id/btn_Enter" 29 android:layout_width="match_parent" 30 android:layout_height="@dimen/dp_45" 31 android:layout_alignParentBottom="true" 32 android:background="@drawable/selecter_button" 33 android:text="确认上传" 34 android:textColor="@color/inButtonText" 35 android:textSize="@dimen/dp_18" />
②Java代码
<基本功能>
实体类
1 public class LoadFileVo { 2 3 File file; 4 5 int pg; //图片下方的进度条 6 7 boolean isUpload = false; //标识该文件是否上传 8 9 Bitmap bitmap; 10 11 public Bitmap getBitmap() { 12 return bitmap; 13 } 14 15 public void setBitmap(Bitmap bitmap) { 16 this.bitmap = bitmap; 17 } 18 19 public boolean isUpload() { 20 return isUpload; 21 } 22 23 public void setUpload(boolean upload) { 24 isUpload = upload; 25 } 26 27 public LoadFileVo() { 28 } 29 30 public LoadFileVo(File file, int pg) { 31 this.file = file; 32 this.pg = pg; 33 } 34 35 public LoadFileVo(File file, boolean isUpload, int pg,Bitmap bitmap) { 36 this.file = file; 37 this.pg = pg; 38 this.isUpload = isUpload; 39 this.bitmap = bitmap; 40 } 41 42 public File getFile() { 43 return file; 44 } 45 46 public void setFile(File file) { 47 this.file = file; 48 } 49 50 public int getPg() { 51 return pg; 52 } 53 54 public void setPg(int pg) { 55 this.pg = pg; 56 } 57 }
适配器
1 /* 2 *Create By 小群子 2018/12/10 3 */ 4 5 public class LoadPicAdapter extends RecyclerView.Adapter<LoadPicAdapter.MyViewHolder> { 6 7 Context context; 8 List<LoadFileVo> fileList = null; 9 View view; 10 int picNum = 8;//列表的图片个数最大值 11 12 public LoadPicAdapter(Context context, List<LoadFileVo> fileList) { 13 this.context = context; 14 this.fileList = fileList; 15 } 16 17 public LoadPicAdapter(Context context, List<LoadFileVo> fileList, int picNum) { 18 this.context = context; 19 this.fileList = fileList; 20 this.picNum = picNum; 21 } 22 23 public interface OnItemClickListener { 24 void click(View view, int positon); 25 26 void del(View view); 27 } 28 29 OnItemClickListener listener; 30 31 public void setListener(OnItemClickListener listener) { 32 this.listener = listener; 33 } 34 35 @Override 36 public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 37 38 view = LayoutInflater.from(context).inflate(R.layout.load_item_pic, parent, false); 39 return new MyViewHolder(view); 40 } 41 42 @Override 43 public void onBindViewHolder(MyViewHolder holder, final int position) { 44 45 //通过默认设置第一个为空文件为添加退保,且在文件个数小于最大限制值的情况。当图片个数等于最大限制值,第一个则不是添加按钮 46 if (position == 0&&fileList.get(position).getBitmap()==null) { 47 holder.ivPic.setImageResource(R.drawable.addpic);//加号图片 48 holder.ivPic.setOnClickListener(new View.OnClickListener() { 49 @Override 50 public void onClick(View view) { 51 listener.click(view, position); 52 } 53 }); 54 holder.ivDel.setVisibility(View.INVISIBLE); 55 holder.bg_progressbar.setVisibility(View.GONE); 56 57 } else { 58 // Uri uri = Uri.parse(fileList.get(position).getFile().getPath()); 59 // holder.ivPic.setImageURI(uri); 60 61 holder.ivPic.setImageBitmap(fileList.get(position).getBitmap()); 62 //使用压缩后的图片进行填充到界面上 63
64 65 66 holder.ivDel.setVisibility(View.VISIBLE); 67 holder.bg_progressbar.setVisibility(View.VISIBLE); 68 holder.bg_progressbar.setProgress(fileList.get(position).getPg()); 69 } 70 71 72 holder.ivDel.setOnClickListener(new View.OnClickListener() { 73 @Override 74 public void onClick(View view) { 75 //判断图片是否上传,上传后将无法删除 76 if (fileList.get(position).isUpload()) { 77 Toast.makeText(context, "该图片已上传!", Toast.LENGTH_SHORT).show(); 78 } else { 79 fileList.remove(position); 80 if (fileList.size()==picNum-1&&fileList.get(0).getBitmap()!=null){ 81 fileList.add(0,new LoadFileVo()); 82 }//如果数量达到最大数时,前面的加号去掉,然后再减去时,则添加前面的加号 83 notifyDataSetChanged(); 84 if (listener!=null){ 85 listener.del(view);//传递接口,计算图片个数显示在界面中 86 } 87 88 } 89 } 90 }); 91 92 93 } 94 95 @Override 96 public int getItemCount() { 97 return fileList.size(); 98 } 99 100 101 static class MyViewHolder extends RecyclerView.ViewHolder { 102 @BindView(R.id.ivPic) 103 ImageView ivPic; 104 @BindView(R.id.ivDel) 105 ImageView ivDel; 106 @BindView(R.id.bg_progressbar) 107 ProgressBar bg_progressbar; 108 109 View view; 110 111 112 MyViewHolder(View view) { 113 super(view); 114 this.view = view; 115 ButterKnife.bind(this, view); 116 } 117 } 118 }
item 布局//布局自行优化
1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 android:layout_width="@dimen/dp_110" 3 android:layout_height="@dimen/dp_115" 4 5 > 6 7 <LinearLayout 8 android:layout_width="match_parent" 9 android:layout_height="match_parent" 10 android:orientation="vertical" 11 android:padding="@dimen/dp_5"> 12 13 <ImageView 14 android:id="@+id/ivPic" 15 android:layout_width="match_parent" 16 android:layout_height="@dimen/dp_100" 17 android:scaleType="centerCrop" 18 android:src="@drawable/ic_pick" 19 /> 20 21 <ProgressBar 22 android:id="@+id/bg_progressbar" 23 style="@style/StyleProgressBarMini" 24 android:layout_width="match_parent" 25 android:layout_height="@dimen/dp_5" 26 android:background="@drawable/shape_progressbar_mini" 27 android:max="100" 28 android:progress="60" /> 29 </LinearLayout> 30 <ImageView 31 android:id="@+id/ivDel" 32 android:layout_width="@dimen/dp_25" 33 android:layout_height="@dimen/dp_25" 34 android:src="@drawable/delete_round" 35 android:layout_alignParentRight="true"/> 36 37 </RelativeLayout>
1 List<LoadFileVo> fileList = new ArrayList<>(); 2 LoadPicAdapter adapter = null; 3 4 //这里使用ButterKnife 5 @BindView(R.id.rvPic) 6 RecyclerView rvPic; 7 8 @BindView(R.id.tvNum) 9 TextView tvNum; 10 11 12 //初始化Adapter 13 //设置图片选择的接口 14 private void initAdapter() { 15 fileList.add(new LoadFileVo()); 16 adapter = new LoadPicAdapter(this, fileList,8); 17 rvPic.setAdapter(adapter); 18 rvPic.setLayoutManager(new GridLayoutManager(this, 3)); 19 adapter.setListener(new LoadPicAdapter.OnItemClickListener() { 20 @Override 21 public void click(View view, int positon) { 22 if (fileList.size()>8){ 23 showShortToast("一次最多上传8张图片!"); 24 }else { 25 selectPic(); //选择添加图片方法 26 } 27 28 } 29 30 @Override 31 public void del(View view) { 32 tvNum.setText((fileList.size()-1)+"/8"); 33 } 34 }); 35 }
《核心代码》
1 String mPhtotPath; 2 Uri uriImage; 3 File mPhotoFile = null; 4 5 6 //选择图片 7 private void selectPic() { 8 9 //动态请求权限,除此之外还需进行Androidmanifest.xml中进行请求 10 11 if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) 12 != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, 13 Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED 14 || ContextCompat.checkSelfPermission(this, 15 Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { 16 ActivityCompat.requestPermissions(this, 17 new String[]{Manifest.permission.CAMERA, 18 Manifest.permission.READ_EXTERNAL_STORAGE, 19 Manifest.permission.WRITE_EXTERNAL_STORAGE}, 20 1); 21 } 22 23 24 final CharSequence[] items = {"相册", "拍照"}; 25 AlertDialog.Builder dlg = new AlertDialog.Builder(EndLoadMstActivity.this); 26 dlg.setTitle("添加图片"); 27 dlg.setItems(items, new DialogInterface.OnClickListener() { 28 public void onClick(DialogInterface dialog, int item) { 29 // 这里item是根据选择的方式, 30 if (item == 0) { 31 Intent intent = new Intent(Intent.ACTION_PICK); 32 intent.setType("image/*"); 33 startActivityForResult(intent, 0); 34 } else { 35 try { 36 Intent intent = new Intent("android.media.action.IMAGE_CAPTURE"); 37 mPhtotPath = getSDPath() + "/" + getPhotoFileName(); 38 mPhotoFile = new File(mPhtotPath); 39 if (!mPhotoFile.exists()) { 40 mPhotoFile.createNewFile(); 41 } 42 // uriImage = FileProvider.getUriForFile(EndLoadMstActivity.this, getPackageName() + ".provider", createImageFile()); 43 44 uriImage = FileProvider.getUriForFile(EndLoadMstActivity.this, "com.ahbcd.app.tms.provider", mPhotoFile); 45 Log.i("TAG", "onClick: "+mPhtotPath+"---------" + getPackageName() + ".provider"); 46 // uriImage = Uri.fromFile(mPhotoFile);以上一句代替解决相机兼容问题 47 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 48 intent.putExtra(MediaStore.EXTRA_OUTPUT, uriImage); 49 50 startActivityForResult(intent, 1); 51 52 } catch (Exception e) { 53 e.printStackTrace(); 54 } 55 } 56 } 57 }).create(); 58 dlg.show(); 59 } 60 61 public String getSDPath() { 62 File sdDir = null; 63 boolean sdCardExsit = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); 64 if (sdCardExsit) { 65 sdDir = Environment.getExternalStorageDirectory(); 66 } 67 return sdDir.toString(); 68 } 69 70 private String getPhotoFileName() { 71 Date date = new Date(System.currentTimeMillis()); 72 SimpleDateFormat dateFormat = new SimpleDateFormat("'IMG'_yyyyMMdd_HHmmss"); 73 return dateFormat.format(date) + ".jpg"; 74 }
注:这里需要在Android中配置一个proveder 具体请参考 Android 系统7.0以上调用相机兼容问题
《获取返回的图片》
1 //重写onActivityResult方法 2 @Override 3 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 4 super.onActivityResult(requestCode, resultCode, data); 5 if (requestCode == 1) { 6 BitmapFactory.Options options = new BitmapFactory.Options(); 7 options.inSampleSize = 2; //图片宽高都为原来的二分之一,即图片为原来的四分之一 8 Bitmap bitmap = BitmapFactory.decodeFile(mPhtotPath, options); 9 if (bitmap != null) { 10 if (uriImage != null) { 11 saveUritoFile(uriImage,1); 12 } 13 14 if (!bitmap.isRecycled()) { 15 bitmap.recycle(); //回收图片所占的内存 16 System.gc(); //提醒系统及时回收 17 } 18 } 19 } 20 if (requestCode == 0) { 21 if (data != null) { 22 Uri uri = data.getData(); 23 saveUritoFile(uri,0); 24 } 25 } 26 27 } 28 29 //将Uri图片类型转换成File,BitMap类型 30 //在界面上显示BitMap图片,以防止内存溢出 31 //上传可选择File文件上传 32 33 private void saveUritoFile(Uri uriImage,int type) { 34 Bitmap photoBmp = null; 35 36 if (uriImage != null) { 37 try { 38 // photoBmp = MediaStore.Images.Media.getBitmap(this.getContentResolver(), uriImage); 39 // ByteArrayOutputStream fos = new ByteArrayOutputStream(); 40 // photoBmp.compress(Bitmap.CompressFormat.JPEG, 80, fos); 41 //以上代码压缩不行,还是会造成内存溢出 42 43 BitmapFactory.Options options = new BitmapFactory.Options(); 44 options.inSampleSize = 2; //图片宽高都为原来的二分之一,即图片为原来的四分之一 45 photoBmp = BitmapFactory.decodeStream(this.getContentResolver() 46 .openInputStream(uriImage), null, options); 47 48 File file = new File(""); 49 if (type==0){ 50 file = FileParseUtils.getFileByUri(this, uriImage); 51 52 }else { 53 if (mPhotoFile!=null){ 54 file = mPhotoFile; 55 } 56 57 } 58 // File file = new File(""); 59 // try { 60 // file = new File(new URI(uriImage.toString())); 61 // } catch (URISyntaxException e) { 62 // e.printStackTrace(); 63 // } 64 fileList.add(new LoadFileVo(file, false, 0, photoBmp)); 65 tvNum.setText((fileList.size()-1)+"/8"); 66 if (fileList.size()>8){ //判断时候达到最大数量,如果达到最大数量,则去掉前面的加号 67 fileList.remove(0); 68 } 69 70 adapter.notifyDataSetChanged(); 71 72 } catch (IOException e) { 73 e.printStackTrace(); 74 Log.i("TAG", "saveUritoFile: ---------压缩图片异常!"); 75 } 76 77 78 } 79 80 }
图片上传到后台OKhttp
1 //一张张图片轮流上传 2 public void netUpload(int i, final String joData) {//用jsonOject方式转string传递其他参数 3 try { 4 5 if (!isRequestHttp) { 6 isRequestHttp = true; 7 final int finalI = i; 8 enterEnable(false); 9 10 if (fileList.get(finalI).isUpload()) { 11 netUpload(finalI + 1, joData); 12 } else { 13 14 RequestBody requestBody = new MultipartBody.Builder() 15 .setType(MultipartBody.FORM) 16 .addFormDataPart("mstjson",msg) //其他信息 17 .addFormDataPart("file", file.getName(), 18 RequestBody.create(MediaType.parse("application/octet-stream"), file))//文件 19 .build(); 20 Request request = new Request.Builder() 21 .url(uploadPic---这里是图片上传的地址).post(requestBody) 22 .build(); 23 okHttpClient.newCall(request).enqueue(new UICallBack() { 24 @Override 25 public void onFailureUI(Call call, IOException e) { 26 showShortToast("连接服务器失败"); 27 isRequestHttp = true; 28 enterEnable(true); 29 30 } 31 32 @Override 33 public void onResponseUI(Call call, Response response) { 34 try { 35 36 isRequestHttp = false; 37 38 String result = response.body().string(); 39 Utils.log("上传图片结果:" + result); 40 41 42 if (!response.isSuccessful()) { 43 Utils.log("响应失败:" + response.code()); 44 showShortToast("响应失败:" + response.code()); 45 enterEnable(true); 46 47 return; 48 } 49 if (result.equals("{}")) { 50 showShortToast("获取服务端数据为空"); 51 enterEnable(true); 52 53 return; 54 } 55 JSONObject jsonObject = new JSONObject(result); 56 if (jsonObject.getString("IsError").equals("true")) { 57 showShortToast(jsonObject.getString("ErrMsg")); 58 enterEnable(true); 59 60 } else { 61 String Data = jsonObject.getString("Data"); 62 fileList.get(finalI).setPg(100); 63 fileList.get(finalI).setUpload(true); 64 adapter.notifyDataSetChanged(); 65 66 if (finalI == fileList.size() - 1) { 67 showShortToast("上传成功!"); 68 69 btnEnter.setText("已上传"); 70 71 } else { 72 netUpload((finalI + 1), joData); 73 } 74 75 } 76 } catch (Exception e) { 77 isRequestHttp = true; 78 hideLoadingDialog(); 79 showShortToast("上传凭证发生异常!"); 80 LogToFile.e("上传凭证发生异常," + e); 81 enterEnable(true); 82 83 } 84 } 85 }); 86 87 } 88 } 89 } catch (Exception e) { 90 isRequestHttp = true; 91 hideLoadingDialog(); 92 93 showShortToast("上传图片请求异常!"); 94 LogToFile.e("上传图片请求异常," + e); 95 enterEnable(true); 96 97 } 98 } 99 100 101 //用来提示用户文件上传的情况。同时也是避免同时反复操作 102 public void enterEnable(boolean isEnabled) { 103 if (isEnabled) { 104 btnEnter.setText("重新上传"); 105 btnEnter.setEnabled(true); 106 btnEnter.setBackgroundResource(R.drawable.selecter_button); 107 btnEnter.setTextColor(getResources().getColor(R.color.inButtonText)); 108 109 } else { 110 btnEnter.setText("正在上传···"); 111 btnEnter.setEnabled(false); 112 btnEnter.setBackgroundResource(R.drawable.buttonshap); 113 btnEnter.setTextColor(getResources().getColor(R.color.outButtonText)); 114 } 115 }