Developing Android services - Phần 1
Bài đăng này đã không được cập nhật trong 9 năm
Developing Android services (Phần 1)
Nội dung gồm những phần chính sau đây:
- Làm sao để tạo một service và chạy nó dưới dạng Backgroud
- Làm sao để Thực thi task lâu dài trong một thread riêng biệt
Một service là một ứng dụng chạy ngầm trong Android mà không cần tương tác với người dùng. Ví dụ như, Trong khi sử dụng một ứng dụng nào đó bạn cũng có thể chơi nhạc trong cung thời gian đó như một ứng dụng chạy ngầm. Trong trường hợp này, chỉ cần code để chơi nhạc chạy ngầm mà không cần phải tương tác với người dùng, và do đó nó có thể chạy như một service. Service cũng là một ý tường tốt trong trường hợp không cần phải hiển thị Giao diện người dùng. Một ví dụ điển hình trong trường hợp này là một ứng dụng cần liên lục ghi lại vị trí địa lý của thiết bị di dộng. Truong trường hợp này bạn cần viết một service để chạy nền. Vậy trong bài viết này tôi sẽ đề cập đến việc làm sao để tạo ra một service và sử dụng chúng dưới dạng các tác vụ chạy ngầm không đồng bộ.
1. Tạo một service của bạn
Cách tốt nhật để hiểu một service là gì là tạo ra nó. xem xét ví dụ sau: + Tạo một Android project mới
- Thêm một class mới với tên MyService.java
package net.learn2develop.Services;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.widget.Toast;
public class MyService extends Service {
@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();
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
Toast.makeText(this, “Service Destroyed”, Toast.LENGTH_LONG).show();
}
}
- 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=”.MyService” />
</application>
<uses-sdk android:minSdkVersion=”9” />
</manifest>
- main.xml file
<?xml version=”1.0” encoding=”utf-8”?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
android:orientation=”vertical”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
>
<Button android:id=”@+id/btnStartService”
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:text=”Start Service” />
<Button android:id=”@+id/btnStopService”
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:text=”Stop Service” />
</LinearLayout>
- Xử lý event cho các button trong MainActivity.java
package net.learn2develop.Services;
import android.app.Activity;
import android.os.Bundle;
import android.content.Intent;
import android.view.View;
import android.widget.Button;
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(), MyService.class));
}
});
Button btnStop = (Button) findViewById(R.id.btnStopService);
btnStop.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
stopService(new Intent(getBaseContext(), MyService.class));
}
});
}
}
Kết quả là:
Khi bạn nhấn vào nút "Start Service" thì Toast sẽ hiện lên.
Giải thích ví dụ:
Ví dụ trên là một ví dụ đơn giản nhất về service
mà bạn có thể tạo ra. Dĩ nhiên là ví dụ này cũng không có bất kỳ tiện ích nào, nó chỉ minh họa cho việc tạo ra một process
.
Đầu tiên, Ta định nghĩa ra một class extends từ class base là Service
. Tất cả các service đều extends từ class này.
public class MyService extends Service {
}
Trong class Myservice này bạn thực thi một số phương thức sau:
@Override
public IBinder onBind(Intent arg0) {...}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {...}
@Override
public void onDestroy() {...}
Phương thức onBind()
cho phép bạn bind
một activity
và một service
. Điều này cho phép activity đó có quyền truy cập vào các phương thức khác bên trong một service.
Phương thức onStartCommand()
sẽ được gọi khi service sử dụng phương thức startService()
. Trong phương thức này bạn sẽ code những thứ mà bạn muốn sau đó return ra một hằng số START_STICKY
nhằm mục đích giữ cho ứng dụng chạy cho đến khi bạn thực sự muốn dừng nó.
Phương thức onDestroy()
được gọi khi service stop khi sử dụng phương thức stopService()
. Điều này cũng đồng nghĩa với việc bạn giải phóng tài nguyên được sử dụng phục vụ service của bạn.
Tất cả các service mà bạn tạo ra đều phải được khai báo trong AndroidManifest.xml
:
<service android:name=”.MyService” />
Nếu bạn muốn service của mình có thể được sử dụng cho các ứng dụng khác nữa thì bạn cần phải add thêm một intent-filter
nữa:
<service android:name=”.MyService”>
<intent-filter>
<action android:name=”net.learn2develop.MyService” />
</intent-filter>
</service>
Để start
một service:
startService(new Intent(getBaseContext(), MyService.class));
Để stop
một service"
stopService(new Intent(getBaseContext(), MyService.class));
2. Thực thi một Task chạy lâu dài trong ứng dụng.
Ở phần trên chúng ta đã tạo ra một Service cơ bản nhất. Tuy nhiên nó lại không hề có tiện ích gì. Bây giờ chúng ta hãy tạo ra một service có ích một chút.
Xem sét ví dụ sau dùng đề Download một file trên internet:
- MainActivity.java
package net.learn2develop.Services;
import android.app.Service;
import android.content.Intent;
import android.content.Intent;
import android.os.IBinder;
import android.widget.Toast;
import java.net.MalformedURLException;
import java.net.URL;
public class MyService extends Service {
@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();
try {
int result = DownloadFile(new URL(“http://www.amazon.com/somefile.pdf”));
Toast.makeText(getBaseContext(),
“Downloaded “ + result + “ bytes”,
Toast.LENGTH_LONG).show();
} catch (MalformedURLException e) {
e.printStackTrace();
}
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
Toast.makeText(this, “Service Destroyed”, Toast.LENGTH_LONG).show();
}
private int DownloadFile(URL url) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 100;
}
}
Chạy ứng dụng và click vào button Start Service
Kết quả:
Sau khi click vào nút "Start Service" đợi khoảng vài giây một Toast với nội dung "Downloaded 100 bytes" sẽ hiện lên.
Trong ví dụ trên, Service của tôi đã gọi đến phương thức DownloadFile()
để download một file từ một url. Phương thức này sẽ trả ra tổng số bytes
đã được download (trường hợ này là HARDCODE sẵn 100%). Để mô phỏng thời gian download file tôi để delays 5 giây bằng cách sử dụng Thread.Sleep()
method.
Khi Servive được start. Chú ý rằng activity của bạn được suspend
trong khoảng 5 giây, đó cũng là thời gian thực cho việc download file trên internet. Trong thời gian này activity cũng không hoạt động. Điều này chứng tỏ: Service đã chạy cùng thread
với activity.
Vì vậy, Đối Với một service chạy dài hạn điều quan trọng là bạn nên đặt code của mình trong một Thread riêng biệt để nó không bị phụ thuộc vào ứng dụng gọi nó.
Bây giờ hãy tiến hành tiếp ví dụ trên nhưng trong một Service Asynchronously
(Service không đồng bộ).
- MyService.java
package net.learn2develop.Services;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;
import java.net.MalformedURLException;
import java.net.URL;
import android.os.AsyncTask;
public class MyService extends Service {
@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();
try {
new DoBackgroundTask().execute(
new URL(“http://www.amazon.com/somefiles.pdf”),
new URL(“http://www.wrox.com/somefiles.pdf”),
new URL(“http://www.google.com/somefiles.pdf”),
new URL(“http://www.learn2develop.net/somefiles.pdf”));
} catch (MalformedURLException e) {
e.printStackTrace();
}
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
Toast.makeText(this, “Service Destroyed”, Toast.LENGTH_LONG).show();
}
private int DownloadFile(URL url) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 100;
}
private class DoBackgroundTask extends AsyncTask<URL, Integer, Long> {
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalBytesDownloaded = 0;
for (int i = 0; i < count; i++) {
totalBytesDownloaded += DownloadFile(urls[i]);
publishProgress((int) (((i+1) / (float) count) * 100));
}
return totalBytesDownloaded;
}
protected void onProgressUpdate(Integer... progress) {
Log.d(“Downloading files”,
String.valueOf(progress[0]) + “% downloaded”);
Toast.makeText(getBaseContext(),
String.valueOf(progress[0]) + “% downloaded”,
Toast.LENGTH_LONG).show();
}
protected void onPostExecute(Long result) {
Toast.makeText(getBaseContext(),
“Downloaded “ + result + “ bytes”,
Toast.LENGTH_LONG).show();
stopSelf();
}
}
}
Chạy ứng dụng và nhấn nút "Start Service" Toast thông báo sẽ hiện lên đồng thời LogCat của bạn sẽ hiển thì:
01-16 02:56:29.021: DEBUG/Downloading files(8844): 25% downloaded
01-16 03:06:29.033: DEBUG/Downloading files(8844): 50% downloaded
01-16 03:16:29.042: DEBUG/Downloading files(8844): 75% downloaded
01-16 03:22:29.061: DEBUG/Downloading files(8844): 100% downloaded
Giải thích ví dụ
Trên đây là một ví dụ minh họa cho việc bạn có thể thực hiện 1 task không đồng bộ
trong Service của bạn.
Bạn có thể làm điều đó bằng cách tạo ra một class entends
từ class AsyncTask
. Class AsyncTask sẽ cho phép chúng ta thực hiện các tác vụ ngầm mà không cần phải tự sử lý các Threads.
oại
Class DoBackgroundTask
extends từ lớp AsyncTask
bằng cách xác định 3 loại chung:
private class DoBackgroundTask extends AsyncTask<URL, Integer, Long> {}
Trong trường hợp này, Ba loại được xác định là URL
, Integer
, Long
. Ba loại được xác định này được sử dụng theo ba phương thức mà bạn implement trong một AsyncTask clas:
-
doInBackground()
Phương thức này sẽ lấy một mảng đó là đối số đầu tiên của phương thức trên. Trong trường hợp này là một mảng các URL. Phương thức này sẽ thực thi các Thread chạy ngầm và là nơi bạn đăt các đoạn code chạy dài hạn của mình. Để biết được tiến độ các tác vụ bạn sử dụng phương thứcpublishProgress()
. Để thực hiện tác vị tiếp theo thì bạn gọi phương thứconProgressUpdate()
. Loại dữ liệu mà phương thức này trả về là kiểu dữ liệu đã được định nghĩa trước đó. Trong trường hợp này là kiển Long. -
onProgressUpdate()
Phương thức này được gọi trong các luồng giao diện khi mà phương thứcpublishProgress()
được gọi. Nó sẽ lấy mảng số nguyên thứ 2 trong đối số đầu vào để thực hiện, Trong trường hợp này làInteger
. Phương thức này sẽ trả ra tiến trình làm việc của các tác vụ để người dùng biết. -
onPostExecute()
Phương thức này được được gọi khi phương thứcdoInBackground()
được hoàn thành. Phương thức này sẽ trả ra kiểu dữ liệu được định nghĩa ở đối số thứ 3 của phương thức này. trong trường hợp này làLong.
Để download được nhiều file và chạy ngầm. Thì chúng ta sẽ tạo ra mộtinstance
của classDoBackgroundTask
và gọi phương thứcexecute()
:
try {
new DoBackgroundTask().execute(
new URL(“http://www.amazon.com/somefiles.pdf”),
new URL(“http://www.wrox.com/somefiles.pdf”),
new URL(“http://www.google.com/somefiles.pdf”),
new URL(“http://www.learn2develop.net/somefiles.pdf”));
} catch (MalformedURLException e) {
e.printStackTrace();
}
Việc download file là của các tác vụ chạy ngầm. và nó sẽ báo cáo tiến độ thành dạng phần trăm của tỉ lệ hoàn thành thông qua các Toast
thông báo. Quan trọng là các activity của bạn vẫn hoạt động bình thường trên một thread riêng biệt.
chú ý rằng, do các tác vụ chạy riêng biệt trên các Thread khác nhau nên sau khi nó hoàn tất thì bạn phải stop nó lại bằng lệnh selfStop()
:
protected void onPostExecute(Long result) {
Toast.makeText(getBaseContext(),
“Downloaded “ + result + “ bytes”,
Toast.LENGTH_LONG).show();
stopSelf();
}
Phương thức selfStop()
được gọi thì tương đương với việc gọi phương thức stopService ()
lúc đó Sevice của bạn sẽ dừng lại.
Như vậy bài viết lần này tôi đã chỉ ra thế nào là một Service trong Android
, làm sao để tạo một service đơn giả và chạy một service trên một thread riêng biệt với activity để nó không phụ thuộc vào ứng dụng mà bạn sử dụng.
Phần tiếp theo tôi sẽ trình bày về:
- 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
Mong các bạn đón đọc.
Xin Cảm ơn!
All rights reserved