I am working on a piece of code to do multipart form data POST request, which in my case is just to upload an image to server with parameters. Here's what I have now:
I have a button to trigger the multipart request, in the button OnClickListener, I have this code to spin a new thread:
new Thread(new Runnable(){
@Override
public void run() {
String photoUri = getPhotoUri();
String url = getEndPointUrl();
try {
NewPostRequest.postFile(url, photoUri, <Other Params...>);
} catch (Exception e) {
// Exception Handling
}
}).start();
And the NewPostRequest.postFile
is just using Apache Http Client to make a request, basically like below:
HttpClient client = new DefaultHttpClient();
HttpPost post = new HttpPost(url);
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
File file = new File(fileUri);
FileBody fb = new FileBody(file);
builder.addPart("file", fb);
builder.addTextBody("param", otherParam);
HttpEntity entity = builder.build();
post.setEntity(entity);
HttpResponse response = client.execute(post);
I need to spin a new thread everytime because the recent Android releases doesn't let program to make http requests on UI thread. However, I really against to spin a random thread and let it out of control like the code above. I have tried to use Google Volley library, but it is not a handful tool when uploading large data files like image.
I was wondering what I should do to make this call more manageable?
===== UPDATE =====
I switched to use AsyncTask and it works OK for now. I will keep this question open to see if any one has better approach.
HTTP has always been a pain point in Android. Fortunately, we have many great libraries that take care of all the hard parts.
Try out Ion.
It allows you to easily do Multi-Part requests in a background thread and let's you get callbacks on the main thread when the request is complete.
It also does other cool stuff like intelligent caching, automatic request cancellation when the calling Context goes out of scope etc.
P.S: Retrofit is another great library but I prefer Ion myself. Just a matter of preference.
The solution for the above problem related to uploading .db(any extension) file to the server : Below are the steps to upload a file :
1 :- new AsynUpload().execute();
2 :-
class AsynUpload extends AsyncTask<Void,Integer,String>
{
String result="";
ProgressDialog dialog=null;
String iFileName = CONST.USER_NAME+".db";
String lineEnd = "\r\n";
String twoHyphens = "--";
String boundary = "*****";
String Tag="fSnd";
@Override
protected void onPreExecute()
{
Log.i("AsynUpload is callinmg...", "calling");
dialog=new ProgressDialog(Upload_Database.this);
dialog.setMessage("File uploading...");
dialog.setCancelable(false);
dialog.show();
}
@Override
protected String doInBackground(Void... params) {
try
{
UTILITIES.copyDBToSDCard();
String selectedFilePath = "/data/data/com.DxS.androidSunTec.visioapp/databases/"+CONST.USER_NAME+".db";
FileInputStream fstrm = new FileInputStream(selectedFilePath);
URL connectURL = new URL(CUSTOM_URL.UPLOAD_URL+"Default.aspx");
HttpURLConnection conn = (HttpURLConnection)connectURL.openConnection();
// Allow Inputs
conn.setDoInput(true);
// Allow Outputs
conn.setDoOutput(true);
// Don't use a cached copy.
conn.setUseCaches(false);
// Use a post method.
conn.setRequestMethod("POST");
conn.setRequestProperty("Connection", "Keep-Alive");
conn.setRequestProperty("Content-Type", "multipart/form-data;boundary="+boundary);
conn.setRequestProperty("FILE_NAME", ""+CONST.USER_NAME+".db");
DataOutputStream dos = new DataOutputStream(conn.getOutputStream());
dos.writeBytes(twoHyphens + boundary + lineEnd);
dos.writeBytes("Content-Disposition: form-data; name=\"title\""+ lineEnd);
dos.writeBytes(lineEnd);
dos.writeBytes(""+CONST.USER_NAME);
dos.writeBytes(lineEnd);
dos.writeBytes(twoHyphens + boundary + lineEnd);
dos.writeBytes("Content-Disposition: form-data; name=\"description\""+ lineEnd);
dos.writeBytes(lineEnd);
dos.writeBytes(loc_code+"~"+user_code+"~"+fyid);
dos.writeBytes(lineEnd);
dos.writeBytes(twoHyphens + boundary + lineEnd);
dos.writeBytes("Content-Disposition: form-data; name=\"uploadedfile\";filename=\"" + iFileName +"\"" + lineEnd);
dos.writeBytes(lineEnd);
// create a buffer of maximum size
int bytesAvailable = fstrm.available();
int maxBufferSize = 1024;
int bufferSize = Math.min(bytesAvailable, maxBufferSize);
byte[ ] buffer = new byte[bufferSize];
// read file and write it into form...
int bytesRead = fstrm.read(buffer, 0, bufferSize);
while (bytesRead > 0)
{
dos.write(buffer, 0, bufferSize);
bytesAvailable = fstrm.available();
bufferSize = Math.min(bytesAvailable,maxBufferSize);
bytesRead = fstrm.read(buffer, 0,bufferSize);
}
dos.writeBytes(lineEnd);
dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);
// close streams
fstrm.close();
// 103424
dos.flush();
InputStream is = conn.getInputStream();
// retrieve the response from server
int ch;
StringBuffer b =new StringBuffer();
while( ( ch = is.read() ) != -1 ){ b.append( (char)ch ); }
String s=b.toString();
Log.i("Response",s);
dos.close();
result="OK";
}
catch (MalformedURLException ex)
{
result = "MalformedURLException";
Log.i(Tag, "URL error: " + ex.getMessage(), ex);
}
catch (IOException ioe)
{
result = "IOException";
Log.i(Tag, "IO error: " + ioe.getMessage(), ioe);
}
catch(Exception e)
{
Log.e("Exception","Exception"+e.getMessage());
result="FAILURE";
}
finally
{
if (result.equalsIgnoreCase("OK"))
{
File file = new File("/data/data/com.test.app/databases/"+CONST.USER_NAME+".db");
if(file.exists())
{
file.delete();
Log.i("uploading database file Deleted from sd card :", "deleted");
}
file = new File("/data/data/com.test.app/databases/"+DatabaseHelper.DB_NAME);
if(file.exists())
{
file.delete();
Log.i("Original database file Deleted from sd card :", "deleted");
}
MyActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
AlertDialog.Builder builder = new AlertDialog.Builder(MyActivity.this);
LayoutInflater inflater = getLayoutInflater();
View vw = inflater.inflate(R.layout.custom_title, null);
builder.setCustomTitle(vw);
builder.setMessage("File uploaded successfully!")
.setCancelable(false)
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
finish();
}
});
builder.create();
builder.show();
}
});
}
else
{
MyActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
AlertDialog.Builder builder = new AlertDialog.Builder(MyActivity.this);
LayoutInflater inflater = getLayoutInflater();
View vw = inflater.inflate(R.layout.custom_title, null);
builder.setCustomTitle(vw);
builder.setMessage("File uploading failed, please try again!")
.setCancelable(false)
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
builder.create();
builder.show();
}
});
}
}
return result;
}
@Override
protected void onProgressUpdate(Integer... values)
{
super.onProgressUpdate(values);
// dialog.incrementProgressBy(5);
}
@Override
protected void onPostExecute(String result)
{
dialog.dismiss();
if (result.equalsIgnoreCase("OK"))
{
}
else
{
}
}
}
3.) UTILITIES class:
public static void copyDBToSDCard() {
try {
InputStream myInput = new FileInputStream("/data/data/com.DxS.androidSunTec.visioapp/databases/"+DatabaseHelper.DB_NAME);
Log.i("sd card path: ", ""+Environment.getExternalStorageDirectory().getPath().toString());
// File file = new File(Environment.getExternalStorageDirectory().getPath()+"/"+CONST.USER_NAME+".db");
File file = new File("/data/data/com.DxS.androidSunTec.visioapp/databases/"+CONST.USER_NAME+".db");
if (!file.exists()){
try {
file.createNewFile();
} catch (IOException e) {
Log.i("FO","File creation failed for " + file);
}
}
// OutputStream myOutput = new FileOutputStream(Environment.getExternalStorageDirectory().getPath()+"/"+CONST.USER_NAME+".db");
OutputStream myOutput = new FileOutputStream("/data/data/com.DxS.androidSunTec.visioapp/databases/"+CONST.USER_NAME+".db");
byte[] buffer = new byte[1024];
int length;
while ((length = myInput.read(buffer))>0){
myOutput.write(buffer, 0, length);
}
//Close the streams
myOutput.flush();
myOutput.close();
myInput.close();
Log.i("FO","copied");
} catch (Exception e) {
Log.i("FO","exception="+e);
}
}
1) Create Native Android Plugin for uploading Multiple files along with JSON Object. 2) Concept is Multipart file upload. 3) Create for offline mode sync. 4) Phonegap and Android Native Code.
Javascript Call for Native Andorid Code
alert(window.FilesUpload.sendFiles(JSON.stringify(jsonObj)));
Below is the Android Plugin (FilesUpload.java)
package com.yourpackagename.core;
import java.io.*;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.json.JSONArray;
import org.json.JSONObject;
public class FilesUpload
{
public FilesUpload() {
}
public String sendFiles(String s) {
String responseBody = "";
try
{
JSONObject jsonObject = new JSONObject(s);
int len_outer = jsonObject.getJSONArray("electricityExpenseManagement").getJSONObject(0).length();
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost("http://yourservername.com/php_file_upload/file_upload2.php");
MultipartEntity reqEntity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE);
StringBody elecExpObj = new StringBody(s);
reqEntity.addPart("elecExpObj", elecExpObj);
for(int i=0;i<len_outer;i++)
{
int bill_count = jsonObject.getJSONArray("electricityExpenseManagement").getJSONObject(i).getJSONArray("electricityExpenseBillInfoData").length();
for(int j=0;j<bill_count;j++)
{
String sourceFileUri = jsonObject.getJSONArray("electricityExpenseManagement").getJSONObject(0).getJSONArray("electricityExpenseBillInfoData").getJSONObject(j).getString("image_path");
String partName = jsonObject.getJSONArray("electricityExpenseManagement").getJSONObject(i).getJSONArray("electricityExpenseBillInfoData").getJSONObject(j).getString("image_base64_encode");
FileBody bin = new FileBody(new File(sourceFileUri));
reqEntity.addPart(partName, bin);
}
}
httppost.setEntity(reqEntity);
System.out.println("Requesting : " + httppost.getRequestLine());
ResponseHandler<String> responseHandler = new BasicResponseHandler();
responseBody = httpclient.execute(httppost, responseHandler);
System.out.println("responseBody : " + responseBody);
return responseBody;
}
catch (UnsupportedEncodingException e) {
e.printStackTrace();
return e.getMessage();
}
catch (ClientProtocolException e) {
e.printStackTrace();
return e.getMessage();
}
catch (IOException e) {
e.printStackTrace();
return e.getMessage();
}
catch(Exception e){
e.printStackTrace();
System.out.println("error");
return e.getMessage();
}
}
}
Main Android Java File (Define plugin class)
package com.yourpackagename.core;
import android.os.Bundle;
import org.apache.cordova.*;
public class Waterhealth extends DroidGap
{
private FilesUpload f;
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// Set by <content src="index.html" /> in config.xml
super.init();
f = new FilesUpload();
appView.addJavascriptInterface(f, "FilesUpload");
super.loadUrl(Config.getStartUrl());
super.loadUrl("file:///android_asset/elect_exp_FS/index.html");
}
}
PHP Script for uploading files (Webservice)
<?php
if(isset($_REQUEST['elecExpObj']))
{
$obj= json_decode(stripslashes($_REQUEST['elecExpObj']));
$len = count($obj->electricityExpenseManagement);
$str = "";
for($i=0;$i<$len;$i++)
{
$bill_count = count($obj->electricityExpenseManagement[$i]->electricityExpenseBillInfoData);
for($j=0;$j<$bill_count;$j++)
{
$filePath = $obj->electricityExpenseManagement[$i]->electricityExpenseBillInfoData[$j]->image_base64_encode;
if($_FILES[$filePath]['name'])
{
if(!$_FILES[$filePath]['error'])
{
$new_file_name = $filePath . rand() . ".jpg"; //rename file
move_uploaded_file($_FILES[$filePath]['tmp_name'], 'uploads/'.$new_file_name);
$str .= 'Congratulations! Your file was accepted.';
}
}
}
}
echo $str;
}
else{
echo "fail";
}
?>
来源:https://stackoverflow.com/questions/20559435/a-good-approach-to-do-multipart-file-upload-in-android