Android 上傳圖片到服務(wù)器(單文件上傳)

      網(wǎng)友投稿 1649 2022-05-30

      Android 上傳圖片到服務(wù)器

      一、Android端選擇圖片并顯示

      1.設(shè)計(jì)item

      2.設(shè)計(jì)數(shù)據(jù)類(lèi)

      3.編寫(xiě)適配器,將數(shù)據(jù)裝配到視圖上

      4.核心代碼

      4.1打開(kāi)相冊(cè)的方式

      4.2打開(kāi)拍照的方式

      4.2.1兼容7.0后的拍照

      4.3獲取圖片

      4.4.單文件上傳圖片核心代碼

      4.5.多文件上傳圖片核心代碼

      二、服務(wù)器端

      一、Android端選擇圖片并顯示

      1.設(shè)計(jì)item

      我們將使用RecyclerView來(lái)展示我們選擇好的圖片。因此我們首先要設(shè)計(jì)item,item有兩種:添加按鈕、圖片。

      添加按鈕item:add_image_button_item.xml

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      2.設(shè)計(jì)數(shù)據(jù)類(lèi)

      數(shù)據(jù)類(lèi)中有我們的圖片數(shù)據(jù):ImageFileBean.java。

      public class ImageFileBean { private File file; // 圖片文件 private boolean isUpload = false; //標(biāo)識(shí)該文件是否上傳 private Bitmap bitmap;// 圖片 private boolean startUpload; // 標(biāo)識(shí)圖片是否開(kāi)始上傳,以此控件ProgressBar的顯示 public ImageFileBean(File file, int pg) { this.file = file; } public ImageFileBean(File file, Bitmap bitmap,boolean isUpload) { this.file = file; this.isUpload = isUpload; this.bitmap = bitmap; } ... }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      將圖片數(shù)據(jù)與添加按鈕統(tǒng)一處理的數(shù)據(jù)類(lèi):ItemBean.java。

      public class ItemBean { private boolean isButton; // 是否是添加圖片的按鈕 private ImageFileBean imageFileBean; public ItemBean(){} public ItemBean(ImageFileBean bean,boolean isButton){ this.imageFileBean = bean; this.isButton = isButton; } ... }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      3.編寫(xiě)適配器,將數(shù)據(jù)裝配到視圖上

      LoadImageAdapter.java:

      package com.wong.imageupload; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.ProgressBar; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import java.util.List; public class LoadImageAdapter extends RecyclerView.Adapter { private final static int BUTTON_TYPE = 100; private final static int IMAGE_TYPE = 200; private List list = null; private OnImageItemClickListener onImageItemClickListener; public LoadImageAdapter(List list) { this.list = list; } @NonNull @Override public ButtonViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { switch (viewType) { case BUTTON_TYPE: View buttonView = LayoutInflater.from(parent.getContext()).inflate(R.layout.add_image_button_item, parent, false); return new ButtonViewHolder(buttonView); default: View imageView = LayoutInflater.from(parent.getContext()).inflate(R.layout.image_item, parent, false); return new ImageViewHolder(imageView); } } @Override public void onBindViewHolder(@NonNull ButtonViewHolder holder, final int position) { if (!list.get(position).isButton()) { ImageViewHolder imageViewHolder = (ImageViewHolder)holder; // 顯示圖片 imageViewHolder.mIVImg.setImageBitmap(list.get(position).getImageFileBean().getBitmap()); boolean startUpload = list.get(position).getImageFileBean().isStartUpload(); boolean isUpload = list.get(position).getImageFileBean().isUpload(); if(startUpload && !isUpload){ imageViewHolder.mPB.setVisibility(View.VISIBLE); }else{ imageViewHolder.mPB.setVisibility(View.GONE); } // 點(diǎn)擊刪除按鈕 imageViewHolder.mIVDel.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (onImageItemClickListener != null) { onImageItemClickListener.onDelete(v, list.get(position), position); } } }); } holder.mIVImg.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (onImageItemClickListener != null) { onImageItemClickListener.onClick(v, list.get(position), position); } } }); } @Override public int getItemCount() { return list.size(); } @Override public int getItemViewType(int position) { if (list.get(position).isButton()) { return BUTTON_TYPE; } else { return IMAGE_TYPE; } } public void setOnImageItemClickListener(OnImageItemClickListener onImageItemClickListener) { this.onImageItemClickListener = onImageItemClickListener; } static class ButtonViewHolder extends RecyclerView.ViewHolder { ImageView mIVImg; View view; public ButtonViewHolder(@NonNull View itemView) { super(itemView); view = itemView; mIVImg = itemView.findViewById(R.id.iv_img); } } static class ImageViewHolder extends ButtonViewHolder { ImageView mIVDel; ProgressBar mPB; public ImageViewHolder(@NonNull View itemView) { super(itemView); mIVDel = itemView.findViewById(R.id.iv_delete); mPB = itemView.findViewById(R.id.pb_bar); } } public interface OnImageItemClickListener { void onClick(View view, ItemBean itemBean, int position); void onDelete(View view, ItemBean itemBean, int position); } }

      1

      2

      3

      4

      5

      6

      Android 上傳圖片到服務(wù)器(單文件上傳)

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      27

      28

      29

      30

      31

      32

      33

      34

      35

      36

      37

      38

      39

      40

      41

      42

      43

      44

      45

      46

      47

      48

      49

      50

      51

      52

      53

      54

      55

      56

      57

      58

      59

      60

      61

      62

      63

      64

      65

      66

      67

      68

      69

      70

      71

      72

      73

      74

      75

      76

      77

      78

      79

      80

      81

      82

      83

      84

      85

      86

      87

      88

      89

      90

      91

      92

      93

      94

      95

      96

      97

      98

      99

      100

      101

      102

      103

      104

      105

      106

      107

      108

      109

      110

      111

      112

      113

      114

      115

      116

      117

      118

      119

      120

      121

      122

      123

      124

      4.核心代碼

      4.1打開(kāi)相冊(cè)的方式

      // 這種方式是通過(guò)action方式打開(kāi)android的其他app來(lái)完成的 Intent galleryIntent = new Intent(Intent.ACTION_PICK); // 系統(tǒng)默認(rèn)的圖片選擇程序 galleryIntent.setType("image/*"); startActivityForResult(galleryIntent,REQUEST_GALLERY);

      1

      2

      3

      4

      獲取選中的圖片:

      Uri uri = data.getData();

      1

      2

      4.2打開(kāi)拍照的方式

      // 這種方式是通過(guò)action方式打開(kāi)android的其他app來(lái)完成的 // MediaStore.ACTION_IMAGE_CAPTURE 即android.media.action.IMAGE_CAPTURE Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);// 系統(tǒng)的相機(jī)程序 // 準(zhǔn)備圖片名稱(chēng) String imageName = UUID.randomUUID().toString().replace("-","")+".jpg"; // 創(chuàng)建應(yīng)用內(nèi)緩存目錄cache/images CacheUtils.createImagesCacheFolder(MainActivity.this); File cameraFile = new File(CacheUtils.getCacheImagesFolder(MainActivity.this).getPath()+"/"+imageName); // 創(chuàng)建好圖片文件接收拍照的數(shù)據(jù) if(!cameraFile.exists()){ try { cameraFile.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } if(Build.VERSION.SDK_INT > Build.VERSION_CODES.M) { // 在官方7.0的以上的系統(tǒng)中,嘗試傳遞 file://URI可能會(huì)觸發(fā)FileUriExposedException。7.0以上的系統(tǒng)需要使用FileProvider兼容拍照 cameraUri = FileProvider.getUriForFile(MainActivity.this, "com.wong.camera.fileprovider", cameraFile); }else{ cameraUri = Uri.fromFile(cameraFile); } cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT,cameraUri); startActivityForResult(cameraIntent,REQUEST_CAMERA);

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      在官方7.0的以上的系統(tǒng)中,嘗試傳遞 file://URI可能會(huì)觸發(fā)FileUriExposedException。7.0以上的系統(tǒng)需要使用FileProvider兼容拍照:

      第一步:在A(yíng)ndroidManifest.xml加入以下內(nèi)容。

      1

      2

      3

      4

      5

      6

      7

      8

      9

      第二步:新建資源目錄xml,并配置FileProvider的資源目錄。

      fileprovider.xml:

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      通過(guò)URI獲取圖片:

      Bitmap bitmap = null; if(uri != null){ try { BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 2; // 圖片寬高都為原來(lái)的二分之一,即圖片為原來(lái)的四分之一 bitmap = BitmapFactory.decodeStream(this.getContentResolver().openInputStream(uri), null, options); String filePath = uri.getEncodedPath();; File file = new File(filePath); }catch (Exception e){ } }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      4.3獲取圖片

      /** * 將Uri圖片類(lèi)型轉(zhuǎn)換成File,BitMap類(lèi)型 * 在界面上顯示BitMap圖片,以防止內(nèi)存溢出 * 上傳可選擇File文件上傳 * * @param uri */ private void saveUriToFile(Uri uri,int from) { Bitmap bitmap = null; if (uri != null) { try { BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 2; // 圖片寬高都為原來(lái)的二分之一,即圖片為原來(lái)的四分之一 bitmap = BitmapFactory.decodeStream(this.getContentResolver().openInputStream(uri), null, options); File file = null; switch (from){ case REQUEST_GALLERY: String filePath = FileUtils.getRealFilePath(this,uri); File oldFile = new File(filePath); // 修改文件名 String newFileName = UUID.randomUUID().toString().replace("-","")+".jpg"; String newFilePath = oldFile.getParent()+"/"+newFileName; file = new File(newFilePath); oldFile.renameTo(file); break; case REQUEST_CAMERA: file = cameraFile; break; } if(file == null || !file.exists()){ Log.i("異常:","文件不存在!"); } list.remove(addImgButton); // 先刪除 if (list.size() < DEFAULT_NUM) { ItemBean itemBean = new ItemBean(new ImageFileBean(file, bitmap, false), false); list.add(itemBean); if (list.size() < DEFAULT_NUM) { // 如果圖片數(shù)量還沒(méi)有達(dá)到最大值,則將添加按鈕添加到list后面 list.add(addImgButton); } } adapter.notifyDataSetChanged(); } catch (Exception e) { } } }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      27

      28

      29

      30

      31

      32

      33

      34

      35

      36

      37

      38

      39

      40

      41

      42

      43

      44

      45

      46

      47

      48

      49

      50

      51

      4.4.單文件上傳圖片核心代碼

      在form表單中enctype屬性規(guī)定了form表單在發(fā)送到服務(wù)器時(shí)候編碼方式,它有如下的三個(gè)值:

      ①application/x-www-form-urlencoded:默認(rèn)的編碼方式。但是在用文本的傳輸和MP3等大型文件的時(shí)候,使用這種編碼就顯得 效率低下。

      ②multipart/form-data:指定傳輸數(shù)據(jù)為二進(jìn)制類(lèi)型,比如圖片、mp3、文件。

      ③text/plain:純文體的傳輸。空格轉(zhuǎn)換為 “+” 加號(hào),但不對(duì)特殊字符編碼。

      private void uploadImage(final ImageFileBean fileBean) { File file = fileBean.getFile(); if (file == null) return; if(!file.exists()){ Toast.makeText(this, "文件不存在!", Toast.LENGTH_SHORT).show(); return; } viewHolder.mTVText.setText("HashCode#"+SHA256.getSHA256(file)); Log.i("文件HashCode:",SHA256.getSHA256(file)); // 準(zhǔn)備Body RequestBody requestBody = new MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart("name",file.getName())// 其他信息 .addFormDataPart("id","12,13,14")// 其他信息 .addFormDataPart("type","2")// 其他信 .addFormDataPart("file", file.getName(), RequestBody.create(MediaType.parse("multipart/form-data"), file))//文件 .build(); Request request = new Request.Builder() .url(Global.UPLOAD_URL).post(requestBody) .addHeader("user-agent", "PDA") .addHeader("x-userid", "752332")// 添加x-userid請(qǐng)求頭 .addHeader("x-sessionkey", "kjhsfjkaskfashfuiwf")// 添加x-sessionkey請(qǐng)求頭 .addHeader("x-tonce", Long.valueOf(System.currentTimeMillis()).toString())// 添加x-tonce請(qǐng)求頭 .addHeader("x-timestamp", Long.valueOf(System.currentTimeMillis()).toString())// 添加x-timestamp請(qǐng)求頭 .build(); OkHttpClient okHttpClient = new OkHttpClient(); final Message msg = myHandler.obtainMessage(); okHttpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(@NotNull Call call, @NotNull IOException e) { msg.obj = fileBean; msg.what =0; myHandler.sendMessage(msg); } @Override public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { String result = response.body().string(); Log.i("上傳圖片結(jié)果:", result); msg.obj = fileBean; if (!response.isSuccessful()) { Log.i("響應(yīng)失敗:", response.code() + ""); msg.what =1; return; } msg.what = 3; myHandler.sendMessage(msg); } }); }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      27

      28

      29

      30

      31

      32

      33

      34

      35

      36

      37

      38

      39

      40

      41

      42

      43

      44

      45

      46

      47

      48

      49

      50

      51

      52

      53

      54

      55

      4.5.多文件上傳圖片核心代碼

      請(qǐng)參考《Android 上傳圖片到服務(wù)器(多文件上傳)》

      二、服務(wù)器端

      服務(wù)端我們選擇用SpringBoot來(lái)實(shí)現(xiàn),非常簡(jiǎn)單:

      /** * 單文件上傳 * * @param file * @param model * @param request * @return */ @PostMapping("/api/upload") public String fileUpload(@RequestParam(value = "file") MultipartFile file, Model model, HttpServletRequest request, @RequestParam(value = "type") int type, @RequestHeader(value = "user-agent") String userAgent) { if (file.isEmpty()) { System.out.println("文件為空空"); } logger.info("獲得的其他參數(shù)type=" + type); logger.info("獲得的Header user-agent=" + userAgent.toString()); // 如果參數(shù)比較少可以直接在方法上使用注解@RequestParam來(lái)映射到不同的名稱(chēng)上獲得,當(dāng)然如果不用此注解,也可以定義一個(gè)與傳過(guò)來(lái)的參數(shù)名一樣的形參來(lái)獲得 // 蒜從客戶(hù)端傳過(guò)來(lái)的其他參數(shù) Enumeration names = request.getParameterNames(); while (names.hasMoreElements()) { String key = names.nextElement().toString(); logger.info("客戶(hù)端傳過(guò)來(lái)的參數(shù)#key=" + key + ",value=" + request.getParameterValues(key).toString()); } Enumeration headers = request.getHeaderNames(); while (headers.hasMoreElements()) { String key = headers.nextElement().toString(); String info = "客戶(hù)端傳過(guò)來(lái)的Header參數(shù):key=" + key + ",value=" + request.getHeader(key); logger.info(info); } // BMP、JPG、JPEG、PNG、GIF String fileName = file.getOriginalFilename(); // 文件名 logger.info("上傳文件名:" + fileName); String suffixName = fileName.substring(fileName.lastIndexOf(".")); // 后綴名 // 驗(yàn)證上傳的文件是否圖片 if (!".bmp".equalsIgnoreCase(suffixName) && !".jpg".equalsIgnoreCase(suffixName) && !".jpeg".equalsIgnoreCase(suffixName) && !".png".equalsIgnoreCase(suffixName) && !".gif".equalsIgnoreCase(suffixName)) { return "上傳失敗,請(qǐng)選擇BMP、JPG、JPEG、PNG、GIF文件!"; } fileName = UUID.randomUUID() + suffixName; // 新文件名 File dest = new File(fileName); // 如果文件的父路徑不存在,則創(chuàng)建 if (fileName.startsWith("/") && !dest.getParentFile().exists()) { dest.getParentFile().mkdirs(); } // 開(kāi)始存放文件到指定目錄去 try { file.transferTo(dest); return "上傳成功"; } catch (IOException e) { e.printStackTrace(); return "上傳失敗"; } }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      27

      28

      29

      30

      31

      32

      33

      34

      35

      36

      37

      38

      39

      40

      41

      42

      43

      44

      45

      46

      47

      48

      49

      50

      51

      52

      53

      54

      55

      56

      57

      58

      59

      60

      61

      同時(shí)在application.yml配置文件配置上傳的目錄:

      spring: servlet: multipart: location: /home/kyun/Downloads/recFiles

      1

      2

      3

      4

      Android客戶(hù)端

      文件上傳服務(wù)器

      附:HTTP請(qǐng)求報(bào)文

      用WireShark軟件對(duì)Android發(fā)起請(qǐng)求時(shí)的數(shù)據(jù)進(jìn)行攔截,得到的HTTP請(qǐng)求的報(bào)文:

      Hypertext Transfer Protocol POST /api/upload HTTP/1.1\r\n [Expert Info (Chat/Sequence): POST /api/upload HTTP/1.1\r\n] [POST /api/upload HTTP/1.1\r\n] [Severity level: Chat] [Group: Sequence] Request Method: POST Request URI: /api/upload Request Version: HTTP/1.1 user-agent: PDA\r\n x-userid: 752332\r\n x-sessionkey: kjhsfjkaskfashfuiwf\r\n x-tonce: 1591980645278\r\n x-timestamp: 1591980645278\r\n Content-Type: multipart/form-data; boundary=27fba8d3-a15a-40f6-8880-e0af1932914b\r\n Content-Length: 48558\r\n [Content length: 48558] Host: 192.168.43.120:8080\r\n Connection: Keep-Alive\r\n Accept-Encoding: gzip\r\n \r\n [Full request URI: http://192.168.43.120:8080/api/upload] [HTTP request 1/1] [Response in frame: 334] File Data: 48558 bytes MIME Multipart Media Encapsulation, Type: multipart/form-data, Boundary: "27fba8d3-a15a-40f6-8880-e0af1932914b" [Type: multipart/form-data] First boundary: --27fba8d3-a15a-40f6-8880-e0af1932914b\r\n Encapsulated multipart part: Content-Disposition: form-data; name="name"\r\n Content-Length: 36\r\n\r\n Data (36 bytes) Data: 353636386333306432636638346534613834323364383837… [Length: 36] Boundary: \r\n--27fba8d3-a15a-40f6-8880-e0af1932914b\r\n Encapsulated multipart part: Content-Disposition: form-data; name="id"\r\n Content-Length: 8\r\n\r\n Data (8 bytes) Data: 31322c31332c3134 [Length: 8] Boundary: \r\n--27fba8d3-a15a-40f6-8880-e0af1932914b\r\n Encapsulated multipart part: Content-Disposition: form-data; name="type"\r\n Content-Length: 1\r\n\r\n Data (1 byte) Data: 32 [Length: 1] Boundary: \r\n--27fba8d3-a15a-40f6-8880-e0af1932914b\r\n Encapsulated multipart part: (multipart/form-data) Content-Disposition: form-data; name="file"; filename="5668c30d2cf84e4a8423d887527fd069.jpg"\r\n Content-Type: multipart/form-data\r\n Content-Length: 47952\r\n\r\n The multipart dissector could not find a required parameter. [Expert Info (Error/Protocol): The multipart dissector could not find a required parameter.] [The multipart dissector could not find a required parameter.] [Severity level: Error] [Group: Protocol] Data (47952 bytes) Data: ffd8ffe12ee245786966000049492a00080000000b001001… [Length: 47952] Last boundary: \r\n--27fba8d3-a15a-40f6-8880-e0af1932914b--\r\n

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      27

      28

      29

      30

      31

      32

      33

      34

      35

      36

      37

      38

      39

      40

      41

      42

      43

      44

      45

      46

      47

      48

      49

      50

      51

      52

      53

      54

      55

      56

      57

      58

      59

      60

      61

      62

      63

      64

      Android

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶(hù)投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:新手語(yǔ)音入門(mén)(四): 傳統(tǒng)語(yǔ)音識(shí)別技術(shù)簡(jiǎn)介 | 隱馬爾可夫鏈 | 聲學(xué)/語(yǔ)言模型 | WFST解碼
      下一篇:全面詳細(xì)解析invoke方法的執(zhí)行和使用
      相關(guān)文章
      亚洲福利精品一区二区三区| 亚洲毛片免费观看| 久久久无码精品亚洲日韩按摩 | 亚洲AV永久无码精品一福利| 亚洲人成网站在线播放影院在线 | 亚洲国产日韩精品| 亚洲黄色三级网站| 久久狠狠高潮亚洲精品| 99人中文字幕亚洲区| 久久久久亚洲av无码专区喷水| 亚洲AV无码日韩AV无码导航| 亚洲Av永久无码精品三区在线| 亚洲av无码一区二区三区乱子伦| 久热综合在线亚洲精品| 久久久综合亚洲色一区二区三区| 亚洲第一精品福利| 久久夜色精品国产噜噜噜亚洲AV | 亚洲国产成人精品女人久久久| 亚洲国产精品狼友中文久久久| 亚洲人成无码www久久久| 国产成人亚洲综合| 亚洲色WWW成人永久网址| 亚洲国产精品久久久天堂| 亚洲VA中文字幕不卡无码| 亚洲天堂男人天堂| 亚洲午夜久久久精品电影院| 亚洲一区二区三区精品视频| 亚洲午夜精品一区二区麻豆| 豆国产96在线|亚洲| 亚洲婷婷国产精品电影人久久| 亚洲人成无码www久久久| 亚洲一区二区三区AV无码| 亚洲AV日韩AV天堂久久| 久久久久久久亚洲Av无码 | 国产成人99久久亚洲综合精品| 亚洲精品无码不卡在线播HE | 亚洲综合精品香蕉久久网97| 亚洲精品视频在线播放| 亚洲综合一区国产精品| 另类小说亚洲色图| 国产亚洲精品xxx|