android volley 上传文件

上传头像到服务器:Android普通与Volley方式实现 本文还有配套的精品资源,点击获取 简介:在Android开发中,上传用户头像至服务器是一项常规任务。本教程将介绍两种方法来实现这一功能:一种是传统的HTTP请求方式,另一种是利用Google的Volley网络库。两种方法都会涉及文件准备、构建请求、发送请求和响应处理等步骤,帮助开发者理解并实现头像上传功能。 1. Android平台头像上传方法 在移动应用中... 阅读详情

1. 实现 multipart/form-data ,不借助第三方库

  • VolleyMultipartRequest.java
    • CLASS VolleyMultipartRequest
    • CLASS VolleyMultipartRequest.DataPart
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
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
package net.simplifiedlearning.androiduploadimage;

import com.android.volley.AuthFailureError;
import com.android.volley.NetworkResponse;
import com.android.volley.ParseError;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.HttpHeaderParser;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Map;

/**
 * Created by Belal on 10/24/2017.
 */

public class VolleyMultipartRequest extends Request<NetworkResponse> {

  private final String twoHyphens = "--";
  private final String lineEnd = "\r\n";
  private final String boundary = "apiclient-" + System.currentTimeMillis();

  private Response.Listener<NetworkResponse> mListener;
  private Response.ErrorListener mErrorListener;
  private Map<String, String> mHeaders;


  public VolleyMultipartRequest(int method, String url,
                  Response.Listener<NetworkResponse> listener,
                  Response.ErrorListener errorListener) {
    super(method, url, errorListener);
    this.mListener = listener;
    this.mErrorListener = errorListener;
  }

  @Override
  public Map<String, String> getHeaders() throws AuthFailureError {
    return (mHeaders != null) ? mHeaders : super.getHeaders();
  }

  @Override
  public String getBodyContentType() {
    return "multipart/form-data;boundary=" + boundary;
  }

  @Override
  public byte[] getBody() throws AuthFailureError {
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    DataOutputStream dos = new DataOutputStream(bos);

    try {
      // populate text payload
      Map<String, String> params = getParams();
      if (params != null && params.size() > 0) {
        textParse(dos, params, getParamsEncoding());
      }

      // populate data byte payload
      Map<String, DataPart> data = getByteData();
      if (data != null && data.size() > 0) {
        dataParse(dos, data);
      }

      // close multipart form data after text and file data
      dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);

      return bos.toByteArray();
    } catch (IOException e) {
      e.printStackTrace();
    }
    return null;
  }

  /**
   * Custom method handle data payload.
   *
   * @return Map data part label with data byte
   * @throws AuthFailureError
   */
  protected Map<String, DataPart> getByteData() throws AuthFailureError {
    return null;
  }

  @Override
  protected Response<NetworkResponse> parseNetworkResponse(NetworkResponse response) {
    try {
      return Response.success(
          response,
          HttpHeaderParser.parseCacheHeaders(response));
    } catch (Exception e) {
      return Response.error(new ParseError(e));
    }
  }

  @Override
  protected void deliverResponse(NetworkResponse response) {
    mListener.onResponse(response);
  }

  @Override
  public void deliverError(VolleyError error) {
    mErrorListener.onErrorResponse(error);
  }

  /**
   * Parse string map into data output stream by key and value.
   *
   * @param dataOutputStream data output stream handle string parsing
   * @param params       string inputs collection
   * @param encoding     encode the inputs, default UTF-8
   * @throws IOException
   */
  private void textParse(DataOutputStream dataOutputStream, Map<String, String> params, String encoding) throws IOException {
    try {
      for (Map.Entry<String, String> entry : params.entrySet()) {
        buildTextPart(dataOutputStream, entry.getKey(), entry.getValue());
      }
    } catch (UnsupportedEncodingException uee) {
      throw new RuntimeException("Encoding not supported: " + encoding, uee);
    }
  }

  /**
   * Parse data into data output stream.
   *
   * @param dataOutputStream data output stream handle file attachment
   * @param data       loop through data
   * @throws IOException
   */
  private void dataParse(DataOutputStream dataOutputStream, Map<String, DataPart> data) throws IOException {
    for (Map.Entry<String, DataPart> entry : data.entrySet()) {
      buildDataPart(dataOutputStream, entry.getValue(), entry.getKey());
    }
  }

  /**
   * Write string data into header and data output stream.
   *
   * @param dataOutputStream data output stream handle string parsing
   * @param parameterName  name of input
   * @param parameterValue   value of input
   * @throws IOException
   */
  private void buildTextPart(DataOutputStream dataOutputStream, String parameterName, String parameterValue) throws IOException {
    dataOutputStream.writeBytes(twoHyphens + boundary + lineEnd);
    dataOutputStream.writeBytes("Content-Disposition: form-data; name=\"" + parameterName + "\"" + lineEnd);
    dataOutputStream.writeBytes(lineEnd);
    dataOutputStream.writeBytes(parameterValue + lineEnd);
  }

  /**
   * Write data file into header and data output stream.
   *
   * @param dataOutputStream data output stream handle data parsing
   * @param dataFile     data byte as DataPart from collection
   * @param inputName    name of data input
   * @throws IOException
   */
  private void buildDataPart(DataOutputStream dataOutputStream, DataPart dataFile, String inputName) throws IOException {
    dataOutputStream.writeBytes(twoHyphens + boundary + lineEnd);
    dataOutputStream.writeBytes("Content-Disposition: form-data; name=\"" +
        inputName + "\"; filename=\"" + dataFile.getFileName() + "\"" + lineEnd);
    if (dataFile.getType() != null && !dataFile.getType().trim().isEmpty()) {
      dataOutputStream.writeBytes("Content-Type: " + dataFile.getType() + lineEnd);
    }
    dataOutputStream.writeBytes(lineEnd);

    ByteArrayInputStream fileInputStream = new ByteArrayInputStream(dataFile.getContent());
    int bytesAvailable = fileInputStream.available();

    int maxBufferSize = 1024 * 1024;
    int bufferSize = Math.min(bytesAvailable, maxBufferSize);
    byte[] buffer = new byte[bufferSize];

    int bytesRead = fileInputStream.read(buffer, 0, bufferSize);

    while (bytesRead > 0) {
      dataOutputStream.write(buffer, 0, bufferSize);
      bytesAvailable = fileInputStream.available();
      bufferSize = Math.min(bytesAvailable, maxBufferSize);
      bytesRead = fileInputStream.read(buffer, 0, bufferSize);
    }

    dataOutputStream.writeBytes(lineEnd);
  }

  class DataPart {
    private String fileName;
    private byte[] content;
    private String type;

    public DataPart() {
    }

    DataPart(String name, byte[] data) {
      fileName = name;
      content = data;
    }

    String getFileName() {
      return fileName;
    }

    byte[] getContent() {
      return content;
    }

    String getType() {
      return type;
    }

  }
}
1.1. Upload Image to Server
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
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
package net.simplifiedlearning.androiduploadimage;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
import android.provider.Settings;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Toast;

import com.android.volley.AuthFailureError;
import com.android.volley.NetworkResponse;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.Volley;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;


public class MainActivity extends AppCompatActivity {

  //ImageView to display image selected
  ImageView imageView;

  //edittext for getting the tags input
  EditText editTextTags;

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

    //initializing views
    imageView = (ImageView) findViewById(R.id.imageView);
    editTextTags = (EditText) findViewById(R.id.editTextTags);

    //checking the permission
    //if the permission is not given we will open setting to add permission
    //else app will not open
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && ContextCompat.checkSelfPermission(this,
        Manifest.permission.READ_EXTERNAL_STORAGE)
        != PackageManager.PERMISSION_GRANTED) {
      Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
          Uri.parse("package:" + getPackageName()));
      finish();
      startActivity(intent);
      return;
    }


    //adding click listener to button
    findViewById(R.id.buttonUploadImage).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View view) {

        //if the tags edittext is empty
        //we will throw input error
        if (editTextTags.getText().toString().trim().isEmpty()) {
          editTextTags.setError("Enter tags first");
          editTextTags.requestFocus();
          return;
        }

        //if everything is ok we will open image chooser
        Intent i = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
        startActivityForResult(i, 100);
      }
    });
  }

  @Override
  protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == 100 && resultCode == RESULT_OK && data != null) {

      //getting the image Uri
      Uri imageUri = data.getData();
      try {
        //getting bitmap object from uri
        Bitmap bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), imageUri);

        //displaying selected image to imageview
        imageView.setImageBitmap(bitmap);

        //calling the method uploadBitmap to upload image
        uploadBitmap(bitmap);
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }

  /*
  * The method is taking Bitmap as an argument
  * then it will return the byte[] array for the given bitmap
  * and we will send this array to the server
  * here we are using PNG Compression with 80% quality
  * you can give quality between 0 to 100
  * 0 means worse quality
  * 100 means best quality
  * */
  public byte[] getFileDataFromDrawable(Bitmap bitmap) {
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    bitmap.compress(Bitmap.CompressFormat.PNG, 80, byteArrayOutputStream);
    return byteArrayOutputStream.toByteArray();
  }

  private void uploadBitmap(final Bitmap bitmap) {

    //getting the tag from the edittext
    final String tags = editTextTags.getText().toString().trim();

    //our custom volley request
    VolleyMultipartRequest volleyMultipartRequest = new VolleyMultipartRequest(Request.Method.POST, EndPoints.UPLOAD_URL,
        new Response.Listener<NetworkResponse>() {
          @Override
          public void onResponse(NetworkResponse response) {
            try {
              JSONObject obj = new JSONObject(new String(response.data));
              Toast.makeText(getApplicationContext(), obj.getString("message"), Toast.LENGTH_SHORT).show();
            } catch (JSONException e) {
              e.printStackTrace();
            }
          }
        },
        new Response.ErrorListener() {
          @Override
          public void onErrorResponse(VolleyError error) {
            Toast.makeText(getApplicationContext(), error.getMessage(), Toast.LENGTH_SHORT).show();
          }
        }) {

      /*
      * If you want to add more parameters with the image
      * you can do it here
      * here we have only one parameter with the image
      * which is tags
      * */
      @Override
      protected Map<String, String> getParams() throws AuthFailureError {
        Map<String, String> params = new HashMap<>();
        params.put("tags", tags);
        return params;
      }

      /*
      * Here we are passing image by renaming it with a unique name
      * */
      @Override
      protected Map<String, DataPart> getByteData() {
        Map<String, DataPart> params = new HashMap<>();
        long imagename = System.currentTimeMillis();
        params.put("pic", new DataPart(imagename + ".png", getFileDataFromDrawable(bitmap)));
        return params;
      }
    };

    //adding the request to volley
    Volley.newRequestQueue(this).add(volleyMultipartRequest);
  }
}

2. 使用 Apache httpmime 库的方法

1
compile 'org.apache.httpcomponents:httpmime:4.5.5'

或者,下载 jar 包:

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
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package com.android.volley.toolbox;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;

import com.android.volley.AuthFailureError;
import com.android.volley.NetworkResponse;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyLog;
import com.common.utils.CLog;
import com.common.utils.FileUtil;

public class MultipartRequest extends Request<String> {

  private MultipartEntity entity = new MultipartEntity();

  private final Response.Listener<String> mListener;

  private List<File> mFileParts;
  private String mFilePartName;
  private Map<String, String> mParams;
  /**
   * 单个文件
   * @param url
   * @param errorListener
   * @param listener
   * @param filePartName
   * @param file
   * @param params
   */
  public MultipartRequest(String url, Response.ErrorListener errorListener,
      Response.Listener<String> listener, String filePartName, File file,
      Map<String, String> params) {
    super(Method.POST, url, errorListener);

    mFileParts = new ArrayList<File>();
    if (file != null) {
      mFileParts.add(file);
    }
    mFilePartName = filePartName;
    mListener = listener;
    mParams = params;
    buildMultipartEntity();
  }
  /**
   * 多个文件,对应一个key
   * @param url
   * @param errorListener
   * @param listener
   * @param filePartName
   * @param files
   * @param params
   */
  public MultipartRequest(String url, Response.ErrorListener errorListener,
      Response.Listener<String> listener, String filePartName,
      List<File> files, Map<String, String> params) {
    super(Method.POST, url, errorListener);
    mFilePartName = filePartName;
    mListener = listener;
    mFileParts = files;
    mParams = params;
    buildMultipartEntity();
  }

  private void buildMultipartEntity() {
    if (mFileParts != null && mFileParts.size() > 0) {
      for (File file : mFileParts) {
        entity.addPart(mFilePartName, new FileBody(file));
      }
      long l = entity.getContentLength();
      CLog.log(mFileParts.size()+"个,长度:"+l);
    }

    try {
      if (mParams != null && mParams.size() > 0) {
        for (Map.Entry<String, String> entry : mParams.entrySet()) {
          entity.addPart(
              entry.getKey(),
              new StringBody(entry.getValue(), Charset
                  .forName("UTF-8")));
        }
      }
    } catch (UnsupportedEncodingException e) {
      VolleyLog.e("UnsupportedEncodingException");
    }
  }

  @Override
  public String getBodyContentType() {
    return entity.getContentType().getValue();
  }

  @Override
  public byte[] getBody() throws AuthFailureError {
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    try {
      entity.writeTo(bos);
    } catch (IOException e) {
      VolleyLog.e("IOException writing to ByteArrayOutputStream");
    }
    return bos.toByteArray();
  }

  @Override
  protected Response<String> parseNetworkResponse(NetworkResponse response) {
    CLog.log("parseNetworkResponse");
    if (VolleyLog.DEBUG) {
      if (response.headers != null) {
        for (Map.Entry<String, String> entry : response.headers
            .entrySet()) {
          VolleyLog.d(entry.getKey() + "=" + entry.getValue());
        }
      }
    }

    String parsed;
    try {
      parsed = new String(response.data,
          HttpHeaderParser.parseCharset(response.headers));
    } catch (UnsupportedEncodingException e) {
      parsed = new String(response.data);
    }
    return Response.success(parsed,
        HttpHeaderParser.parseCacheHeaders(response));
  }


  /*
   * (non-Javadoc)
   * 
   * @see com.android.volley.Request#getHeaders()
   */
  @Override
  public Map<String, String> getHeaders() throws AuthFailureError {
    VolleyLog.d("getHeaders");
    Map<String, String> headers = super.getHeaders();

    if (headers == null || headers.equals(Collections.emptyMap())) {
      headers = new HashMap<String, String>();
    }


    return headers;
  }

  @Override
  protected void deliverResponse(String response) {
    mListener.onResponse(response);
  }
}

3. github - anggadarkprince 的方法

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
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import java.io.ByteArrayOutputStream;

/**
 * Sketch Project Studio
 * Created by Angga on 12/04/2016 14.27.
 */
public class AppHelper {  
  
  /**
   * Turn drawable resource into byte array.
   *
   * @param context parent context
   * @param id    drawable resource id
   * @return byte array
   */
  public static byte[] getFileDataFromDrawable(Context context, int id) {
    Drawable drawable = ContextCompat.getDrawable(context, id);
    Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    bitmap.compress(Bitmap.CompressFormat.PNG, 0, byteArrayOutputStream);
    return byteArrayOutputStream.toByteArray();
  }

  /**
   * Turn drawable into byte array.
   *
   * @param drawable data
   * @return byte array
   */
  public static byte[] getFileDataFromDrawable(Context context, Drawable drawable) {
    Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    bitmap.compress(Bitmap.CompressFormat.JPEG, 80, byteArrayOutputStream);
    return byteArrayOutputStream.toByteArray();
  }
}
  • VolleyMultipartRequest.java
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
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
package com.sketchproject.infogue.modules;

import com.android.volley.AuthFailureError;
import com.android.volley.NetworkResponse;
import com.android.volley.ParseError;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.HttpHeaderParser;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Map;

/**
 * Custom request to make multipart header and upload file.
 * 
 * Sketch Project Studio
 * Created by Angga on 27/04/2016 12.05.
 */
public class VolleyMultipartRequest extends Request<NetworkResponse> {
  private final String twoHyphens = "--";
  private final String lineEnd = "\r\n";
  private final String boundary = "apiclient-" + System.currentTimeMillis();

  private Response.Listener<NetworkResponse> mListener;
  private Response.ErrorListener mErrorListener;
  private Map<String, String> mHeaders;

  /**
   * Default constructor with predefined header and post method.
   *
   * @param url       request destination
   * @param headers     predefined custom header
   * @param listener    on success achieved 200 code from request
   * @param errorListener on error http or library timeout
   */
  public VolleyMultipartRequest(String url, Map<String, String> headers,
                  Response.Listener<NetworkResponse> listener,
                  Response.ErrorListener errorListener) {
    super(Method.POST, url, errorListener);
    this.mListener = listener;
    this.mErrorListener = errorListener;
    this.mHeaders = headers;
  }

  /**
   * Constructor with option method and default header configuration.
   *
   * @param method    method for now accept POST and GET only
   * @param url       request destination
   * @param listener    on success event handler
   * @param errorListener on error event handler
   */
  public VolleyMultipartRequest(int method, String url,
                  Response.Listener<NetworkResponse> listener,
                  Response.ErrorListener errorListener) {
    super(method, url, errorListener);
    this.mListener = listener;
    this.mErrorListener = errorListener;
  }

  @Override
  public Map<String, String> getHeaders() throws AuthFailureError {
    return (mHeaders != null) ? mHeaders : super.getHeaders();
  }

  @Override
  public String getBodyContentType() {
    return "multipart/form-data;boundary=" + boundary;
  }

  @Override
  public byte[] getBody() throws AuthFailureError {
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    DataOutputStream dos = new DataOutputStream(bos);

    try {
      // populate text payload
      Map<String, String> params = getParams();
      if (params != null && params.size() > 0) {
        textParse(dos, params, getParamsEncoding());
      }

      // populate data byte payload
      Map<String, DataPart> data = getByteData();
      if (data != null && data.size() > 0) {
        dataParse(dos, data);
      }

      // close multipart form data after text and file data
      dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);

      return bos.toByteArray();
    } catch (IOException e) {
      e.printStackTrace();
    }
    return null;
  }

  /**
   * Custom method handle data payload.
   *
   * @return Map data part label with data byte
   * @throws AuthFailureError
   */
  protected Map<String, DataPart> getByteData() throws AuthFailureError {
    return null;
  }

  @Override
  protected Response<NetworkResponse> parseNetworkResponse(NetworkResponse response) {
    try {
      return Response.success(
          response,
          HttpHeaderParser.parseCacheHeaders(response));
    } catch (Exception e) {
      return Response.error(new ParseError(e));
    }
  }

  @Override
  protected void deliverResponse(NetworkResponse response) {
    mListener.onResponse(response);
  }

  @Override
  public void deliverError(VolleyError error) {
    mErrorListener.onErrorResponse(error);
  }

  /**
   * Parse string map into data output stream by key and value.
   *
   * @param dataOutputStream data output stream handle string parsing
   * @param params       string inputs collection
   * @param encoding     encode the inputs, default UTF-8
   * @throws IOException
   */
  private void textParse(DataOutputStream dataOutputStream, Map<String, String> params, String encoding) throws IOException {
    try {
      for (Map.Entry<String, String> entry : params.entrySet()) {
        buildTextPart(dataOutputStream, entry.getKey(), entry.getValue());
      }
    } catch (UnsupportedEncodingException uee) {
      throw new RuntimeException("Encoding not supported: " + encoding, uee);
    }
  }

  /**
   * Parse data into data output stream.
   *
   * @param dataOutputStream data output stream handle file attachment
   * @param data       loop through data
   * @throws IOException
   */
  private void dataParse(DataOutputStream dataOutputStream, Map<String, DataPart> data) throws IOException {
    for (Map.Entry<String, DataPart> entry : data.entrySet()) {
      buildDataPart(dataOutputStream, entry.getValue(), entry.getKey());
    }
  }

  /**
   * Write string data into header and data output stream.
   *
   * @param dataOutputStream data output stream handle string parsing
   * @param parameterName  name of input
   * @param parameterValue   value of input
   * @throws IOException
   */
  private void buildTextPart(DataOutputStream dataOutputStream, String parameterName, String parameterValue) throws IOException {
    dataOutputStream.writeBytes(twoHyphens + boundary + lineEnd);
    dataOutputStream.writeBytes("Content-Disposition: form-data; name=\"" + parameterName + "\"" + lineEnd);
    //dataOutputStream.writeBytes("Content-Type: text/plain; charset=UTF-8" + lineEnd);
    dataOutputStream.writeBytes(lineEnd);
    dataOutputStream.writeBytes(parameterValue + lineEnd);
  }

  /**
   * Write data file into header and data output stream.
   *
   * @param dataOutputStream data output stream handle data parsing
   * @param dataFile     data byte as DataPart from collection
   * @param inputName    name of data input
   * @throws IOException
   */
  private void buildDataPart(DataOutputStream dataOutputStream, DataPart dataFile, String inputName) throws IOException {
    dataOutputStream.writeBytes(twoHyphens + boundary + lineEnd);
    dataOutputStream.writeBytes("Content-Disposition: form-data; name=\"" +
        inputName + "\"; filename=\"" + dataFile.getFileName() + "\"" + lineEnd);
    if (dataFile.getType() != null && !dataFile.getType().trim().isEmpty()) {
      dataOutputStream.writeBytes("Content-Type: " + dataFile.getType() + lineEnd);
    }
    dataOutputStream.writeBytes(lineEnd);

    ByteArrayInputStream fileInputStream = new ByteArrayInputStream(dataFile.getContent());
    int bytesAvailable = fileInputStream.available();

    int maxBufferSize = 1024 * 1024;
    int bufferSize = Math.min(bytesAvailable, maxBufferSize);
    byte[] buffer = new byte[bufferSize];

    int bytesRead = fileInputStream.read(buffer, 0, bufferSize);

    while (bytesRead > 0) {
      dataOutputStream.write(buffer, 0, bufferSize);
      bytesAvailable = fileInputStream.available();
      bufferSize = Math.min(bytesAvailable, maxBufferSize);
      bytesRead = fileInputStream.read(buffer, 0, bufferSize);
    }

    dataOutputStream.writeBytes(lineEnd);
  }

  /**
   * Simple data container use for passing byte file
   */
  public class DataPart {
    private String fileName;
    private byte[] content;
    private String type;

    /**
     * Default data part
     */
    public DataPart() {
    }

    /**
     * Constructor with data.
     *
     * @param name label of data
     * @param data byte data
     */
    public DataPart(String name, byte[] data) {
      fileName = name;
      content = data;
    }

    /**
     * Constructor with mime data type.
     *
     * @param name   label of data
     * @param data   byte data
     * @param mimeType mime data like "image/jpeg"
     */
    public DataPart(String name, byte[] data, String mimeType) {
      fileName = name;
      content = data;
      type = mimeType;
    }

    /**
     * Getter file name.
     *
     * @return file name
     */
    public String getFileName() {
      return fileName;
    }

    /**
     * Setter file name.
     *
     * @param fileName string file name
     */
    public void setFileName(String fileName) {
      this.fileName = fileName;
    }

    /**
     * Getter content.
     *
     * @return byte file data
     */
    public byte[] getContent() {
      return content;
    }

    /**
     * Setter content.
     *
     * @param content byte file data
     */
    public void setContent(byte[] content) {
      this.content = content;
    }

    /**
     * Getter mime type.
     *
     * @return mime type
     */
    public String getType() {
      return type;
    }

    /**
     * Setter mime type.
     *
     * @param type mime type
     */
    public void setType(String type) {
      this.type = type;
    }
  }
}
  • VolleySingleton.java
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
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
package com.sketchproject.infogue.modules;

import android.content.Context;
import android.graphics.Bitmap;
import android.support.v4.util.LruCache;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.Volley;

/**
 * Singleton volley to populate request into single queue.
 * 
 * Sketch Project Studio
 * Created by Angga on 22/04/2016 22.58.
 */
public class VolleySingleton {
  private static VolleySingleton mInstance;
  private RequestQueue mRequestQueue;
  private ImageLoader mImageLoader;
  private static Context mCtx;

  /**
   * Private constructor, only initialization from getInstance.
   *
   * @param context parent context
   */
  private VolleySingleton(Context context) {
    mCtx = context;
    mRequestQueue = getRequestQueue();

    mImageLoader = new ImageLoader(mRequestQueue,
        new ImageLoader.ImageCache() {
          private final LruCache<String, Bitmap> cache = new LruBitmapCache(mCtx);

          @Override
          public Bitmap getBitmap(String url) {
            return cache.get(url);
          }

          @Override
          public void putBitmap(String url, Bitmap bitmap) {
            cache.put(url, bitmap);
          }
        });
  }

  /**
   * Singleton construct design pattern.
   *
   * @param context parent context
   * @return single instance of VolleySingleton
   */
  public static synchronized VolleySingleton getInstance(Context context) {
    if (mInstance == null) {
      mInstance = new VolleySingleton(context);
    }
    return mInstance;
  }

  /**
   * Get current request queue.
   *
   * @return RequestQueue
   */
  public RequestQueue getRequestQueue() {
    if (mRequestQueue == null) {
      // getApplicationContext() is key, it keeps you from leaking the
      // Activity or BroadcastReceiver if someone passes one in.
      mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext());
    }
    return mRequestQueue;
  }

  /**
   * Add new request depend on type like string, json object, json array request.
   *
   * @param req new request
   * @param <T> request type
   */
  public <T> void addToRequestQueue(Request<T> req) {
    getRequestQueue().add(req);
  }

  /**
   * Get image loader.
   *
   * @return ImageLoader
   */
  public ImageLoader getImageLoader() {
    return mImageLoader;
  }
}
  • MainActivity.java
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
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
/**
 * Sketch Project Studio
 * Created by Angga 20/04/2016 19:32
 */
public class MainActivity extends AppCompatActivity {
  private EditText mNameInput;
  private EditText mLocationInput;
  private EditText mAboutInput;
  private EditText mContact;
  
  private ImageView mAvatarImage;
  private ImageView mCoverImage;
  
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    
    mNameInput = (EditText) findViewById(R.id.input_name);
    mLocationInput = (EditText) findViewById(R.id.input_location);
    mAboutInput = (EditText) findViewById(R.id.input_about);
    mContact = (EditText) findViewById(R.id.input_contact);
    
    mAvatarImage = (ImageView) findViewById(R.id.avatar);
    mCoverImage = (ImageView) findViewById(R.id.cover);
    
    // do anything before post data.. or triggered after button clicked
    saveProfileAccount();
  }
  
  private void saveProfileAccount() {
    // loading or check internet connection or something...
    // ... then
    String url = "http://www.angga-ari.com/api/something/awesome";
    VolleyMultipartRequest multipartRequest = new VolleyMultipartRequest(Request.Method.POST, url, new Response.Listener<NetworkResponse>() {
      @Override
      public void onResponse(NetworkResponse response) {
        String resultResponse = new String(response.data);
        try {
          JSONObject result = new JSONObject(resultResponse);
          String status = result.getString("status");
          String message = result.getString("message");

          if (status.equals(Constant.REQUEST_SUCCESS)) {
            // tell everybody you have succed upload image and post strings
            Log.i("Messsage", message);
          } else {
            Log.i("Unexpected", message);
          }
        } catch (JSONException e) {
          e.printStackTrace();
        }
      }
    }, new Response.ErrorListener() {
      @Override
      public void onErrorResponse(VolleyError error) {
        NetworkResponse networkResponse = error.networkResponse;
        String errorMessage = "Unknown error";
        if (networkResponse == null) {
          if (error.getClass().equals(TimeoutError.class)) {
            errorMessage = "Request timeout";
          } else if (error.getClass().equals(NoConnectionError.class)) {
            errorMessage = "Failed to connect server";
          }
        } else {
          String result = new String(networkResponse.data);
          try {
            JSONObject response = new JSONObject(result);
            String status = response.getString("status");
            String message = response.getString("message");
            
            Log.e("Error Status", status);
            Log.e("Error Message", message);

            if (networkResponse.statusCode == 404) {
              errorMessage = "Resource not found";
            } else if (networkResponse.statusCode == 401) {
              errorMessage = message+" Please login again";
            } else if (networkResponse.statusCode == 400) {
              errorMessage = message+ " Check your inputs";
            } else if (networkResponse.statusCode == 500) {
              errorMessage = message+" Something is getting wrong";
            }
          } catch (JSONException e) {
            e.printStackTrace();
          }
        }
        Log.i("Error", errorMessage);
        error.printStackTrace();
      }
    }) {
      @Override
      protected Map<String, String> getParams() {
        Map<String, String> params = new HashMap<>();
        params.put("api_token", "gh659gjhvdyudo973823tt9gvjf7i6ric75r76");
        params.put("name", mNameInput.getText().toString());
        params.put("location", mLocationInput.getText().toString());
        params.put("about", mAvatarInput.getText().toString());
        params.put("contact", mContactInput.getText().toString());
        return params;
      }

      @Override
      protected Map<String, DataPart> getByteData() {
        Map<String, DataPart> params = new HashMap<>();
        // file name could found file base or direct access from real path
        // for now just get bitmap data from ImageView
        params.put("avatar", new DataPart("file_avatar.jpg", AppHelper.getFileDataFromDrawable(getBaseContext(), mAvatarImage.getDrawable()), "image/jpeg"));
        params.put("cover", new DataPart("file_cover.jpg", AppHelper.getFileDataFromDrawable(getBaseContext(), mCoverImage.getDrawable()), "image/jpeg"));

        return params;
      }
    };

    VolleySingleton.getInstance(getBaseContext()).addToRequestQueue(multipartRequest);
  }
}
android volley 实现上传图片文件到服务器 android volley 实现上传文件到服务器,通过表单提交的方式,如果gradle的问题,最好重新建个项目,考进去代码, 立即下载

相关推荐

Android volley 文件上传

android volley 实现上传文件到服务器,通过表单提交的方式

Android volley 解析(三)之文件上传篇

Android volley 解析(三)之文件上传篇 分类: volley 解析2015-03-05 08:22 931人阅读 评论(3) 收藏 举报 上传volley表单 目录(?)[-] 数据格式文件实体类Volley 对文件数据的封装文件上传接口图片上传验证 前面我们讲了如何通过 volley 实现表单的提交,而这篇文章跟上一篇衔

安卓学习乐园 880

Android使用Volley实现上传文件功能

一个项目中用到的使用Volley上传头像文件的例子,供大家参考,具体内容如下 /** * Created by wangshihui on 2015/11/30. * 上传文件 * url:..... method:post 参数:file 接口给的参数:file 就是表单的key,传给mFilePartName; 这是个测试类,测试上传头像没有问题,适合只上传单个文件的场景 */ public class MultipartRequest extends Request { private String mHeader; privat

Android Volley文件上传(一)

对于Volley的使用这里不做介绍,不清楚的自己找资料了解,我给大家介绍的是使用Volley网络框架来实现上传文件功能,因为volley没有自带上传文件功能,因此只能自己实现。而实现Volley文件上传前,必须了解表单的提交。(一)表单提交要了解表单提交必须知道表单提交的数据格式是什么样,从网站抓取一条数据,分析其表单的数据格式Connection: keep-alive Content-Lengt

hong15007046964的专栏 1744

Cisco Packet Tracer 7.3免登录技巧:3种方法快速跳过注册界面(附详细截图)

本文针对Cisco Packet Tracer 7.3启动时遇到的登录界面问题,提供了三种合法高效的解决方案。详细介绍了如何利用官方访客模式快速启动、注册思科账号实现一劳永逸登录,以及通过调整本地安装配置优化启动流程,帮助网络学习者与备考者绕过繁琐步骤,快速进入实验环境。

weixin_29305337的博客 738

别再傻傻分不清了!Anaconda和Miniconda到底选哪个?附保姆级安装与镜像源配置(Windows/Mac)

本文详细解析了Anaconda和Miniconda的核心差异,包括体积、预装内容、系统资源占用等关键维度,并提供了针对不同需求的决策框架。附有Windows和Mac双平台的详细安装与配置指南,帮助用户根据实际需求选择最适合的conda工具,提升开发效率。

weixin_30315723的博客 426

android volley文件上传,android volley ,多文件 表单上传

public static void createFeed( FeedEntity feed, List files){final Map params = new HashMap();params.put("userFrom", feed.getUserFrom()+"");params.put("userNickname", feed.getUserNickname());params.p...

weixin_39742471的博客 159

深入C6678启动流程:从BootRom参数表到多核镜像部署的完整解析

本文深入解析TMS320C6678 DSP的启动流程,从BootRom参数表到多核镜像部署的全过程。详细介绍了七种启动模式的工程选择、多核镜像构建技巧以及二级引导策略,帮助开发者掌握C6678的启动机制与调试方法,提升嵌入式系统开发效率。

weixin_30505225的博客 610

Android中利用volley同时上传文件和文本参数

Android中利用volley同时上传文件和文本参数 前言 在做项目时利用了Volley网络库与服务器之间进行通信,但Volley并没有现成的上传文件的Request可以调用,网上搜索一圈发现很多都是基于HttpClient进行自定义Request封装,可是无奈android 6.0之后移除了HttpClient的代码,只能自己基于HttpURLConnection封装一个Request使用

Reed_优快云的博客 2742

VMware虚拟机调用NVIDIA显卡全攻略:从3D加速到直通与vGPU

显卡虚拟化技术是虚拟化领域的关键组成部分,它通过在虚拟机中模拟或直接分配物理GPU资源,实现了图形渲染与通用计算的加速。其核心原理在于通过Hypervisor层对GPU指令进行拦截、转换或直接映射,从而平衡资源隔离与性能损耗。这项技术的核心价值在于提升资源利用率、实现计算环境隔离,并支持AI训练、3D渲染、科学计算等高性能负载的灵活部署。在应用场景上,轻量级图形界面加速、CUDA学习验证适合使用虚拟3D加速;而追求极致性能的机器学习训练、专业渲染则需依赖PCIe直通技术;对于企业级GPU资源池化与多租户共享

weixin_32308101的博客 997

HBuilderX结合Android Studio实现Uniapp安卓Apk高效离线打包指南

本文提供了一份详细的Uniapp安卓Apk离线打包实战指南。通过整合HBuilderX与Android Studio,手把手教你搭建本地编译环境、配置原生工程、执行核心打包流程,并分享快速更新资源和排查常见问题的技巧,助你彻底摆脱云端打包的排队依赖,实现高效、可控的应用构建。

weixin_28835551的博客 551

Android使用Volley上传文件

一个项目中用到的使用Volley上传头像文件的例子 /** * Created by wangshihui on 2015/11/30. * 上传文件 * url:..... method:post 参数:file 接口给的参数:file 就是表单的key,传给mFilePartName; 这是个测试类,测试上传头像没有问题,适合只上传单个文件的场景

聚精会神搞建设 5086

Python爬虫实战:5分钟搞定Web of Science文献批量导出与解析(附完整代码)

本文提供了一套利用Python爬虫技术,快速实现Web of Science文献批量提取与解析的完整方案。通过解析官方导出的HTML文件,结合BeautifulSoup和pandas库,可将文献数据自动转换为结构化的Excel/CSV表格,帮助科研人员高效管理文献信息,节省大量手动整理时间。

weixin_29050513的博客 1164

android volley 上传图片 和参数,android Volley 上传文件上传图片

android volley 实现上传文件功能Volley不解释了吧, android 官方的一个网络请求库.源代码的地址在:git@github.com:com314159/VolleyMultiPartRequest.git上面的是ssh下面的是http地址https://github.com/com314159/VolleyMultiPartRequest是根据https://github....

weixin_29002209的博客 147

别再凭感觉了!手把手教你用Excel搞定激光器MPE与NOHD安全计算(附模板下载)

本文详细介绍了如何利用Excel自动化计算激光器的MPE(最大允许照射量)和NOHD(标称眼危害距离),提升激光安全评估的效率和准确性。通过实战案例和模板下载,帮助工程师快速掌握GB/T 7247标准中的关键计算,避免常见错误,适用于激光测距仪等设备的安评工作。

weixin_30954265的博客 465

【ArcGIS Engine二次开发】入门基础(2):ArcGIS开发方式(VBA、DLL、Add-in、Engine)对比

文章目录2.1 使用VBA进行桌面软件开发2.1.1 VBA开发方式2.1.1 VBA代码的安全性2.2 使用DLL进行桌面软件开发2.2.1 DLL开发方式2.2.2 DLL功能的应用部署2.3 使用Add-in进行桌面软件开发2.4 使用ArcGIS Engine构建独立应用程序 对于桌面GIS应用来说,ArcGIS平台不仅提供了用于开发独立运行的GIS应用软件的二次开发工包ArcGIS Engine,还提供了对ArcGIS Deskop桌面软件进行扩展的开发的话,在开发个具体的GIS 行业应用软件时

智慧空间实验室 6507

(Android)安卓form-data形式上传二进制(binary)文件Volley

本文使用的是Volley网络通讯框架 背景:之前一直是做Web前端开发,现要写一个android程序,web端有现成的上传文件接口,是常用的new FormData()形式,新写的android也要一样使用,可是我用camer2拍照,最终得到的是nv21,束手无策中... 网页multipart/form-data形式上传的二进制文件流,安卓咋整 一、了解form-data的格式要求 Content-Type:multipart/form-data; boundary=yourselfDefi

Jone's Area 4337

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

Android中使用Volley上传文件的源码

1、上传文件         Volley是不支持文件上传的,如果我们非要使用Volley支持文件上传,除了前面有一篇文章说明的那样,将文件转换为流之外,还可以扩展Volley的接口。今天,本文要做的就是扩展Volley的接口,让它支持文件的上传。 2、Volley框架图 额,Volley的框架图网上很多了,我找了一张过来,看看就好,这样下面的代码会更加容易理解。

ljtyzhr的专栏 1106

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

android volley 上传图片 和参数,Android使用Volley实现上传文件功能

一个项目中用到的使用Volley上传头像文件的例子,供大家参考,具体内容如下/*** Created by wangshihui on 2015/11/30.* 上传文件* url:.....method:post参数:file接口给的参数:file 就是表单的key,传给mFilePartName;这是个测试类,测试上传头像没有问题,适合只上传单个文件的场景*/public class Mult...

weixin_32744285的博客 214

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

android volley 上传图片,SpringMVC+Volley实现Android端上传文件

Android端使用的是Volley的Request基类,实现它的几个方法,以拼接表单的方式来上传文件服务器端则是采用Spring框架来接收文件。一、Android:三个类:MainActivity,PostRequest,UploadFileRequest.1.PostRequest继承自Request,实现它的deliverResponse和parseNetworkResponse以及getP...

weixin_36051940的博客 218

Android Volley框架定制PostUploadRequest上传文件(图片)

发现问题 项目中有发表动态的功能,该功能可以将文本和图片上传至服务器。 Volley通过定制PostUploadRequest实现文件上传的功能,本文以一张图片上传为例。 数据格式 以下为项目中图片上传实例的数据格式 多张图片上传可通过添加——WebKitFormBoundary 内容实现 POST /CloudLife/user/social HTTP/1.1 Host: lo

fjnu_se的博客 2200

android 文件上传功能,Android使用Volley实现上传文件功能

一个项目中用到的使用Volley上传头像文件的例子,供大家参考,具体内容如下/*** Created by wangshihui on 2015/11/30.* 上传文件* url:.....method:post参数:file接口给的参数:file 就是表单的key,传给mFilePartName;这是个测试类,测试上传头像没有问题,适合只上传单个文件的场景*/public class Mult...

weixin_42366319的博客 238
上一篇: nikto 进行web安全扫描
下一篇: notepad++ 删除空行
NetMicrobe
博客等级 码龄22年 27粉丝 79原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值