Bài 65: Xây dựng phần mềm Camera Uploader trong Android Studio


Ngày nay với sự ra đời của nhiều mạng xã hội (Facebook, Google +, Linkedin, Twitter…) nơi mà mỗi con người chúng ta có thể dễ dàng chém gió xả stress cũng như bắt người khác ăn stress. Tui nghĩ rằng mạng xã hội đã và đang đi sâu vào quần chúng, đi sâu vào mọi ngõ ngách hẻm hóc trong mỗi người sử dụng công nghệ. Ngoài chém thuần túy thông qua những trận phun châu nhả ngọc chúng ta còn có thể chém gió thông qua những tấm hình… để giải stress hoặc ăn thêm stress.

Việc chia sẻ hình ảnh trên Facebook giúp mọi người sử dụng trong mạng có kết nối có thể nhận được sự chia sẻ và hưởng thụ cảm giác hạnh phúc từ những tấm hình không hề vô tri vô giác. Tuy nhiên không phải tấm hình nào chúng ta cũng có thể chia sẻ lên mạng xã hội để tránh gây phiền toái cũng như bảo mật thông tin. Hiện nay các dòng máy Sờ Mác Phôn của Hồ Cẩm Đào ngày càng thịnh hành với giá rẻ đến ngạc nhiên nên hầu như ai cũng có thể sở hữu nó, và đặc biệt đi đến đâu cũng có WIFI miễn phí, quán nước mía 5k/1 ly cũng có WIFI miễn phí và kể cả quán cơm chay cũng có WIFI miễn phí (rất may WIFI chưa được đưa vào là món ăn mặn). Việc sở hữu 1 chiếc điện thoại hoành tráng cùng với internet miễn phí nên chúng ta có thể tự sướng và chỉ cần 1 cú nhấn “tách” là hình này có thể lên “Phây” ngay và “nuôn”. Nhưng có những công ty họ cần viết các phần mềm trên Smartphone để nhận hình ảnh từ nhân viên gửi về máy chủ, họ không muốn chia sẻ những hình ảnh bí mật này, họ muốn xây dựng một server riêng mỗi lần nhân viên chụp hình thì có thể tự động gửi hình này lên Server để tiến hành phân tích. Ví dụ như Tui có một người bạn làm trong ngành định giá bất động sản, phải đến tận nơi khảo sát chụp ảnh mọi vùng liên quan để có chứng cứ định giá chính xác hơn, nhân viên khi đi khảo sát ở xa chỉ cần chụp hình và thông qua 3G sẽ gửi những hình ảnh trực tiếp này lên server và ở công ty nhóm phân tích sẽ dựa vào những hình ảnh này để hỗ trợ đắc lực cho việc ra quyết định định giá.

-Vì vậy Trong bài tập này Tui muốn hướng dẫn các bạn xây dựng phần mềm Camera Uploader, hi vọng nếu như một ngày nào đó có bạn sinh viên nào cần phải viết phần mềm tương tự có thể đáp ứng được.

– Phần mềm này gồm các chức năng sau (ở đây Tui không làm giao diện đẹp, Tui xử lý coding):

1) Xây dựng Server riêng để lưu trữ hình ảnh gửi về từ client

2) “Thiết kế” một website PHP hiển thị hình ảnh để demo (cho có cảm giác)

3) Xây dựng chức năng Cho phép sử dụng Camera để chụp ảnh

4) Đưa ảnh mới chụp lên Server riêng của công ty

5) Cho phép lấy những hình ảnh khác được chụp trước đó trong điện thoại lên Server riêng.

Giao diện chính vô cùng đơn gian như sau:

android65_1Chương trình chỉ có 3 control chính: ImageView để hiển thị hình ảnh chụp được hoặc hình ảnh có sẵn, ImageButton chụp ảnh, ImageButton upload lên Server, ngoài ra có Menu để cho phép lấy hình ảnh có sẵn trong điện thoại lên giao diện.

1)- Trước tiên ta cần xây dựng server riêng, ở đây Tui hướng dẫn các bạn sử dụng một server khác miễn phí tương tự như somee.com để các bạn có thêm trải nghiệm.

Các bạn vào trang http://freevnn.com/ đăng ký gói free hosting.

Sau khi đăng ký thành công hệ thống sẽ gửi email cho bạn thông tin chi tiết về đăng nhập Cpanel, FTP, phpAdmin…

android65_2Sau khi đăng ký thành công, bạn sẽ nhân được 1 email tương tự như sau:

android65_3Bạn cần nghiên cứu CPANEL của hosting này vì Tui thấy nó rất hữu ích cho các bạn.

– Ta tiến hành viết 2 trang php để làm service tải hình từ client lên server riêng và dùng để trình diễn những hình ảnh đã tải lên server riêng. Nếu bạn nào chưa biết php cũng không sao (cứ coi như mình đã biết để đỡ tủi thân, sau đó tự học sau cho nó bằng bạn bằng bè).

– Bạn dùng Notepad ++ để tạo Trang index2.php cho lẹ, trang nay Tui dùng để nhận hình ảnh từ Client gửi về.


<?php
header('Content-Type: text/html; charset=utf-8');
echo "Lấy Hình Từ Android";
error_reporting(E_ALL);
$target_Path = "images/";
if(isset($_POST['ImageName'])){
$imgname = $_POST['ImageName'];
$target_Path = $target_Path.$imgname;
$imsrc = base64_decode($_POST['base64']);
$fp = fopen($target_Path, 'w');
fwrite($fp, $imsrc);
if(fclose($fp)){
echo "Tải hình thành công";
}else{
echo "Tải hình thất bại";
}
}
?>

Coding ở trên mỗi lần lấy được bất cứ hình nào gửi về từ client thì nó sẽ lưu vào thư mục images trên server. Bạn để ý 2 từ khóa “ImageName” và “base64” nó được gửi về từ client là tên hình và binary hình định dạng chuỗi.

– Tiếp theo bạn “thiết kế” một trang index.php để trình diễn mọi hình ảnh gửi về từ client.


<?php
$url1=$_SERVER['REQUEST_URI'];
header("Refresh: 5; URL=$url1");
$imagesDir = 'images/';
$images = glob($imagesDir . '*.{jpg,jpeg,png,gif}', GLOB_BRACE);
foreach ($images as $img) {
echo "<img src='$img' width='300' height='300'/> ";
}
?>

code trên đơn giản chỉ là đọc toàn bộ hình ảnh trong thư mục images rồi hiển thị lên giao diện, mỗi hình Tui để 300, 300.

– Sau đó ta đưa 2 trang web này lên Server riêng vừa đăng ký, ở đây bạn có thể sài bất kỳ phần mềm nào (FTP) cũng được. Vì nó có mấy lạng nên tui dùng Totalcommander luôn cho lẹ (bạn phải copy paste thông tin mà nó gửi email về cho các bạn để đăng nhập vào FTP):

android65_4Vào menu Net/ chọn FTP connect…

android65_5Chọn New Connection :

android65_6Nhập Hostname, user name, password… rồi nhấn nút OK. Sau đó ra màn hình nhấn nút Connect.

android65_7Bạn vào bên trong thư mục htdocs. Tạo 1 thư mục images, và chép 2 file index.phpindex2.php vào htdocs như hình Tui chụp ở trên.

index2.php sẽ được gọi trong android client, còn index.php sẽ được truy suất trực tiếp trên website. Ví dụ khi bạn chụp và gửi 1 hình lên Server riêng thì bạn chỉ cần gọi tên miền đã đăng ký là chương trình sẽ tự động hiển thị toàn bộ hình lên website (không cần gõ index.php vì nó ngầm mặc định là trang chủ), ví dụ tui vào http://duythanhcse.freevnn.com/ sẽ có kết quả:

android65_8– Như vậy là bạn đã cấu hình xong Server, bây giờ tiến hành coding cho Client.

– Ta xem cấu trúc của Project Android:

android65_9– Tiến hành thiết kế giao diện cho activity_main.xml như sau:


<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:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity"
android:orientation="vertical"
>

<ImageView
android:id="@+id/Imageprev"
android:layout_width="match_parent"
android:layout_height="380dp" />

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >

<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/btnCapture"
android:src="@drawable/camera" />

<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/btnUpload"
android:src="@drawable/upload"
android:layout_gravity="right" />
</LinearLayout>

</LinearLayout>

-Nhớ bổ sung thêm 1 menu đọc hình ảnh từ điện thoại:


<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity">
<item android:id="@+id/mnuImageList" android:title="Xem hình trong SD Card"></item>
<item android:id="@+id/action_settings" android:title="@string/action_settings"
android:orderInCategory="100" app:showAsAction="never" />
</menu>

-Tiến hành viết UploadToServerTask là lớp đa tiến trình để tải hình từ client lên Server.


package com.tranduythanh.camerauploader;

import android.app.Activity;
import android.app.ProgressDialog;
import android.os.AsyncTask;
import android.util.Log;

import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import java.util.ArrayList;

/**
* Created by drthanh on 14/05/2015.
*/
public class UploadToServerTask extends AsyncTask<Void, Void, String> {

//URL để tải hình lên server
private String URL = "http://duythanhcse.freevnn.com/index2.php";
private Activity context=null;
private ProgressDialog progressDialog=null;
private String ba1;
public UploadToServerTask(Activity context, String ba1)
{
this.context=context;
this.ba1=ba1;
this.progressDialog=new ProgressDialog(this.context);
}
protected void onPreExecute() {
super.onPreExecute();
this.progressDialog.setMessage("Vui lòng chờ hệ thống đang upload hình!");
this.progressDialog.show();
}

@Override
protected String doInBackground(Void... params) {
//Coding gửi hình lên Server
ArrayList<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
nameValuePairs.add(new BasicNameValuePair("base64", ba1));
nameValuePairs.add(new BasicNameValuePair("ImageName", System.currentTimeMillis() + ".jpg"));
try {
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost(URL);
httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
HttpResponse response = httpclient.execute(httppost);
String st = EntityUtils.toString(response.getEntity());
Log.v("log_tag", "In the try Loop" + st);

} catch (Exception e) {
Log.v("log_tag", "Lỗi kết nối : " + e.toString());
}
return "Thành công";

}

protected void onPostExecute(String result) {
super.onPostExecute(result);
this.progressDialog.hide();
this.progressDialog.dismiss();
}
}

– Cuối cùng tiến hành coding cho MainActivity:

+ Trong lớp này cho phép chụp hình và chỉ lấy thumbnail để tối ưu bộ nhớ

+ Tương tự cho việc lấy hình ảnh có sẵn cũng lấy thumbnail

+ Đặc biệt tự động quay lại hình (rotate) nếu như hình bị quay không đúng hướng layout của phần mềm.


package com.tranduythanh.camerauploader;

import android.graphics.Matrix;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import java.io.ByteArrayOutputStream;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.provider.MediaStore;
import android.util.Base64;
import android.util.Log;
import android.view.View;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.Toast;

public class MainActivity extends ActionBarActivity {
ImageButton btnCapture, btnUpload;
ImageView imageView;
private Uri fileUri;
String picturePath;
Uri selectedUriImage;
Bitmap selectedBitmap;
String ba1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
addControls();
addEvents();
}
public void addControls()
{
btnCapture = (ImageButton) findViewById(R.id.btnCapture);
btnUpload = (ImageButton) findViewById(R.id.btnUpload);
imageView = (ImageView) findViewById(R.id.Imageprev);
btnUpload.setEnabled(false);
}
public void addEvents()
{
btnCapture.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
capturePicture();
}
});
btnUpload.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
uploadPictureToServer();
}
});
}

/**
* hàm xử lý lấy thumbnail để tối ưu bộ nhớ
* @param pathHinh
* @return
*/
public Bitmap getThumbnail(String pathHinh)
{
BitmapFactory.Options bounds = new BitmapFactory.Options();
bounds.inJustDecodeBounds = true;
BitmapFactory.decodeFile(pathHinh, bounds);
if ((bounds.outWidth == -1) || (bounds.outHeight == -1))
return null;
int originalSize = (bounds.outHeight > bounds.outWidth) ?
bounds.outHeight
: bounds.outWidth;
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inSampleSize = originalSize / 500;
return BitmapFactory.decodeFile(pathHinh, opts);
}

/**
* Hàm xử lys lấy encode hình để gửi lên Server
*/
private void uploadPictureToServer() {
Log.e("path", "----------------" + picturePath);
ByteArrayOutputStream bao = new ByteArrayOutputStream();
selectedBitmap.compress(Bitmap.CompressFormat.JPEG, 100, bao);
byte[] ba = bao.toByteArray();
ba1 =Base64.encodeToString(ba,Base64.DEFAULT);

Log.e("base64", "-----" + ba1);

// Upload hình  lên server
UploadToServerTask uploadToServer=new UploadToServerTask(MainActivity.this,ba1);
uploadToServer.execute();
}

private void capturePicture() {
// Kiểm tra Camera trong thiết bị
if (getApplicationContext().getPackageManager().hasSystemFeature(
PackageManager.FEATURE_CAMERA)) {
// Mở camera mặc định
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);

// Tiến hành gọi Capture Image intent
startActivityForResult(intent, 100);

} else {
Toast.makeText(getApplication(), "Camera không được hỗ trợ", Toast.LENGTH_LONG).show();
}
}

/**
* Lấy đường dẫn file hình theo uri hình
* @param uriImage
* @return
*/
public String getPicturePath(Uri uriImage)
{
String[] filePathColumn = {MediaStore.Images.Media.DATA};
Cursor cursor = getContentResolver().query(uriImage,
filePathColumn, null, null, null);
cursor.moveToFirst();

int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
String path = cursor.getString(columnIndex);
cursor.close();
return path;
}
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if ((requestCode == 100||requestCode==200) && resultCode == RESULT_OK) {
//Lấy URI hình kết quả trả về
selectedUriImage = data.getData();
//lấy đường dẫn hình
picturePath=getPicturePath(selectedUriImage);
//lấy thumbnail để tối ưu bộ nhớ
selectedBitmap=getThumbnail(picturePath);
selectedBitmap=rotateImageIfRequired(selectedBitmap,selectedUriImage);
imageView.setImageBitmap(selectedBitmap);
btnUpload.setEnabled(true);
}
}
/**
* Hàm hiển thị Camera folder và cho phép hiển thị hình người sử dụng chọn
* lên giao diện, hình này sẽ được gửi lên Server nếu muốn
*/
public void processChonHinh()
{
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
startActivityForResult(intent, 200);
}
/**
* Quay lại hình nếu chưa đúng
* @param img
* @param selectedImage
* @return
*/
private  Bitmap rotateImageIfRequired(Bitmap img, Uri selectedImage) {

// Detect rotation
int rotation=getRotation();
if(rotation!=0){
Matrix matrix = new Matrix();
matrix.postRotate(rotation);
Bitmap rotatedImg = Bitmap.createBitmap(img, 0, 0, img.getWidth(), img.getHeight(), matrix, true);
img.recycle();
return rotatedImg;
}else{
return img;
}
}
/**
* Lấy Rotation của hình
* @return
*/
private int getRotation() {
String[] filePathColumn = {MediaStore.Images.Media.ORIENTATION};
Cursor cursor = getContentResolver().query(selectedUriImage,
filePathColumn, null, null, null);
cursor.moveToFirst();

int rotation =0;
rotation = cursor.getInt(0);
cursor.close();
return rotation;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();

//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
if(id==R.id.mnuImageList)
{
processChonHinh();
}
return super.onOptionsItemSelected(item);
}
}

– Nhớ cấu hình AndroidManifest:


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tranduythanh.camerauploader" >

<uses-feature
android:name="android.hardware.Camera"
android:required="true" >
</uses-feature>

<uses-permission android:name="android.permission.CAMERA" >
</uses-permission>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" >
</uses-permission>

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>

– Thực thi phần mềm, tiến hành chụp rồi gửi lên Server

– Hoặc vào menu lấy hình có sẵn trong máy gửi lên server:

android65_10– Sau đó nhấn nút Upload để đưa lên Server:

android65_11–> Chương trình sử dụng kỹ thuật đa tiến trình để tải hình lên server.

Ta có thể kiểm tra lại kết quả các hình chụp và hình lấy sẵn được truyền lên server:

android65_12– Như vậy bạn đã hoàn thành xong phần mềm và có thể test chạy trực tiếp trên server riêng Tôi tạo, nhưng các bạn nhớ tự tạo riêng để test để học hỏi được nhiều hơn.

– và nhớ bổ sung thêm hàm kiểm tra xem điện thoại có đang kết nối internet hay không nhé, nếu chưa có internet thì phải tắt nút Upload hình đi, đoạn code kiểm tra internet có hay không đã được đề cập đến trong bài 64 dự báo thời tiết.

– Bạn có thể tải source code đầy đủ ở đây: http://www.mediafire.com/download/ts6agjkv3z30z8a/CameraUploader.rar

-Chúc các bạn thành công.

26 responses

  1. hình như có lỗi trong code thì phải. dòng selectedUriImage = data.getData(); bị NullPointerException

    1. Của mình cũng bị này. Sửa như nào ạ?

      1. Sao mình sửa các kiểu mà nó vẫn null các bạn, getPicturePath return path null
        Mong các bạn đã qua được chia sẻ, mình dùng samsung j5 2015

    2. Đúng vậy, lệnh đó không phải đúng cho mọi thiết bị. Cách giải quyết em có thể xem trên: http://developer.android.com/training/camera/photobasics.html

    3. protected void onActivityResult(int requestCode, int resultCode, Intent data) {
      super.onActivityResult(requestCode, resultCode, data);
      if (requestCode == 100 && resultCode == RESULT_OK) {
      Bundle extras = data.getExtras();
      selectedBitmap = (Bitmap) extras.get(“data”);
      imageView.setImageBitmap(selectedBitmap);
      }
      }

    4. Sao mình sửa các kiểu mà nó vẫn null các bạn, getPicturePath return path null
      Mong các bạn đã qua được chia sẻ, mình dùng samsung j5 2015

  2. còn 1 vấn đề nữa là khi em gửi lên host của thầy thì đc, nhưng khi gửi lên host của em đã tạo thì lại báo “Tải hình thất bại” 😦

      1. chmod 777 là gì bạn.bạn có thể nói cụ thể hơn không?

  3. Phạm Công Quân | Reply

    Thầy ơi ! Cho em hỏi dùng hàm nào để Refesh lại địa chỉ Api của mình trong 1 khoảng thời gian nhất định không à ?

  4. sao có lúc thầy dùng ASP.net lại có lúc dùng PHP thế ạ.theo thầy nên tập trung dùng cái nào để tạo webserver

  5. Thầy cho e hỏi k hiểu tại sao em up lên server thầy thì dc mà sao server của em thì không được ạ ??

    1. được rồi thầy ạ.của em phải thêm đuôi index.php nữa mới được.

  6. Thầy ơi.thầy có thể hướng dẫn lấy kết quả trả về từ server nữa không ạ ?

  7. của e không gọi được DefaultHttpClient 😦

  8. có bạn nào có ứng dụng chat cơ bản nào có thể share để mình học được không
    tks!!

  9. 02-16 14:14:26.802 20885-20885/com.tranduythanh.camerauploader E/ViewRootImpl: sendUserActionEvent() mView == null
    em làm theo nhưng lại bị cái này, thầy giúp em với, em cảm ơn.

  10. Thầy ơi, sao em up lên trang của thầy mà không được, nó cứ hiện ra tải ảnh thất bại, lên trang web của thầy không thấy ảnh của em đâu cả.

    1. Em nên thử tạo 1 trang riêng bất kỳ của em.

  11. Nguyễn Văn Chung | Reply

    Thầy cho em hỏi để chọn nhiều ảnh hiển thị lên GridView thì thế nào thầy

    1. Em làm y xì như Custom Layout cho ListView. GridView hiển thị dữ liệu dạng dòng cột nhưng dữ liệu đầu vào em setup y xì ListView (Cụ thể em làm custom layout có 1 ImageView để hiển thị hình ảnh)

  12. Cho em hỏi cái này làm trên android studio hay eclipse thầy, em import không được

    1. Em làm trên tool nào cũng được (nhưng coi chừng version SDK khác nhau chưa chắc chạy được)

      1. Em cảmơn thầy ạ.
        Thầy cho em hỏi thầy có hướng dẫn viết nhận dạng biển số xe bằng OpenCV trên android không ạ?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s