我编写了 Android 代码以通过 HTTP Put 将大文件(10MB+)上传到 Apache PHP 服务器(我不能使用 FTP,因为我的应用程序有时在移动网络上运行,而且我听说许多移动运营商不允许 FTP-如果我错了,请纠正我)。处理实际上传的函数由 AsyncTask 调用,因此不会导致应用程序挂起。但是,我对上传的方法有些担心——有时我上传不完整,有时会出现“误报”——我收到一个信号,表明文件已上传,只是检查并看不到任何内容。
我的 C 语言背景比 Java 更深,而且我对 Java 的自动内存管理并不完全清楚,所以如果我的问题看起来有些荒谬,请原谅我。
所以,这是我的问题:
1) 上传问题可能是由内存问题引起的吗?该代码最初由原始开发人员编写以一次读取所有内容,但我在下面重新编写了它以使用 ByteArrayOutputStream 并逐块读取文件。我仍然担心 InputStream 试图在初始化时立即读取所有内容。我只是误解了 InputStream,还是它的行为更像 C 中的文件指针?
2)我不喜欢在while循环中每次都调用new——我怀疑,就像在C中一样,新内存一直在分配,但我不确定在重新初始化之前调用null是否真的有帮助。这会导致这里泄漏吗?我之所以问,是因为我认为 Java 的内存管理以某种方式确保不允许更多内存进入堆并跟踪引用。
3) 如果手机进入待机状态,则上传停止。发生这种情况时,是否有办法让上传继续进行?
4) PHP 服务器代码在报告网络错误/问题方面是否可能缺少某些内容?我已经尽可能多地包含了处理写入错误的内容,但我觉得不知何故这可以写得更好。
此外,我并不局限于使用 HttpPut——这只是向我建议的一种方法。我愿意在服务器端进行更改以支持更好的文件传输方法。
Android Java 代码(上传客户端中的函数):
private boolean transmitBytes(Uri uri)
{
/**/
ByteArrayEntity requestEntity;
InputStream inputStream = null;
float temp = 0.0f;
float temp1 = 0.0f;
long total = 0;
int buffSize = 102400;
byte[] buffer;
try {
inputStream = getContentResolver().openInputStream(uri);
ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
if (length <= 1048576) {
buffer = new byte[buffSize];
} else {
buffer = new byte[bufferSize];
}
// we need to know how may bytes were read to write them to the byteBuffer
int len = 0;
sharedPreferences = getSharedPreferences("Upload", MODE_PRIVATE);
String userid = sharedPreferences.getString("userid", ""); // Set the first segment with
// the resume flag at 0 so
// wipes anything of that
// filename on
// srvr.
String putURL = Constants.UPLOAD_MEDIA_URL;
putURL += "mediatype=";
putURL += mediatype;
putURL += "&file_name=";
putURL += new File(vidFileName).getName();
putURL += "&usrname=";
putURL += userid;
putURL += "&filesize=";
putURL += Long.toString(length);
putURL += "&sha256sum=";
putURL += cksum;
putURL += "&resume=0";
putURL = putURL.replace(" ", "");
// Read the first segment for transmission.
if ((len = inputStream.read(buffer)) != -1) {
byteBuffer.write(buffer, 0, len);
// Dump first segment into requestEntity
requestEntity = new ByteArrayEntity(byteBuffer.toByteArray());
// Wipe byte buffer after you are done writing with it to clear out contents.
byteBuffer.reset();
// Transmit segment.
HttpPut httpPut = new HttpPut(putURL);
httpPut.setEntity(requestEntity);
DefaultHttpClient httpClient = new DefaultHttpClient();
//Set timeouts. If no response by timeout, mark failure.
HttpParams httpParameters = new BasicHttpParams();
// The default value is zero, that means the timeout is not used and we go to infinite.
//Set to 3000 ms here.
int timeoutConnection = 5000;
HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
// in milliseconds which is the timeout for waiting for data. If not set, will be infinite.
int timeoutSocket = 5000;
HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);
httpClient.setParams(httpParameters);
// Transmit via PUT
HttpResponse response = httpClient.execute(httpPut);
HttpEntity entity = response.getEntity();
entity.getContent();
// Set resume tag to 1.
putURL = putURL.replaceAll("&resume=0", "&resume=1");
}
// write the remaining segments, (bufferSize) at a time
while ((len = inputStream.read(buffer)) != -1) {
byteBuffer.write(buffer, 0, len);
requestEntity = new ByteArrayEntity(byteBuffer.toByteArray());
// Wipe byte buffer after you are done writing with it to clear out contents.
byteBuffer.reset();
HttpPut httpPut = new HttpPut(putURL);
httpPut.setEntity(requestEntity);
DefaultHttpClient httpClient = new DefaultHttpClient();
//Set timeouts. If no response by timeout, mark failure.
HttpParams httpParameters = new BasicHttpParams();
// The default value is zero, that means the timeout is not used. Set to 3000 ms here.
int timeoutConnection = 3000;
HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
// in milliseconds which is the timeout for waiting for data.
int timeoutSocket = 5000;
HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);
httpClient.setParams(httpParameters);
// Transmit via PUT
HttpResponse response = httpClient.execute(httpPut);
HttpEntity entity = response.getEntity();
InputStream is = entity.getContent();
String result = convertStreamToString(is);
//9/10/2013: TODO-check for response codes in addition to these. Maybe, there is an error code being returned?
if (result.contains("0") || result.contains("1")) {
total += len;
temp = total * 100;
temp1 = temp / length;
//Updates object within async task.
mProgressDialog.setProgress((int) temp1);
}
else
{
/* Because this is controlled by an AsyncTask, we must bubble up that
* there has been a failure in the system if the user is unable to upload.
*
*/
return false;
}/**/
}
return true;
} catch (FileNotFoundException e) {
e.printStackTrace();
return false;
}
catch (ConnectTimeoutException e ){
//Took too long to connect to remote host
e.printStackTrace();
return false;
}
//Handled already by IOException.
catch (SocketTimeoutException e){
//Remote host didn’t respond in time
e.printStackTrace();
return false;
}/**/
//In case anything else is missing...
catch (Exception e) {
e.printStackTrace();
return false;
} finally {
try {
if (null != inputStream)
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
}
PHP服务器端代码:
<?php
$CHUNK = 8192;
//$header_size=1024;
//7/16/2012: this will now be set to 0 by default, and only to 1 upon complete transmission of file.
$rslt=0;
$bytes_written=0;
//set error handler
set_error_handler("errorLogFunction");
try {
//Error handler test
//echo($test);
if (!($putData = fopen("php://input", "r")))
{
//throw new Exception ("Can't get PUT data.");
$log = new KLogger ( "putfile_php_log.txt" , KLogger::DEBUG );
// Do database work that throws an exception
$log->LogError("Can't get PUT data.");
// Print out some information
$log->LogInfo($errMsg);
}
else
{
$filedata_arr=explode("&",$filedata);
$mediadata_tmp=explode("=",$filedata_arr[0]);
$filetype=$mediadata_tmp[1];
$filename_tmp=explode("=",$filedata_arr[1]);
$filename=$filename_tmp[1];
$username_tmp=explode("=",$filedata_arr[2]);
$usr=$username_tmp[1];
$filesize_tmp=explode("=",$filedata_arr[3]);
$filesize=(int)$filesize_tmp[1];
$sha256sum_tmp=explode("=",$filedata_arr[4]);
$sha256sum=$sha256sum_tmp[1];
//echo "SHA read: " . $sha256sum . "<BR>";
$resume_tmp=explode("=",$filedata_arr[5]);
$resume=$resume_tmp[1];
//6/18/2013: Replace any spaces with underscores. For now, don't add date to filename.
$tmp2=ereg_replace(" ","_",$filename);
$targetFilename=$_SERVER['DOCUMENT_ROOT'] . '/received/'.$filetype.'/'.$usr."_".$tmp2;
//7/27/2012: Check if the user exists-if the user does not exist, do not allow the upload. Do this only once-if the file exists, don't run this check.
if (!file_exists($targetFilename))
{
if (checkIfUsernameExists($usr)==0)
{
$rslt=-9;
}
}
//Continue on only if file could be written
if ($rslt>=0)
{
// Open the file for writing
if ($resume==0)
{
if (!($fp = fopen($targetFilename, "w")))
{
//throw new Exception ("Can't write to tmp file");
//echo json_encode(-1);
//throw new Exception ("Can't get PUT data.");
$log = new KLogger ( "putfile_php_log.txt" , KLogger::DEBUG );
// Do database work that throws an exception
$log->LogError("Can't write to tmp file.");
// Print out some information
$log->LogInfo($errMsg);
$rslt=-1;
}
}
else
{
if (!($fp = fopen($targetFilename, "a")))
{
//throw new Exception ("Can't write to tmp file");
//throw new Exception ("Can't get PUT data.");
$log = new KLogger ( "putfile_php_log.txt" , KLogger::DEBUG );
// Do database work that throws an exception
$log->LogError("Can't append to tmp file.");
// Print out some information
$log->LogInfo($errMsg);
//echo json_encode(-2);
$rslt=-2;
}
else
{
$bytes_written=filesize($targetFilename);
}
}
if ($rslt>=0)
{
// Read the data a chunk at a time and write to the file.
while ($data = fread($putData, $CHUNK))
{
$chunk_read = strlen($data);
if (($block_write = fwrite($fp, $data)) != $chunk_read)
{
//throw new Exception ("Can't write more to tmp file");
//throw new Exception ("Can't get PUT data.");
$log = new KLogger ( "putfile_php_log.txt" , KLogger::DEBUG );
// Do database work that throws an exception
$log->LogError("Can't write more to tmp file.");
// Print out some information
$log->LogInfo($errMsg);
//echo json_encode(-3);
$rslt=-3;
break;
}
$bytes_written += $block_write;
//7/2/2012: Commented out temporarily until resume upload feature supported.
//echo "<BR>" . $tot_write . " written.";
//echo $tot_write;
}
if ( ! fclose($fp) )
{
//throw new Exception ("Can't close tmp file");
//echo json_encode(-4);
//throw new Exception ("Can't get PUT data.");
$log = new KLogger ( "putfile_php_log.txt" , KLogger::DEBUG );
// Do database work that throws an exception
$log->LogError("Can't close tmp file.");
// Print out some information
$log->LogInfo($errMsg);
$rslt=-4;
}
unset($putData);
// now the params can be used like any other variable
// see below after input has finished
// Check file length and SHA-256
//6/16/2012: no need to do filesize check anymore. Just do checksum.
/*if ($tot_write != $file_size)
{
throw new Exception ("Wrong file size");
//echo json_encode(-6);
$rslt=-6;
}*/
if (($rslt>=0) && ($bytes_written==$filesize))
{
//7/27/2012: Skipping checksum check because of iOS. Will consider adding another flag for checksum later.
/*$sha256_arr = explode(' ',exec("sha256sum $targetFilename"));
$sha256 = $sha256_arr[0];
if ($sha256 != $sha256sum)
{
//throw new Exception ("Wrong sha256");
//echo json_encode(-7);
$rslt=-7;
}
//if the checksums and filesizes match, return success.
else
{
$rslt=1;
}*/
//echo "<BR>Calculated SHA-256: " . $sha256 . "<BR>";
//echo "Read in SHA-256: " . $sha256sum . "<BR>";
$rslt=1;
}
//If the filesize and checksum match, send the messages indicating success.
}
}
}//if (put data)
}//try
catch (Exception $e)
{
//var_dump($e->getMessage());
//echo json_encode(-5);
$rslt=-5;
}
echo json_encode($rslt);
?>