12/08/2018, 10:09

Developing Android services - Phần 2

Developing Android services - Phần 2 Như đã giới thiệu ở bài trước Tôi đã giới thiệu thế nào là một Service trong Android, tạo một service đơn giản và Thực hiện một tác vụ chạy dài sử dụng Service tới các bạn. Bài viết này tôi sẽ tiếp tục với 2 nội dung chính sau đây: Làm sao để cải ...

Developing Android services - Phần 2

Như đã giới thiệu ở bài trước Tôi đã giới thiệu thế nào là một Service trong Android, tạo một service đơn giản và Thực hiện một tác vụ chạy dài sử dụng Service tới các bạn. Bài viết này tôi sẽ tiếp tục với 2 nội dung chính sau đây:

  • Làm sao để cải thiện hiệu xuất của một task lặp trong một service
  • Làm sao để một activity và một service giao tiếp được với nhau

1. Cải thiện hiệu xuất của một task chạy lặp trong Service


Bên cạnh việc thực hiện một tác vụ chạy dài, bạn cũng có thể thực hiện một tác vụ chạy lặp đi lặp lại trong service. Ví dụ như, Bạn có thể viết một ứng dụng làm đồng hồ báo thức sử dụng Service, service này chạy liên tục dưới dạng backgroud. Trong trường hợp này, Service của bạn có thể phải định kỳ chạy một khố lệch để kiểm tra xem liệu có một lịch trình công việc nào cần thực hiện không và sẽ bật âm thanh báo thức. Để thực hiện được điều này bạn cần dùng đến `Time` class trong service. Hãy cùng tôi xem xét ví dụ sau: Sử dụng Time class trong service
  • QuanService.java
package com.example.hoangquan.quanservice;
import android.app.Service;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;
import java.net.URL;

public class QuanService extends Service {
	int counter = 0;
	static final int UPDATE_INTERVAL = 1000;
	private Timer timer = new Timer();
	@Override
	public IBinder onBind(Intent arg0) {
		return null;
	}
	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		Toast.makeText(this, “Service Started”, Toast.LENGTH_LONG).show(); 			doSomethingRepeatedly();
		return START_STICKY;
	}
	private void doSomethingRepeatedly() {
		timer.scheduleAtFixedRate( new TimerTask() { public void run()
		{
			Log.d(“MyService”, String.valueOf(++counter)); } }, 0, UPDATE_INTERVAL);
		}
	@Override
	public void onDestroy()
	{
		super.onDestroy();
		if (timer != null){
			timer.cancel();
		}
	Toast.makeText(this, “Service Destroyed”, Toast.LENGTH_LONG).show();
	}
}

Chạy chương trình Nhấn vào nút Start và xem kết quả

01-16 15:12:04.364: DEBUG/QuanService(495): 1
01-16 15:12:05.384: DEBUG/QuanService(495): 2
01-16 15:12:06.386: DEBUG/QuanService(495): 3
01-16 15:12:07.389: DEBUG/QuanService(495): 4
01-16 15:12:08.364: DEBUG/QuanService(495): 5
01-16 15:12:09.427: DEBUG/QuanService(495): 6
01-16 15:12:10.374: DEBUG/QuanService(495): 7

Giải thích ví dụ:

Trong Ví dụ này, Tôi đã tạo ra một Đối tượng kiểu Timer và gọi Phương thức scheduleAtFixedRate() trong function doSomethingRepeatedly(). trong đó định nghĩa như sau:

private void doSomethingRepeatedly() {
	timer.scheduleAtFixedRate(new TimerTask() {
		public void run() {
			Log.d(“MyService”, String.valueOf(++counter));
		}
	}, 0, UPDATE_INTERVAL);
}

Bạn thông qua một instance của TimerTask class để thực hiện phương thức scheduleAtFixedRate() qua đó bạn thực thi khối lệnh của mình trong phương phương thức run() chạy liên tục. Đối số thứ hai scheduleAtFixedRate() chỉ định tổng thời gian tính bằng mini giây trước lần thực thi đầu tiên. Tham số thứ ba chỉ định thời gian giữa các hành động tiếp theo.

Trong ví dụ trên, Về cơ bản tôi in ra giá trị đếm được mỗi giây. Service sẽ liên tục in ra giá trị counter cho đến khi service kết thúc.

 @Override
public void onDestroy() {
	super.onDestroy();
	if (timer != null){
		timer.cancel();
	}
	Toast.makeText(this, “Service Destroyed”, Toast.LENGTH_LONG).show();
}

Đối với phương thức scheduleAtFixedRate(), Code của bạn sẽ được thực thi cố định với khoảng thời gian cụ thể bất kể bao lâu. Ví dụ, Nếu code ở trong phương thức run() mất hai giây để hoàn thành thì nhiệm vụ thứ hai sẽ thực hiện ngay sau khi Nhiệm vụ thứ nhất kết thúc. Tương tự như vậy, Nếu bạn để delay ba giây và nhiệm vụ đó mất hai giây để hoàn thành thì nhiệm vụ thứ 2 phải chờ một giây để bắt đầu hoạt động.

Thực hiện Một Task không đồng bộ trên các Theard riêng biệt sử dụng IntentService

Như bạn đã biết, Một Service sẽ chạy liên tục và có thể chạy lặp đi lặp lại. Vì vậy rất tốn tài nguyên của thiết bị. Do đó bạn phải thực hiện phương thức stopSelf() để dừng service của mình khi chắc chắn rằng nó đã hoàn thành xong tất cả nhiệm vụ của nó. Thật không may là đôi khi chính bạn cũng không biết khi nào task được hoàn thành hoặc quên việc stop service. Để tạo dễ dàng hơn khi tạo ra một service không đồng bộ và chấm dứt nó ngay khi nó được thực hiện bạn có thể sử dụng IntentService.

IntentService class là một class base của Service để xử lý các yêu cầu không đồng bộ. Nó sẽ được khởi động như một Service bình thường và thực thi các task trong một "Worker thread" và tự hủy khi nhiệm vụ đó chấm dứt.

Xem xét ví dụ sau đây:

  • MyIntentService.java
package com.example.hoangquan.quanservice;
import java.net.MalformedURLException;
import java.net.URL;
import android.app.IntentService;
import android.content.Intent;
import android.util.Log;
public class MyIntentService extends IntentService {
        public MyIntentService() {
            super(“MyIntentServiceName”);
        }
@Override
protected void onHandleIntent(Intent intent) {
        try {
            int result = DownloadFile(new URL(“http://www.amazon.com/somefile.pdf”)); Log.d(“IntentService”, “Downloaded “ + result + “ bytes”);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
}
private int DownloadFile(URL url) {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } return 100; }
}
  • AndroidManifest.xml
<?xml version=”1.0” encoding=”utf-8”?>
<manifest xmlns:android=”http://schemas.android.com/apk/res/android”
 package=”net.learn2develop.Services”
 	android:versionCode=”1”
android:versionName=”1.0”>
    <application android:icon=”@drawable/icon” android:label=”@string/app_name”>
        <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>
        <service android:name=”.QuanService” />
        <service android:name=”.MyIntentService” />
    </application>
    <uses-sdk android:minSdkVersion=”9” />
    <uses-permission android:name=”android.permission.INTERNET”></uses-permission>
</manifest>
  • MainActivity.java
public class MainActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Button btnStart = (Button) findViewById(R.id.btnStartService);
        btnStart.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                //startService(new Intent(getBaseContext(), QuanService.class));
                startService(new Intent(getBaseContext(), MyIntentService.class));
            }
        });
        Button btnStop = (Button) findViewById(R.id.btnStopService);
        btnStop.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                stopService(new Intent(getBaseContext(), QuanService.class));
            }
        });
    }
}

Chạy ứng dụng và click nút " Start Service" sau đó xem logCat bạn sẽ thấy kết quả như sau:

01-17 03:05:21.244: DEBUG/IntentService(692): Downloaded 100 bytes

Giải thích ví dụ:

Bạn cần phải implement một constructor cho class của mình sau đó gọi supperclass với tên của IntentService của bạn:

	public MyIntentService() {
        super(“MyIntentServiceName”);
    }

Sau đó bạn implement phương thức onHandleIntent() để sử lý các task của bạn như một worker thread:

	@Override
    protected void onHandleIntent(Intent intent) {
        try {
            int result =
                DownloadFile(new URL(“http://www.amazon.com/somefile.pdf”));
            Log.d(“IntentService”, “Downloaded “ + result + “ bytes”);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
    }

Trong Phương thức onHandleIntent() bạn định nghĩa khối lệnh của mình để thực thi các tác vụ mà bạn muốn. Sau khi các task vụ được hoàn thành thì Thread bị hủy đồng thời service cũng được stop một cách tự động.

2. Giao tiếp giữa một Service và một Activity.

Thông thường một Service đơn giản sẽ thực thi trong luồng (Thread) riêng của nó, độc lập với activity gọi nó. Điều nàu sẽ không vấn đề gì nếu service của bạn chỉ thực hiện các tác vụ định kỳ mà không cần báo cáo tiến độ hoặc trạng thái của nó. Tuy nhiên, Trong trường hợp bạn muốn tạo ra một service giám sát một địa chỉ cụ thể. Trong trường hợ này, Service của bạn cần phải logs lại một địa chỉ gần địa chỉ bạn muốn giám sát. Lúc đó Service sẽ cần phải giao tiếp với activity để đưa ra thông tin về địa chỉ cho người dùng. Như vậy ta cần phải có một cách nào đó để Service có thể giao tiếp được với activity.

Xem xét ví dụ sau:

  • MyIntentService.java
package com.example.hoangquan.quanservice;
import java.net.MalformedURLException;
import java.net.URL;
import android.app.IntentService;
import android.content.Intent;
import android.util.Log;
public class MyIntentService extends IntentService {
	public MyIntentService() {
		super(“MyIntentServiceName”);
	}
	@Override
	protected void onHandleIntent(Intent intent) {
		try {
        	int result = DownloadFile(new URL("http://www.amazon.com/somefile.pdf"));
			Log.d(“IntentService”, "Downloaded " + result + “ bytes”);
			Intent broadcastIntent = new Intent();
        	broadcastIntent.setAction(“FILE_DOWNLOADED_ACTION”);
        	getBaseContext().sendBroadcast(broadcastIntent);
		} catch (MalformedURLException e) {
			e.printStackTrace();
	}
}

	private int DownloadFile(URL url) {
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		} return 100;
	}
}

  • MainActivity.java
package com.example.hoangquan.quanservice;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import android.content.IntentFilter;
public class MainActivity extends Activity {
IntentFilter intentFilter;
	@Override
	public void onCreate(Bundle savedInstanceState) {
    	super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		intentFilter = new IntentFilter();
        intentFilter.addAction(“FILE_DOWNLOADED_ACTION”);

		registerReceiver(intentReceiver, intentFilter);
		Button btnStart = (Button) findViewById(R.id.btnStartService);
        btnStart.setOnClickListener(new View.OnClickListener() {
			public void onClick(View v) {
				startService(new Intent(getBaseContext(),
                MyIntentService.class));
 		}
});

	Button btnStop = (Button) findViewById(R.id.btnStopService);
    btnStop.setOnClickListener(new View.OnClickListener() {
		public void onClick(View v) {
			stopService(new Intent(getBaseContext(), MyService.class));
		}
	});
}

	private BroadcastReceiver intentReceiver = new BroadcastReceiver() {
		@Override
		public void onReceive(Context context, Intent intent) {
		Toast.makeText(getBaseContext(), "File downloaded!",Toast.LENGTH_LONG).show();
		}
	};
}

Chạy ứng dụng, Nhấn nút start sercive sau đó đợi 5 giây và Toast sẽ hiện lên.

Giải thích ví dụ

Để thông báo cho Activity của bạn biết service đã hoạt động xong trong Android cũng cấp class BroadcastReceiver để bạn phát sóng tín hiệu cho Activity biết. sử dụng Method sendBroadcast() :

	protected void onHandleIntent(Intent intent) {
		try { int result = DownloadFile(new URL("http://www.amazon.com/somefile.pdf"));
		Log.d(“IntentService”, “Downloaded “ + result + “ bytes”);

		Intent broadcastIntent = new Intent();
        broadcastIntent.setAction(“FILE_DOWNLOADED_ACTION”);
        getBaseContext().sendBroadcast(broadcastIntent);
		} catch (MalformedURLException e) {
			e.printStackTrace();
	}
}

Action của Intent mà bạn phát sóng được set “FILE_DOWNLOADED_ACTION”, Điều này có nghĩa là bất kỳ Activity nào đăng lắng nghe đều được gọi. Do đó, trong file MainActivity.java bạn đăng ký Phương thức registerReceiver() từ lớp IntentFilter để lắng nghe sự kiện mà bạn phát song trước đó:

public void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.main);
	intentFilter = new IntentFilter();
    intentFilter.addAction(“FILE_DOWNLOADED_ACTION”);
	registerReceiver(intentReceiver, intentFilter);

	Button btnStart = (Button) findViewById(R.id.btnStartService);
    btnStart.setOnClickListener(new View.OnClickListener() {
		public void onClick(View v) {
			 startService(new Intent(getBaseContext(), MyIntentService.class));
        }
 	}
 });

alt

Khi Intent được nhận nó sẽ gọi đến instance của class BroadcastReceiver mà bạn đã định nghĩa:

private BroadcastReceiver intentReceiver = newBroadcastReceiver() {
	@Override
	public void onReceive(Context context, Intent intent) {
    	Toast.makeText(getBaseContext(), "File downloaded!", Toast.LENGTH_LONG).show();
		}
	};
}

Trong trường hợp này Toast “File downloaded” sẽ được hiển thị. Tất nhiên trong service của mình bạn cần phải lấy ra dữ liệu mà Service trả về.

Trên đây, Bạn vừa thực hiện phát sóng và lắng nghe một Service từ một Activity. Tuy nhiên , Các ví dụ trên đều đơn giản là đếm thời gian hoặc fix cứng giá trị. Một service trên thực tế phức tạp hơn nhiều và đòi hỏi phải trả lại giá trị chính xác mà service làm được cho bạn. Giả sử bạn muốn Activity gọi chính xác những gì File được tải về thay vì hardcode thì bạn cần làm những thứ sau đây:

Đầu tiên bạn gọi Activity tạo ra Intent với service name:

Button btnStart = (Button) findViewById(R.id.btnStartService);  btnStart.setOnClickListener(new View.OnClickListener() {
	public void onClick(View v) {
 		Intent intent = new Intent(getBaseContext(), MyService.class);
	}
});

Sau đó bạn truyền vào mảng các url:

Button btnStart = (Button) findViewById(R.id.btnStartService);  btnStart.setOnClickListener(new View.OnClickListener() {
 	public void onClick(View v) {
 		Intent intent = new Intent(getBaseContext(),​MyService.class);
 		try {
 			URL[] urls = new URL[]
			{
				new URL("http://www.amazon.com/somefiles.pdf"), new
                URL("http://www.wrox.com/somefiles.pdf"), new
                URL("http://www.google.com/somefiles.pdf")            
0