[Tản mạn] Nghệ thuật viết code

Mỗi người có một phong cách viết code riêng. Tuy nhiên, hầu hết họ đều “tuân thủ” theo một quy tắc nhất định chung nào đó mà không ai nói, không ai viết, nhưng họ tự ngầm định cho nhau.
Với kinh nghiệm viết code gần 6 năm tôi xin chia sẻ những kinh nghiệm của mình trong suốt quá trình học: Viết code là gì và viết code như thế nào.

1. Viết code là gì?

Tôi có hỏi nhiều lập trình viên, nhiều bạn sinh viên học lập trình nhưng ít người trả lời được câu hỏi “viết code là gì?”. Không máy móc tôi tự định nghĩa theo ý hiểu của mình:
Viết code là quá trình viết các câu lệnh “mà máy có thể hiểu được ” theo một trật tự nhất định nhằm điều khiển hoạt động của máy tính (điện thoại) hoạt động theo mong muốn của mình. Có thể gọi là quá trình tạo ra một sản phẩm “phần mềm”.

2. Viết code như thế nào?

Trước khi các bạn đọc những dòng sau, tôi muốn các bạn nhớ lại rằng viết code là viết cho máy hiểu những gì chúng ta muốn làm. Vì vậy để viết code, các bạn cần biết rõ 2 điều:
– 1 là bạn muốn làm gì: đây là mục đích bạn viết code
– 2 là viết code cho máy hiểu: đây là phần ngôn ngữ lập trình, chỉ cần học là được.
Cả 2 điều này các bạn đều thấy bình thường, nhưng nếu thiếu 1 trong 2 thì bạn không thể viết được code và cũng không nên đọc tiếp.

2.1 Viết code bằng tay

Tôi đã nhiều lần đứng lớp dạy về lập trình. Nhìn các bạn mới học code tôi hiểu các bạn nghĩ “Muốn học giỏi phải chịu khó viết tay” không sai! Nhưng vì tâm lý lo sợ, sợ mình học kém, sợ mình không làm được, sợ không bằng người khác,… dẫn tới hậu quả viết code bằng tay một cách “máy móc”.
Cụ thể: có bạn khi viết code nhớ từng chữ cái, từng dấu chấm,… nhưng viết xong vẫn sai. Tại sao?
Hầu hết các công cụ lập trình hiện nay “rất thông minh” chúng có khả năng suy đoán chúng ta “viết gì tiếp theo”. Ví dụ viết lệnh System.out.println(“”); thì đôi khi chỉ cần viết Sys là công cụ đã gợi ý ra lệnh này, hoặc viết tắt “sou”,….
Không phải nghiễm nhiên nhà phát triển công cụ lại đưa ra chức năng này. Vì họ muốn giúp lập trình viên nhàn hơn, nhanh hơn. Tập chung nhiều hơn vào các việc tư duy logic chứ không phải “nhớ từng ký tự, từng dấu chấm”. Đây cũng là một vấn đề bạn cần quan tâm – chọn trình soạn thảo thích hợp để code cho thuận tiện.

2.2 Copy code

Có nhiều người cho rằng copy code sẽ không hiểu vấn đề. Đúng mà cũng sai!
Nếu bạn lười không nghĩ, không tìm hiểu xem đoạn code bạn copy về nó làm gì và nó làm như thế nào thì bạn sẽ không hiểu, nhưng nếu bạn tìm hiểu thì lại khác, chúng ta vừa hiểu code, vừa nhanh chóng có code và lại vừa mới học được nhiều thứ mới trong đoạn code đó.

2.3 Trình bày code

Vấn đề trình bày luôn là vấn đề quan trọng, mỗi ngôn ngữ có một cách trình bày riêng. Tuy nhiên, các ngôn ngữ đều ngầm định một quy tắc trình bày “chuẩn”:
– Tách biệt các phần lệnh bằng 1 dòng trắng theo tiêu chí nội dung công việc
– Gom các đoạn lệnh thực hiện nhiệm vụ như nhau vào một hàm.
– Căn code theo cấu trúc cha con, đoạn nào nằm trong thì tab nó vào so với thằng cha của nó, các cặp dấu {}, (), “” nên viết cùng một lúc, tức là viết mở thì hãy đóng nó luôn và di chuyển con trỏ sang trái để viết nội dung bên trong.
– Thường xuyên sử dụng phím tắt để căn code thật đẹp mắt, dễ đọc (nếu trình soạn thảo hỗ trợ)

2.4 Viết code thông minh ngắn gọn nhưng phải dễ hiểu


    // Tìm số lớn nhất 01
    public int max(int a, int b){
        return (a > b) ? a : b;
    }

    // Tìm số lớn nhất 02
    public int max(int a, int b){
        int max = a;

        if(a > b){
            max = a;
        }else{
            max = b;
        }

        return max;
    }

    // Tìm số lớn nhất 03
    public int max(int a, int b){

        if(a > b){
            return a;
        }else{
            return b;
        }
    }

Các bạn thấy 3 hàm trên đều dùng để tìm số lớn nhất trong 2 số a, b. Nhưng cách viết nào là hiệu quả, dễ nhớ mỗi người có một lựa chọn khác nhau. Tuy nhiên, đôi lúc phải lựa chọn những cách viết khó hiểu để code ngắn, ít lệnh (lợi thế về tốc độ – cách viết 01). Với những bài đơn giản thì nên chọn những cách viết dễ hiểu dễ nhớ (Cách 03).

2.5 Viết code dễ nhớ – chú thích hiệu quả

Nhiều người thắc mắc cặp lệnh /**/ hay // chẳng mấy khi dùng. Không! nó dùng rất nhiều.
Với mỗi hàm, đoạn lệnh khi chúng ta viết có thể chúng ta hiểu và nhớ ngay nhưng một thời gian sau sẽ quên, nếu xem lại có thể sẽ không hiểu. Vậy việc comment là cần thiết đẻ chú thích các đoạn lệnh phức tạp. Ngoài các nội dung chức năng, trong các phần comment người ta thường viết ngày giờ chỉnh sửa, phiên bản, người viết,… với mục đích tra cứu thuận tiện.

[AndroidAdvance] Chụp ảnh màn hình

Chào các bạn, đôi khi viết apps chúng ta cần thiết phải lưu lại ảnh màn hình hiện tại, làm điều đó chúng ta sử dụng một đoạn code đơn giản như sau:


public Bitmap takeScreenshot() {
   View rootView = findViewById(android.R.id.content).getRootView();
   rootView.setDrawingCacheEnabled(true);
   return rootView.getDrawingCache();
}

public void saveBitmap(Bitmap bitmap) {
    File imagePath = new File(Environment.getExternalStorageDirectory() + "/screenshot.png");
    FileOutputStream fos;
    try {
        fos = new FileOutputStream(imagePath);
        bitmap.compress(CompressFormat.PNG, 100, fos);
        fos.flush();
        fos.close();
    } catch (FileNotFoundException e) {
        Log.e("GREC", e.getMessage(), e);
    } catch (IOException e) {
        Log.e("GREC", e.getMessage(), e);
    }
}

Tại phương thức onCreate ta viết mã sau:

findViewById(R.id.button1).setOnClickListener(new OnClickListener() {
   @Override
   public void onClick(View v) {
       Bitmap bitmap = takeScreenshot();
       saveBitmap(bitmap);
   }
});

Khi chạy đoạn mã trên các bạn sẽ được bức ảnh dạng .png lưu trong thẻ nhớ ngoài.

[AndroidAdvance] Bài 3: Custom Broadcast Reveice và truyền dữ liệu giữa hai project

Hai bài trước các bạn đã đi hết các cách cơ sở để viết một ứng dụng nhận Broadcast từ hệ thống. Bài hôm nay mình giới thiệu tới các bạn cách gửi một Broadcast và nhận một Broadcast không phải từ hệ thống mà từ ứng dụng này sang ứng dụng khác.

Screenshot from 2014-07-14 07:11:52 

Screenshot from 2014-07-14 07:12:02

 

Theo hình trên Project 1 mình sẽ gửi một Broadcast, Project 2 sẽ nhận Broadcast đó và hiển thị một Toast lên màn hình.

Viết một ứng dụng nhận một Broadcast các bạn đã biết ở bài trước, nó chỉ khác duy nhất action trong thẻ <action> không phải là các action cố định mà hệ thống đã định nghĩa trước đó mà action này do ta định nghĩ

<intent-filter >
    <action android:name="clbtinhoc.ictu.custom_intent"/>
</intent-filter>

Ta sẽ đi luôn vào viết ứng dụng gửi broadcast trước, bạn tạo một project trong đó file main_activity.xml (file giao diện) có nội dung là một Button

<?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:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Broadcast Receiver custom"
        android:textAppearance="?android:attr/textAppearanceLarge" 
        android:layout_gravity="center_horizontal"/>

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Send Broadcast" 
        android:layout_gravity="center_horizontal"/>

</LinearLayout>

Tại file ActivityMain.java các bạn viết một đoạn code gửi broadcast khi ấn Button

package clbtinhoc.ictu;

import clbtinhoc.ictu.broadcastreceivercustom.R;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class ActivityMain extends Activity{

	
	@Override
	public void onCreate(Bundle bundle){
		super.onCreate(bundle);
		setContentView(R.layout.main_activity);
		
		final Button button = (Button) findViewById(R.id.button1);
		button.setOnClickListener(new View.OnClickListener() {
			
			@Override
			public void onClick(View v) {
				// intent kèm theo hành động có tên clbtinhoc.ictu.custom_intent
				Intent intent = new Intent("clbtinhoc.ictu.custom_intent");
				Bundle bundle = new Bundle();
				bundle.putString("xau", "Xin chào!");
				intent.putExtras(bundle);
				// gửi Broadcast
				sendBroadcast(intent);
			}
		});
		
	}
}

Đoạn code khá đơn giản nên mình chỉ chú thích duy nhất hàm gửi broadcast thôi 🙂

Tiếp theo bạn tạo một Project mới dùng để nhận broadcast. Project này khi tạo không có file Activity.java. Bạn tạo một class có tên MyProject.java sau đó extends từ BroadcastReceiver

package clbtinhoc.ictu;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast;

public class MyBroadcast extends BroadcastReceiver{

	@Override
	public void onReceive(Context context, Intent intent) {
		Bundle bundle = intent.getExtras();
		String text = bundle.getString("xau");
		
		Toast.makeText(context, text, Toast.LENGTH_SHORT).show();	
	}
}

Tại file AndroidMaintifest.xml các bạn đăng ký nhận broadcast với nội dung như sau:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="clbtinhoc.ictu.broadcastreceivercustom"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="8" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        
        <receiver android:name="clbtinhoc.ictu.MyBroadcast">
            <intent-filter >
                <action android:name="clbtinhoc.ictu.custom_intent"/>
            </intent-filter>
        </receiver>
    </application>

</manifest>

Code không có gì mới chỉ có phần thẻ action với nội dung là “clbtinhoc.ictu.custom_intent”

Các bạn build project số hai vào máy trước sau đó build và chạy project số 1 sau đó ấn button sẽ thấy hiện một Toast.

Ở project mẫu mình sẽ hợp luôn hai project làm một project các bạn có thể download ở đây:

https://www.dropbox.com/sh/9yc7jmkbvdhxry4/AACWleHUgd32wPmEc7O80dxGa

[AndroidAdvance] Bài 2- Broadcast Receiver trong Android, đăng ký trong AndroidMaintifest và chương trình nhận tin nhắn

Ở bài trước các bạn đã biết cách đăng ký BroadcastReceiver trong Android thông qua coding để nhận biết các sự kiện của hệ thống. Bài này mình giới thiệu tiếp tới các bạn cách thứ hai để đăng ký, sử dụng xml đăng ký trong AndroidMaintifest, cách này được sử dụng phổ biến và rộng dãi hơn bởi ngay cả khi ứng dụng đang tắt nó vẫn có thể nhận được thông tin từ hệ thống và khởi động ứng dụng.

Cách đăng ký trong Android Maintifest có vẻ đơn giản hơn chỉ với thẻ receiver và intentfilter

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >

        <!-- Đăng ký nhận BroadcastReceiver cho class MyBroadcastReceiver-->
        <receiver
            android:name="clbtinhoc.ictu.onreceiversms.MyBroadcastReceiver"
            android:label="On Receiver SMS" >
            <!-- Đăng ký lọc hành động nhận tin nhắn -->
            <intent-filter>
                <action android:name="android.provider.Telephony.SMS_RECEIVED" />
            </intent-filter>
        </receiver >

    </application>

Để xử lý thông tin khi nhận được sự kiện nhận tin nhắn thì trong mã java ta không cần phải tạo một Activity mà chỉ cần tạo một class extends từ BroadcastReceiver, cụ thể mình sẽ demo một chương trình nhận tin nhắn như sau:
Screenshot from 2014-07-12 16:34:47

Các bạn thấy ở hình trên có một tin nhắn đến và được hiển thị ra một Dialog, số gửi tin là 5554 đây là port của máy ảo thôi, cách gửi tin nhắn đến máy ảo mình sẽ trình bày ở cuối bài viết.

Đầu tiên các bạn tạo một Project Android rỗng không có class nào cả nhé!

 

Screenshot from 2014-07-12 12:11:26

Ta sẽ đi lần lượt từng bước một, đầu tiên các bạn nhấn chuột phải vào thư mục src sau đó chọn tạo một class đặt tên là MyBroadcastReceiver.java có nội dung như sau:

package clbtinhoc.ictu.onreceiversms;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.SmsMessage;
import android.widget.Toast;

public class MyBroadcastReceiver extends BroadcastReceiver {

	// Phương thức này sẽ được gọi khi chương trình nhận được sự kiện có tin nhắn đến
	@Override
	public void onReceive(Context context, Intent intent) {

		processSMS(context, intent);
	}

	public void processSMS(Context context, Intent intent) {
		// lấy đối tượng bundle mình đã nói ở loạt bài về intent
		Bundle bundle = intent.getExtras();
		// Do hệ thống trả về một loạt các tin nhắn đến cùng lúc nên phải dùng mảng
		Object[] array = (Object[]) bundle.get("pdus"); // hành động "pdus" để lấy gói tin nhắn
	
		for (int i = 0; i < array.length; i++) {
			// lệnh này dùng được chuyển đổi Object về tin nhắn createFromPdu
			SmsMessage smsMessage = SmsMessage.createFromPdu((byte[]) array[i]);
			// lấy nội dung tin nhắn
			String msg = smsMessage.getMessageBody();
			// lấy số điện thoại tin nhắn
			String number = smsMessage.getDisplayOriginatingAddress();

			// Dùng Toast để hiển thị tin nhắn
//			Toast.makeText(context, number + "\n" + msg, Toast.LENGTH_LONG).show();
			
			// Hiển thị Tin nhắn lên một Dialog
			showDialog(context, intent, number, msg);
		}

		// Hiển thị tin nhắn cuối cùng trong số loạt tin nhắn nhận được lên Activity

	}
	
	public void showDialog(Context context, Intent intent, String number, String msg){
		/*
		 * vì MyBroadcastReceiver không kế thừa context (Activity) nên khi tạo intent mới không truyền
		 * this vào được, mà phải truyền cái context đã được gửi kèm
		 */
		// Gửi dữ liệu lên Activity mới
		Intent i = new Intent(context, MyView.class);
		Bundle bundle2 = new Bundle();
		bundle2.putString("PHONE", number);
		bundle2.putString("SMS", msg);
		i.putExtra("GOITIN", bundle2);
		/* Do đang làm việc trong BroadcastReceiver và một số vấn đề liên quan tới task 
		 * trong Android nên phải thêm cờ FLAG_ACTIVITY_NEW_TASK
		 */
		i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 
		context.startActivity(i); // cũng vì lý do trên nên phải dùng context để khởi động Activity

	}
}

Tại mã java xử lý trên chúng ta thấy nó không được kế thừa từ Activity như thông thường mà lại kế thừa từ BroadcastReceiver do đó để khởi động một Activity khi nhận được tin nhắn ta phải thông qua context mà được gửi kèm theo.
Đoạn mã trên có lệnh mở một Activity mang tên MyView.class nên ta sẽ tạo một class MyView nằm trong cùng package với MyBroadcastReceiver có nội dung như sau:

Class này chỉ làm nhiệm vụ đọc lại dữ liệu gửi từ MyBroadcastReceiver sau đó hiển thị lên giao diện.

package clbtinhoc.ictu.onreceiversms;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

public class MyView extends Activity{

	@Override
	public void onCreate(Bundle bundle){
		super.onCreate(bundle);
		setContentView(R.layout.layout_dialog);
		
		final TextView textView = (TextView) findViewById(R.id.textView1);
		
		Intent intent = getIntent();
		Bundle b = intent.getBundleExtra("GOITIN");
		String sms = b.getString("SMS");
		String number = b.getString("PHONE");
		
		this.setTitle(number);
		textView.setText(sms);
		
		((Button) findViewById(R.id.button)).setOnClickListener(new View.OnClickListener() {
			
			@Override
			public void onClick(View v) {
				finish();
			}
		});
	}
	
}

Đoạn code trên đã quá quen thuộc nên mình cũng không giới thiệu nhiều nữa.

Việc quan trọng nhất khi xây dựng ứng dụng nhận tin nhắn bạn phải đăng ký hành động nhận tin trong AndroidMaintifest:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="clbtinhoc.ictu.onreceiversms"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="8" />

    <uses-permission android:name="android.permission.RECEIVE_SMS" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >

        <!-- Đăng ký nhận thông báo từ BroadcastReceiver cho class MyBroadcastReceiver-->
        <receiver
            android:name="clbtinhoc.ictu.onreceiversms.MyBroadcastReceiver"
            android:label="On Receiver SMS" >
            <!-- Đăng ký lọc hành động nhận tin nhắn -->
            <intent-filter>
                <action android:name="android.provider.Telephony.SMS_RECEIVED" />
            </intent-filter>
        </receiver >

        <activity 
            android:name="clbtinhoc.ictu.onreceiversms.MyView"
            android:label="SMS"
            android:theme="@android:style/Theme.Dialog">
        </activity>
    </application>

</manifest>

Bình thường một ứng dụng sẽ không được phép truy cập ngẫu nhiên vào các thành phần mà hệ thống “cấm” như: Nhận tin nhắn, gửi tin, gọi điện,… do đó, ngoài việc đăng ký broadcastReceiver nhận tin nhắn bằng thẻ receiver ta còn phải đăng ký với hệ thống cho phép ứng dụng được nhận tin nhắn.

Ở trên ta có tạo một Activity MyView, nó sẽ được khởi động khi ứng dụng nhận được tin nhắn do đó trong AndroidMaintifest cũng phải đăng ký bằng thẻ . Thể hiện tính chuyên nghiệp của ứng dụn nhắn tin ta sẽ cho tin nhắn hiển thị thông qua Dialog bằng cách đặt Theme cho Activity MyView: “@android:style/Theme.Dialog”.

Ngay bây giờ bạn hãy chạy ứng dụng, khi chạy bạn sẽ không thấy có hiện tượng gì, tìm trong menu app cũng không thấy app vừa cài đâu cả là vì ứng dụng của ta không đăng ký là một chương trình laucher. Bạn thử gửi một tin nhắn vào máy mình xem, nếu mà không hiện tin nhắn bạn hãy vào phần quản lý ứng dụng xóa mặc định của các chương trình đọc tin nhắn khác đi nhé!

Note:
Với những bạn sử dụng máy ảo để test thì cách gửi tin nhắn vào máy ảo cũng khá đơn giản:
Bước 1: tại thẻ window của eclipse chọn Open Perspective\DDMS sẽ ra giao diện như hình dưới.

Screenshot from 2014-07-12 17:22:44

Bước 2: Tại thẻ Emulator trong mục Telephony Actions phần Incoming number bạn nhập số điện thoại của máy ảo. Máy ảo coi số hiệu port là số điện thoại luôn, để xem số hiệu port của máy ảo bạn mở máy ảo lên nhìn vào thanh tiêu đề ấy.

Screenshot from 2014-07-12 17:25:02

Như hình trên thì số hiệu cổng của máy ảo là 5554.

Bước 3: Chọn mục SMS sau đó nhập tin nhắn vào ô Message rồi ấn nút send là được.

Các bạn có thể download code mẫu ở đây:
https://www.dropbox.com/sh/11chl5lrj3xxg98/AAAHvHjcZ3-WXnH_6XGJfxHHa

[AndroidAdvance] Bài 1: Broadcast Receiver trong Android, đăng ký bằng coding

Nối tiếp loạt bài về Intent bài này mình giới thiệu tới các bạn một thành phần cũng rất quan trọng trong Android là BroadcastReceiver nó là một trong những thành phần chính của Android.  BroadcastReceiver dùng để nhận các intent từ hệ thống hoặc trao đổi dữ liệu giữa hai hay nhiều ứng dụng. Nói là nhận các intent từ hệ thống mới nghe có vẻ khó hiểu do đó mình sẽ có một ví dụ cụ thể sau:

Chắc hẳn nhiều bạn mới học Android mà có tính tò mò thì sẽ tự hỏi “khi có một tin nhắn đến phần mềm làm sao có thể biết được tin nhắn đến” hay là làm sao để biết có cuộc gọi đến? đang sạc pin hay đã rút sạc,…. Android cung cấp riêng một thành phần BroadcastReceiver dùng để nhận biết các thông tin đó từ hệ điều hành.

Một ứng dụng muốn nhận được các thông tin từ hệ thống nó phải đăng ký sử dụng BroadcastReceiver, đăng ký có hai cách:

1. Đăng ký ngay trong coding: Cách này ít được dùng vì chỉ khi ứng dụng mở lên nó mới nhận các thông tin từ hệ thống, khi ứng dụng tắt đi thì nó cũng không nhận được nữa.

2. Đăng ký trong AndroidMaintifest.xml: Cách này được sử dụng rất nhiều, với cách đăng ký này ngay cả khi ứng dụng của bạn đang tắt nó vẫn có thể nhận được thông tin từ hệ thống, khi nhận được thông tin nó lập tức khởi động ứng dụng (Bạn thử liên tưởng tới phần mềm nhắn tin xem, khi có tin cái là nó tự mở luôn 🙂 ).

Bài hôm nay mình sẽ trình bày tới các bạn cách thứ nhất đăng ký trong coding với demo nhận biết trạng thái sạc pin/ không sạc pin.

 Screenshot_2014-07-12-13-49-00 Screenshot_2014-07-12-13-49-07

Đăng ký nhận thông tin từ hệ thống chúng ta phải tạo một đối tượng IntentFilter để lọc những thông tin cần thiết, ở đây chúng ta sẽ lọc hai sự kiện cắm sạc và rút sạc.

IntentFilter filter = new IntentFilter();
// Đăng ký lọc sự kiện cắm sạc và rút sạc cho intentfilter
filter.addAction("android.intent.action.ACTION_POWER_CONNECTED");
filter.addAction("android.intent.action.ACTION_POWER_DISCONNECTED");

Sau khi đăng ký lọc sự kiện ta phải đăng ký với hệ thống xử lý các sự kiện đó qua BroadcastReceiver

// Khởi tạo BroadcastReceiver
BroadcastReceiver receiver = new BroadcastReceiver() {

	// Phương thức này sẽ được hệ thống gọi khi nhận được sự kiện đang sạc pin
	@Override
	public void onReceive(Context context, Intent intent) {
		// nội dung công việc cần làm khi sự kiện đã đăng ký xảy ra
	}
}

// Đăng ký nhận và xử lý các sự kiện
registerReceiver(receiver, filter); // được viết trong Activity

Ta sẽ đi vào viết mã cho app với file giao diện trước, về giao diện thì chỉ có một TextView thôi 🙂

<?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:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="Large Text"
        android:textAppearance="?android:attr/textAppearanceLarge" />

</LinearLayout>

Đăng ký cho hệ thống biết chương trình sẽ nhận thông tin bộ sạc ActivityMain.java

package opin.onreceiversms;

import opin.onreceiversms_incodeding.R;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.widget.TextView;

public class ActivityMain extends Activity {

	private BroadcastReceiver receiver = null;

	@Override
	public void onCreate(Bundle bundle){
		super.onCreate(bundle);
		setContentView(R.layout.main_activity);
		final TextView textView = (TextView) findViewById(R.id.textView1);

		IntentFilter filter = new IntentFilter();
		// Đăng ký lọc sự kiện cắm sạc và rút sạc cho intentfilter
		filter.addAction("android.intent.action.ACTION_POWER_CONNECTED");
		filter.addAction("android.intent.action.ACTION_POWER_DISCONNECTED");

		receiver = new BroadcastReceiver() {

			// Phương thức này sẽ được hệ thống gọi khi nhận được sự kiện đang sạc pin
			@Override
			public void onReceive(Context context, Intent intent) {

				// nếu sự kiện nhận được là kết nối sạc
				if(intent.getAction().equals(Intent.ACTION_POWER_CONNECTED)){
					textView.setText(" Đang sạc pin");
				}
				// nếu sự kiện nhận đưọc là rút sạc
				if(intent.getAction().endsWith(Intent.ACTION_POWER_DISCONNECTED)){
					textView.setText(" Đã rút sạc");
				}
			}
		};

		// Đăng ký nhận thông tin với hệ thống
		registerReceiver(receiver, filter);

	}

	@Override
	public void onDestroy(){
		// hủy đăng ký
		if (receiver != null) {
			unregisterReceiver(receiver);
		}
		super.onDestroy();
	}
}

Ở trên các bạn thấy lệnh:
filter.addAction(“android.intent.action.ACTION_POWER_CONNECTED”);
Để biết được các hành động nào có thể đăng ký mình sẽ hướng dẫn ở bài sau. hoặc bạn có thể xem một số hành động ở đây:
http://developer.android.com/reference/android/content/Intent.html

Các khâu thủ tục như vậy là xong, các bạn có thể chạy sau đó thử cắm và rút sạc xem sao.

Các bạn có thể download code mẫu ở đây:

https://www.dropbox.com/sh/hj4y6te7tp2hy48/AAD2vqJtuVCJ0FeEs-PguODea

[Tản mạn] Đôi điều suy nghĩ về phát triển phần mềm

Là một lập trình viên, công việc hàng ngày của bạn là viết code. Viết code cũng như xây dựng vậy, bạn phải đắp từng viên gạch để xây nên một ngôi nhà. Một ngôi nhà có chắc chắn và dễ thay đổi hay không không chỉ dựa vào việc bạn có dùng gạch tốt hay không, mà còn phụ thuộc rất lớn vào nhà thiết kế.

Gần đây tôi có đọc cuốn sách tựa đề ‘Practical Object Oriented Design in Ruby’ của Sandi Metz, một diễn giả ưa thích của tôi. Cuốn sách dành phần lớn để nói về các kĩ thuật thiết kế phần mềm với đối tượng là ngôn ngữ là Ruby. Tuy nhiên có rất nhiều ý tưởng của tác giả mà không chỉ giới hạn ở Ruby nói chung mà có thể áp dụng cho bất kì ngôn ngữ nào. Trong bài viết này tôi sẽ liệt kê ra một số ý trong cuốn sách mà tôi thấy rất hay và đáng nhớ.

Why

Hãy bắt đầu từ việc tại sao nên dành thời gian cho việc thiết kế phần mềm:

  • Việc nên dành một khoảng thời gian tương đối cho việc thiết kế phần mềm một cách nghiêm chỉnh hay không phụ thuộc vào việc: Logic mà bạn sẽ viết có tầm ảnh hưởng như thế nào đến hiện tại và tương lai. Ảnh hưởng đến hiện tại là nhiều khi một tính năng có ngay lập tức là quan trọng hơn cả. Ảnh hưởng đến tương lai là khi mà tính năng mà bạn code một cách cẩu thả sẽ ảnh hưởng vô cùng lớn đến tốc độ phát triển trong tương lai, gây ra các lỗi nghiêm trọng. Hãy nhìn việc thiết kế nghiêm chỉnh hay không như là một món nợ kĩ thuật (technical debt). Bạn thiết kế cẩu thả ở thời điểm hiện tại cũng như là bạn vay mượn thời gian ở tương lai vậy. Nếu khoảng thời gian đó không đến thì sẽ không sao, nhưng nếu đằng nào nó cũng xảy đến thì hãy suy nghĩ thật kĩ, bạn có thể đang làm mất thời gian trong tương lai của mình đó :).

How

Vậy việc nên dành thời gian nghiêm chỉnh để thiết kế phần mêm là nên, thì chúng ta nên thiết kế theo phương pháp thế nào:

  • Từ xưa, việc thiết kế phần mêm thường được thực hiện theo mô hình thác nước (water flow). Mô hình này được tiến hành từ trên cao xuống thấp qua các bước như nhận yêu cầu (requirement), thiết kế kiến trúc (architecture design), thiết kế cơ bản (Basic design).. Tuy nhiên mô hình này đòi hỏi bạn phải nắm rõ 90% yêu cẩu của dự án ngày từ đầu, và việc này gần như là không thể. Do đó gần đây xu hướng là Agile development, khi mà việc phát triển chia thành nhiều chặng nhỏ, mỗi chặng sẽ có yêu cầu rõ ràng, và qua mỗi chặng sẽ đánh giá lại tính khả thi của dự án và đưa ra yêu cầu cho chặng tiếp theo. Về cơ bản thì Agile development có tính khả thi hơn cả khi mà không ai có thể đoán trước được sản phẩm đầu ra khi nào sẽ hoàn thành và nên có tính năng ra sao.

Detail

Khi đã quyết được phương pháp thiết kế, việc quan trọng nhất, khó nhất, đó là bắt tay vào làm, bắt tay vào thiết kế chương trình. Để làm được việc này tốt quả thật là rất khó, bởi vì không có một tiêu chuẩn chung nào có thể áp dụng cho mọi yêu cầu, mọi chương trình. Bản thân tôi cũng là một junior software developer, nên tôi luôn gặp khó khăn mỗi khi viết một chương trình từ đầu (from the scratch). Có rất nhiều cách để giảm khó khăn, và tăng khả năng thiết kế của bạn như: nắm vững về các design pattern, nắm vững về domain logic, đọc về kiến trúc của các phần mêm open source nổi tiếng, và sử dụng các “luật” về thiết kế. Ở dưới đây tôi sẽ nói về một số “luật” mà cuốn sách đề cập đến, mà bản thân tôi thấy khá hữu dụng.

  • Rule 1: Nền tảng cơ bản của việt thiết kế hướng đối tượng, là việc các đối tượng thao tác với nhau qua việc gửi thông điệp (sending message). Do đó mà việc thiết kế một phần mêm sẽ xoay quanh việc bạn thiết kế sao cho các đối tượng gửi thông điệp cho nhau thông qua một interface dễ hiểu nhất, rõ ràng nhất. Hãy luôn hình dung bài toán của bạn sẽ được giải quyết thông qua một loạt các đối tượng gửi rất nhiều loại thông điệp cho nhau, bạn sẽ hình dung được kiến trúc tổng thể của chương trình dễ dàng hơn.
  • Rule 2: Single Responsibility: Đây là một luật khá cơ bản trong thiết kế hướng đối tượng. Ai cũng biết về luật này nhưng rất khó để làm theo, nhất là khi khối lượng chương trình tăng lên, và công việc chính của bạn hàng ngày là thêm logic vào một code base đã có. Luật này nói rằng mỗi class chỉ nên đảm trách một vai trò duy nhất. Làm thế nào để đảm bảo tính chất này là một việc khá mơ hồ. Cuốn sách nói rằng với mỗi class, bạn nên có thử mô tả về nó chỉ trong 1 câu. Làm được việc này một cách dễ dàng đảm bảo cho việc logic của class đó thống nhất và không bị lai tạp.
  • Rule 3: Giảm sự kết dính của code (Writing loosely coupled code). Cá nhân tôi thấy rule này là rule quan trọng bậc nhất trong việc thiết kế phần mềm. Muốn đánh giá một phần mềm được thiết kế tồi hay không, hãy nhìn vào việc các logic có bị kết dính(couple) hay phụ thuộc vào nhau hay không. Vậy các bạn sẽ hỏi “kết dính” cụ thể ở đây có nghĩa là gì? Sự kết dính được hình thành khi logic này “phụ thuộc” vào logic khác. Cụ thể hơn ở khái niệm phụ thuộc, đó là việc mà khi mà một trong logic của class A lại chứa các logic class B, hay nói cách khác là khi A “biết” quá nhiều về B thì khi đó A sẽ phụ thuộc vào B. Khái niệm này hay được nhắc đến bằng những cụm từ khác như là logic hiding, tức khi thiết kế một class, bạn phải giấu logic của class đó càng nhiều càng tốt. Đó chính là lý do tại sao các ngôn ngữ như java có những keyword như public, private hay protected. Vậy quay lại từ đầu, để giảm sự kết dính của code thì chúng ta phải làm một việc là thiết kế sao cho các class không phụ thuộc vào nhau, và “biết” càng ít về nhau càng tốt. Vì vậy mỗi khi bạn viết một đoạn code nào đó, bạn hãy tự đọc lại và xem đoạn code đó có sử dụng quá nhiều logic của một class hay logic bên ngoài không. Để giải quyết cho việc “writing loosely coupled code” thì có khá nhiều kĩ thuật nổi tiếng như là: Inject Dependencies, Isolate Dependencies, Reversing Dependencies mà nếu có dịp tôi sẽ giới thiệu trong một bài viết khác. Ngoài ra còn có một luật rất hữu dụng để giải quyết vấn đề kết dính của code được gọi là Law of Demeter, các bạn có thể tham khảo ở đường link tôi vừa gửi.

Auto Testing

Bản thân việc testing không nằm trong khâu “thiết kế” phần mềm. Auto testing (hay là unit test) chỉ là một bước để đảm bảo rằng logic hiện tại đang có là đang chạy “gần như là” tốt (nói gần như là do unit test không thể đảm bảo 100% việc “chạy tốt” của tất cả mọi logic. Tuy nhiên chỉ việc đảm bảo “gần như” tốt thôi đã cho thấy tầm quan trọng của testing. Có một việc mà bất kì một nhà phát triển nào khi mới bắt đầu viết test, và ngay cả những người đã quá quen việc kĩ thuật TDD (Test Driven Development) cũng sẽ băn khoăn, đó là việc nên test cái gì. Trong cuốn sách Sandi đã chia khá rõ ràng về 2 phần mà bạn nên test đó là:

  • Test Incomming Message
  • Test Ougoing Message

Như tôi đã đề cập ở trên, bản chất của việc thiết kế hướng đối tượng xoay quanh việc các class sẽ gửi message cho nhau. Do đó khi test chúng ta cũng nên xoay quanh khái niệm mesage này. Một cách đơn giản, Incomming Message tức là các message được “gửi” đến một object X, và test các message đó tương đương với việc bạn sẽ test các interface của object X đó được công khai (public interface) ra ngoài.Outgoing Message hơi phức tạp hơn một chút, giả sử bạn có một object X với method Foo, trong Foo sẽ gọi method Bar để thực hiện một logic nào đó. Việc test Foo sẽ gọi Bar đúng N lần, với kết quả nhất định sẽ được gọi là test Outgoing Message. Việc chia ra làm 2 loại message cần test sẽ giúp cho bạn nhìn thấy một cách rõ ràng hơn cái gì nên test, và cái gì không nên test.

Conclusion

Ở trên tôi đã trình bày về một số suy nghĩ của cá nhân, và các suy nghĩ của Sandi Metz trong cuốn sách về thiết kế phần mềm. Bản thân việc thiết kế được phần mềm tốt là rất khó, mà mỗi một dạng phần mềm, với mỗi một logic domain lại có một cách giải quyết riêng. Không có một cách giải quyết nào chung cho mọi bài toán cả, nhưng có một số qui tắc chung mà bán có thể áp dụng được cho nhiều bài toán khác nhau. Để nắm được các qui tắc đó đòi hỏi bạn không những phải đọc nhiều, làm nhiều, tích luỹ nhiều kinh nghiệm, mà còn dựa trên việc bạn thất bại nhiều nữa. Tạo ra các phần mềm tồi, khó bảo trì cũng là một bước đệm tốt để bạn rút kinh nghiệm cho các lần sau :).

Theo: Blogs kỹ thuật máy tính http://ktmt.github.io/

[Tản mạn IT] Định Luật Amdahl trog lập trình

Bài viết này mình đọc ở blogs http://ktmt.github.io/ thấy rất hay nên dẫn về đây để tiện theo dõi sau này 🙂

Có 1 người bạn gần đây bắt đầu lập trình với threads và thiết kế chương trình như sau.

Chương trình có đầu vào là một mảng gồm một số phần tử (khoảng vài chục). Chương trình làm nhiệm vụ duyệt từng phần tử trong mảng, tính toán và trả về kết quả đối với từng phần tử. Bạn mình thiết kế chương trình bằng cách với mỗi phần tử trong mảng, bạn tạo một thread và cho thread thực hiện tính toán với phần tử đó.

Khi mình hỏi tại sao bạn lại thiết kế chương trình như thế thì bạn trả lời: các thread sẽ chạy song song nên về lý thuyết càng nhiều thread thì chương trình chạy càng nhanh!

Mình nhận ra bạn mình có về không biết định luật Amdahl, tuy đơn giản nhưng lại là một định luật rát quan trọng trong tính toán song song. Khi hiểu định luật này chắc chắn bạn sẽ có cái nhìn tổng quan hơn về hệ thống máy tính nói chung, và cụ thể là lập trình multithread nói riêng. Trong bài viết này, mình muốn giới thiệu định luật Amdahl.

Định luật Amdahl

Giả sử bạn thay CPU mới có tốc độ cao hơn CPU cũ.

Định luật Amdahl nói rằng sự tằng tốc nhờ cải thiện hiệu năng của CPU = thời gian chạy toàn bộ tác vụ khi sử dụng CPU cũ / thời gian chạy toàn bộ tác vụ khi sử dụng CPU mới.

Độ tăng tốc phụ thuộc vào 2 thừa số:

  • Tỉ lệ chương trình có thể cải thiện nhờ CPU mới. Ví dụ chương trình của bạn có 60 tính toán, 20 tính toán có thể được chuyển qua CPU mới (ví dụ CPU mới cung cấp tập lệnh mà CPU không có) như vậy tỉ lệ này là 20/60. Tỉ lệ này luôn nhỏ hơn hoặc bằng 1.
  • Độ Tăng tốc thu được thu được từ CPU mới. Ví dụ 20 tính toán ở ví dụ trên ở CPU cũ hết 5s, CPU mới hết 2s, độ tăng tốc sẽ là 5/2

Thời gian chạy với CPU mới = Thời gian chạy CPU cũ * (1 – tỉ lệ chương trình có thể cải thiện nhờ CPU mới + tỉ lệ chương trình có thể cải thiện nhờ CPU mới / độc tăng tốc thu được từ CPU mới).

 Độ tăng tốc tổng thể = Thời gian chạy trên CPU cũ / Thời gian chạy trên CPU mới     
                      = 1 / (1 - tỉ lệ chương trình cải thiện nhờ CPU mới + tỉ lệ chương trình cải thiện nhờ CPU mới / độ tăng tốc thu được từ CPU mới)

Ví dụ 1:

Bạn thay CPU cho máy chủ web. CPU mới chạy nhanh hơn CPU cũ 10 lần. Chương trình web của bạn giả sử tốn 60% cho SQL (I/O) và 40% tính toán (nhận kết quả từ cơ sở dữ liệu, render page). Hỏi tốc độ cải thiện từ việc thay CPU là bao nhiêu?

Giải:

  • Tỉ lệ chương trình có thể cải thiện nhờ CPU mới = 0.4
  • Độ tăng tốc = 10

Độ tăng tốc tổng thể = 1 / (0.6 + 0.4/10) = 1 / 0.64 = 1.56

Vậy dù rằng CPU có tính nhanh 10 lần thì tốc độ của cả hệ thống chỉ được cải thiện 1.56 lần.

Ví dụ 2:

Hàm căn bậc hai của một số thực được sử dụng rất nhiều trong đồ hoạ máy tính. Giả sử tính toán căn bậc 2 chiếm 20% tổng thời chạy của thao tác đồ hoạ. Bạn muốn tăng tốc độ của hệ thống đồ hoạ của bạn. Có 2 lựa chọn sau đây:

  • Mua card đồ hoạ mới với chip tính toán nhanh hơn 10 lần.
  • Tăng tốc độ của các thao tác số thực khác lên 1.6 lần (ngoài thao tác tính căn bậc 2). Giả sử tổng số thao tác số thực là 50% (50% tính toán của bạn liên quan đến số thực).

Bạn sẽ đầu tư tiền hay bỏ thời gian và trí não cải thiện các thao tác còn lại.

Giải:

Trường hợp 1, độ tăng tốc = 1 / (0.8 + 0.2 / 10) = 1 / 0.82 = 1.22

Trường hợp 2, độ tăng tốc = 1 / (0.5 + 0.5 / 1.6) = 1.23

Như vậy lựa chọn 2 cho kết quả tốt hơn 1 chút!

Quan sát

Nếu thử quan sát, bạn sẽ thấy từ công thức Amdahl có thể rút ra là độ tăng tốc phụ thuộc cả vào bản chất bài toán. Nếu tỉ lệ có thể tăng tốc được không cao, việc bạn thêm song song cũng không giải quyết vấn đề gì. Nói cách khác nếu tỉ lệ cải thiện nhờ CPU mới = 0 thì độ tăng tốc tổng thể sẽ là 1 / (1 + 0/10) = 1 tức không thay đổi.

Bạn mình sai lầm ở đâu?

Quay trở lợi vấn đề của bạn mình, tại sao mình lại nghĩ việc tăng số thread lên không giải quyết được tốc độ?

Giả sử bạn CPU bạn có 4 cores (Ví dụ Corei7 MQ). Chương trình của bạn sẽ được lập lịch bởi kernel. Nếu bạn dùng 2 threads, tại thời điểm CPU được cấp cho process của bạn, 2 cores sẽ được sử dụng để chạy chương trình. Giả sử chương trình bạn dùng CPU để tính toán 50% thời gian, 50% thời gian còn lại được chia đều cho các cores.

Nếu không dùng thread, chương trình của bạn là 1 chương trình liên tục bình thường, tốc độ sẽ cải thiện sẽ là:

Độ tăng tốc = 1 / (0.5 + 0.5 / 1) = 1 (không tăng tí nào!)

Nếu bạn dùng 2 threads:

Độ tăng tốc = 1 / (0.5 + 0.5 / 2) = 1.33 (Tăng 33%!)

Nếu bạn dùng 4 threads:

Độ tăng tốc = 1 / (0.5 + 0.5 / 4) = 1.6 (Tăng 60%!)

Nếu dùng 8 threads, bạn mong chờ tốc độ tăng tốc là 1.7! Sai lầm! Lý do: giống như quan sát ở trên, bản thân việc chia việc cho CPU không phải là việc làm song song được. Nói cách khác CPU chỉ thực hiện được cùng 1 lúc 4 tác vụ. Nếu có nhiều hơn 4 tác vụ, tỉ lệ thực hiện song song (số task thực hiện đồng thời không đổi, nhưng só task phải thực hiện tăng lên) sẽ giảm khiến hiệu năng toàn hệ thống giảm xuống.

Ví dụ ta có 4 threads, thì số task có thể tận dụng được CPU là 100%. Khi ta có 5 threads, số threads có thể tận dụng được CPU sẽ giảm xuống 80%. Ta có thể xem sự thay đổi về hiệu năng so sánh tương đối với trường hợp 1 thread như sau:

4 threads: Độ tăng tốc = 1 / (0 + 1 / 4) = 4

5 threads: Độ tăng tốc = 1 / (0.2 + 0.8 / 4) = 1 / 0.4 = 2.5

Như vậy độ tăng tốc tương đối với trường hợp chỉ sử dụng 1 thread đã giảm từ 4 lần xuông còn 2.5 lần.

Nói cách khác, khi tất cả các cores đã làm việc thì việc tăng threads sẽ chỉ làm tăng thêm phần không thể tính song song, khiến hiệu năng hệ thống giảm. Ngoài ra còn có các chi phí khác mà ta chưa kể đến như: tạo một thread cũng tốn thời gian, bộ nhớ v.v. Nói cách khác việc tăng thread không làm tăng tốc độ chương trình mà nhiều trường hợp còn làm giảm tốc độ chạy. Suy nghĩ lúc đầu của bạn mình là sai lầm!

Design nhờ định luật Amdahl

Như ở ví dụ 2 ở trên, bạn thấy rằng việc mua card đồ hoạ mới không làm tăng hiệu năng tổng thể như việc tối ưu chương trình. Như vậy ta hoàn toàn có thể thay đổi thiết kế chương trình để làm tăng hiệu năng. Ta xét bài toán ví dụ sau đây:

Nhập n. In ra tất cả các số nhỏ hơn n mà là số nguyên tố.

Dưới góc độ thread, ta có 2 cách design hệ thống (Giả định hệ thống có CPU 4 cores)

  • Chia n ra làm 4 phần, mỗi thread thực hiện tìm số nguyên tố trong 1 phần.
  • Một biến đếm từ 3 -> n, bước nhảy 2, cho mỗi thread đang rảnh lần lượt kiểm tra xem số hiện tại có phải là số nguyên tố không.

Bạn sẽ chọn cách nào?

Thoạt nhìn có vẻ 2 cách không có gì khác nhau, nhưng nếu để ý sẽ nhận ra là mật độ số nguyên tố không giống nhau. Nói cách khác nếu làm theo cách 1, sẽ có thread rất nhanh hoàn thành (thread phải xử lý vùng ít số nguyên tố), và có những thread phải làm việc rất vất vả (thread phải xử lý vùng có nhiều số nguyên tố). Nói cách khác cách design 1 có tỉ lệ tính toán có thể cải thiện không cao.

Cách 2 thoạt nhìn có vẻ chậm nhưng lại là cách cho tỉ lệ xử lý song song cao hơn, vì việc xử lý từng số một không phụ thuộc và phân bố của số nguyên tố!

Vậy ta nên thiết kế chương trình theo cách 2!

Tổng kết

Bài viết giới thiệu định luật Amdahl, làm rõ ý nghĩa định luật cũng qua 2 ví dụ đồng thời áp dụng định luật Amdahl vào việc thiết kế bài toán đơn giản. Hy vọng qua bài viết bạn hiểu phần nào về đột tăng tốc trong tính toán song song, cũng như biết cách tính toán định lượng để đánh giá các thiết kế (Nhiều khi mua máy mới không hắn đã là tốt!).

Quiz

  1. Lý giải tại sao các hệ thống lại thiết kế dùng worker queue!
  2. mysql có biến innodb_read_io_threads. Bạn sẽ thiết lập giá trị biến này là bao nhiêu?

Tài liệu tham khảo

  1. Định luật Amdahl
  2. Computer Architecture, A Quantitative Approach