segunda-feira, 1 de outubro de 2012

Upload de arquivos no Android

Olá povo,

Esse post tava há um bom tempo como rascunho no blog e só agora consegui terminar para postar aqui. Vou mostrar como fazer uma aplicação Android que faz upload de arquivos para um servidor web.
public class UploadActivity extends Activity {

  private TextView txtArquivo;
 
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_upload);

    txtArquivo = (TextView)
      findViewById(R.id.txtArquivo);
  }
 
  // Ao selecionar o arquivo, preenchemos o 
  // TextView com o caminho real do arquivo
  @Override
  protected void onActivityResult(int requestCode, 
    int resultCode, Intent data) {
    super.onActivityResult(requestCode, 
      resultCode, data);

    if (resultCode == RESULT_OK) {
      if (requestCode == 0) {
        Uri selectedImageUri = data.getData();
        txtArquivo.setText(getPath(selectedImageUri));
      }
    }  
  }

  // Botão que abre a galeria de mídia para 
  // selecionar um arquivo
  public void selecionarArquivo(View v){
    Intent intent = new Intent();
    intent.setType("image/*");
    intent.setAction(Intent.ACTION_GET_CONTENT);
    startActivityForResult(Intent.createChooser(
      intent,"Selecione uma imagem"), 0);
  }
 
  // Botão que envia o arquivo selecionado
  public void enviarArquivo(View v){
    try {
      new Thread(){
        public void run(){
          executeMultipartPost();
        }
      }.start();
    } catch (Exception e) {
      Log.e("NGVL", e.getMessage());
    }  
  }
 
  // Obtém o caminho real do arquivo 
  // através do ContentProvider
  public String getPath(Uri uri) {
    String[] projection = { 
      MediaStore.Images.Media.DATA };

    Cursor cursor = getContentResolver().query(
      uri, projection, null, null, null);

    int column_index = cursor.getColumnIndexOrThrow(
      MediaStore.Images.Media.DATA);

    cursor.moveToFirst();
    return cursor.getString(column_index);
  }
 
  // Método que realmente envia o arquivo
  public void executeMultipartPost() 
    throws Exception {

    String caminhoDoArquivoNoDispositivo = 
      txtArquivo.getText().toString();

    String urlDoServidor = 
      "http://url_servidor/pagina_que_trata_upload";
    String lineEnd = "\r\n";
    String twoHyphens = "--";
    String boundary = "*****"; // Delimitador

    byte[] buffer;
    int bytesRead, bytesAvailable, bufferSize;
    int maxBufferSize = 1 * 1024 * 1024; // 1MB

    try {
      // Criando conexão com o servidor
      URL url = new URL(urlDoServidor);
      HttpURLConnection connection = 
        (HttpURLConnection) url.openConnection();

      // Conexão vai ler e escrever dados
      connection.setDoInput(true);
      connection.setDoOutput(true);
      connection.setUseCaches(false);

      // Setando método POST
      connection.setRequestMethod("POST");

      // Adicionando cabeçalhos
      connection.setRequestProperty(
        "Connection", "Keep-Alive");
      connection.setRequestProperty(
        "Content-Type",
        "multipart/form-data;boundary=" + boundary);
      connection.connect();
   
      // Escrevendo payload da requisição
      DataOutputStream outputStream = 
        new DataOutputStream(
          connection.getOutputStream());
      outputStream.writeBytes(
        twoHyphens + boundary + lineEnd);
      outputStream.writeBytes(
        "Content-Disposition: form-data; "+
        "name=\"uploadedfile\";filename=\""+ 
        caminhoDoArquivoNoDispositivo + "\"" +
        lineEnd);
      outputStream.writeBytes(lineEnd);

      // Stream para ler o arquivo
      FileInputStream fileInputStream = 
        new FileInputStream(new File(
          caminhoDoArquivoNoDispositivo));
   
      // Preparando para escrever arquivo
      bytesAvailable = fileInputStream.available();
      bufferSize = Math.min(
        bytesAvailable, maxBufferSize);
      buffer = new byte[bufferSize];

      // Lendo arquivo e escrevendo na conexão
      bytesRead = fileInputStream.read(
        buffer, 0, bufferSize);

      while (bytesRead > 0) {
        outputStream.write(buffer, 0, bufferSize);
        bytesAvailable = fileInputStream.available();
        bufferSize = Math.min(
          bytesAvailable, maxBufferSize);
        bytesRead = fileInputStream.read(
          buffer, 0, bufferSize);
      }
      outputStream.writeBytes(lineEnd);
      outputStream.writeBytes(
        twoHyphens + boundary + 
        twoHyphens + lineEnd);

      // Obtendo o código e a mensagem
      // de resposta do servidor
      int serverResponseCode = 
        connection.getResponseCode();
      String serverResponseMessage = 
        connection.getResponseMessage();

      Log.d("NGVL", serverResponseCode +" = "+ 
        serverResponseMessage);
   
      fileInputStream.close();
      outputStream.flush();
      outputStream.close();
   
    } catch (Exception ex) {
      // Exception handling
    }
  }
}
O código acima está todo comentado, então vou mostrar o arquivo de layout da aplicação. O único detalhe que eu quero ressaltar, é que utilizei uma Thread apenas pra facilitar o código, mas deveríamos utilizar uma AsyncTask ou até um Service.
<LinearLayout 
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical" >

  <TextView
    android:id="@+id/txtArquivo"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Arquivo a ser enviado" />

  <Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Selecionar arquivo" 
    android:onClick="selecionarArquivo"/>

  <Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Enviar" 
    android:onClick="enviarArquivo"/>

</LinearLayout>
A última coisa a fazer é adicionar a permissão de INTERNET no AndroidManifest.xml.
<uses-permission 
  android:name="android.permission.INTERNET"/>
A figura abaixo mostra a aplicação em execução:

Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

13 comentários:

João Paulo Oliveira disse...

Muito bom seu post! Só fique com uma dúvida: como eu envio imagem junto com um formulário?

Tks

Nelson Glauber de Vasconcelos Leal disse...

Oi João Paulo,

Como é um multi-part/form-data, cada campo seria uma part. Então teria que escrever um campo dentro do boundary do request. Mais ou menos assim:

outputStream.outputStream.writeBytes("Content-Disposition: form-data; name=\"seucampo\""+ lineEnd + twoHyplineEnd +"valorDoCampo);
outputStream.writeBytes(lineEnd);
outputStream.writeBytes(
twoHyphens + boundary +
twoHyphens + lineEnd);

4br4ç05s,
nglauber

João Paulo Oliveira disse...

Mais uma vez, obrigado! Consegui fazer o upload da imagem junto com o formulário!!!

Tks!!!

Thiaguinho disse...

Muito bom o artigo mesmo.

Porém, e se eu quiser fazer um upload de um arquivo qualquer e não de uma imagem, o que eu precisaria mudar?

Nelson Glauber de Vasconcelos Leal disse...

oi Thiaguinho,

Basicamente o que mudaria:
1) No método selecionarArquivo, o tipo seria */*
2) No método getPath teria que pesquisar como obter o caminho do arquivo selecionado
3) Usei a classe Thread nesse exemplo, mas o ideal é usar um IntentService ou uma AsyncTask dependendo do tamanho do download.

4br4ç05,
nglauber

Rafael Miranda B Rocha Rocha disse...

Oi Glauber, tenho um serviço web no qual passo via post a imagem a ser upada, posso usar esse mesmo serviço para o código apresentado ??

Nelson Glauber de Vasconcelos Leal disse...

Oi Rafael,

Teoricamente, pode sim... Mas aí depende de como o serviço tá implementado.

4br4ç05,
nglauber

Isaac disse...

E se fosse para enviar via Bluetooth, como seria ?


Abraços.

Nelson Glauber disse...

Oi Isaac,

A abordagem seria completamente diferente, pois são protocolos diferentes em vários aspectos.
No meu livro tem um capítulo sobre Bluetooth que pode ajudar e que é basicamente este artigo aqui que eu escrevi para a DevMedia.
http://www.devmedia.com.br/resumo/?ed=35&site=5

Se preferir, consulte a documentação oficial:
http://developer.android.com/guide/topics/connectivity/bluetooth.html

4br4ç05,
nglauber

Bruno Romualdo disse...

Olá N.Glauber, primeiramente ótimo tutorial, ja fiz esse exemplo utilizando seu livro mas perdi meu app e agora estou refazendo ele. Porém me veio uma dúvida tem como mostrar o progresso de upload? Já tenho um ProgressBar e o layout tudo certinho mas não faço a minima ideia de como pegar o progresso de upload.

Nelson Glauber disse...

Oi Bruno,

A maneira mais simples é você usar a AsyncTask e no "while" do método executeMultipartPost() chamar o publishProgress(valor). Dá uma olhada nisso que vai te ajudar.
https://developer.android.com/reference/android/os/AsyncTask.html

4br4ç05,
nglauber

Bruno Romualdo disse...

Sim sim, eu já uso uma AsyncTask, o problema é que como sou leigo não sei o que usar como valor no publishProgress(?), poderia dar um exemplo, se não for pedir muito.

Nelson Glauber disse...

Oi Bruno,

Você abriu o link que eu mandei? Nele tem um exemplo...
https://developer.android.com/reference/android/os/AsyncTask.html

No publishProgress você pode passar qualquer valor (dependendo de como você definiu a task), mas no seu caso, você passará quantos bytes já foram enviados e o total de bytes. Por exemplo:

int contadorBytes = 0;
while (bytesRead > 0) {
...
contadorBytes += bytesRead;
publishProgress(contadorBytes, bytesAvailable);
}

E no onProgress() você atualiza esse progresso de alguma forma:

protected void onProgressUpdate(Integer... progress) {
// progress[0] é o contadorBytes
// progress[1] é o total de bytes
}

Lembrando que sua AsyncTask tem que estar anotada (neste caso) como AsyncTask

4br4ç05,
nglauber