Vấn đề chính khi xây dựng ứng dụng từđiển trên Series 60 là vấn dề chọn cấu trúc dữ liệu, thuật toán xử lý, giao diện ứng dụng thích hợp với thiết bị, hệ điều hành. Tiêu chuẩn chọn phải đảm bảo ứng dụng đáp ứng được những yêu cầu cơ bản đối với một ứng dụng từđiển mà vẫn thỏa mãn những hạn chế phần cứng của điện thoại di động Serries 60. Ta sẽ lần lượt làm rõ: những hạn chế về phần cứng của điện thoại di động Series 60 và các yêu cầu cơ bản đối với một ứng dụng từ điển. Điều này sẽ giúp chúng ta đưa ra các giải pháp phù hợp.
3.3.1 Một số hạn chế phần cứng của điện thoại di động Series 60
3.3.1.1 Hạn chế về bộ vi xử lý
Giống như các thiết bị máy tính khác, thiết bị điện thoại di động Series 60 cũng có một bộ vi xử lý. Các điện thoại di động Series 60 hiện tại sử dụng các bộ vi xử lý ARM-9. Khác với bộ vi xử lý của máy tính để bàn, bộ vi xử lý ARM-9 được thiết kế với kiến trúc 32 bit, chạy ở tốc độ thấp hơn so với máy tính để bàn. Các bộ vi xử lý ARM-9 có tốc độ khác nhau tùy vào mục đích thiết kế và mục đích thương mại. Tuy nhiên tốc độ trung bình của các bộ vi xử lý ARM-9 hiện nay là lớn hơn 100 MHz.
3.3.1.2 Hạn chế về bộ nhớ và khả năng lưu trữ
Các thiết bị di động Series 60 không có đĩa cứng. Các thiết bị này lưu trữ những chương trình thông dụng mà nhà sản xuất cung cấp (như sổ địa chỉ, lịch, hệ điều hành,…) trong chíp nhớ ROM, các chương trình này sẽ không mất đi khi ta tắt máy. Bộ nhớ ROM rất giới hạn thường là lớn hơn 8 MB. Còn bộ nhớ RAM của máy được sử dụng bởi các ứng dụng đang thực thi và nhân của hệ thống. Một phần RAM được gán là ổđĩa C dùng để chứa các chương trình, các tập tin tài liệu ứng dụng, … Dung lượng RAM rất hạn chế thường là lớn hơn 8 MB và ổ C thường chiếm 50% dung lượng RAM. Một dạng lưu trữ khác nữa trên điện thoại di động là dùng thẻ nhớ để lưu trữ dữ liệu tuy nhiên dung lượng thẻ nhớ này cũng rất hạn chế.
3.3.1.3 Hạn chế về kích thước màn hình
Các màn hình điện thoại di động của Series 60 được thiết kế với độ phân giải 176*208 pixel, từ 12 đến 18 bit màu khác nhau tùy mục đích thương mại. Với kích thước như vậy rõ ràng là rất hạn chế so với một ứng dụng bất kỳ. Ta có thể khắc phục bằng cách sử dụng tối đa các popupmenu và các phím tắt theo chuẩn của điện thoại di động.
3.3.2 Các yêu cầu cơ bản của một ứng dụng từđiển
Một ứng dụng từđiển trước hết phải đáp ứng được những yêu cầu về dữ liệu và yêu cầu về xử lý. Thứ nhất là dữ liệu lớn dể có thể lưu trữ được nhiều từ loại của
các từ điển khác nhau. Kế đến là xử lý (bao gồm tra cứu và hiển thị kết quả) phải nhanh cho dù nguồn dữ liệu có kích thước lớn vì thao tác hết sức thường xuyên trên ứng dụng từđiển là tra cứu. Ngoài ra yêu cầu dễ sử dụng cũng là một yêu cầu quan trọng.
Thế nhưng với hạn chế về khả năng lưu trữ của điện thoại di động sẽ kéo theo hạn chế kích thước dữ liệu và với hạn chế về tốc độ xử lý của địên thoại di động sẽ kéo theo hạn chế về tốc độ xử lý của ứng dụng.
3.3.3 Kết luận
Như vậy qua việc trình bày những hạn chế của điện thoại di động Series 60 và các yêu cầu cơ bản của một ứng dụng từđiển, ta thấy vấn đề có các mâu thuẫn như sau:
Mâu thuẫn giữa khả năng lưu trữ của điện thoại di động và yêu cầu về dữ liệu của từđiển.
Mâu thuẫn giữa tốc độ xử lý của điện thoại di động và tốc độ xử lý của ứng dụng.
Chương 4 Kĩ thuật lập trình C++ trên Symbian
Do các đặc trưng, hạn chế của điện thoại di động Việc lập trình ứng dụng bằng ngôn ngữ C++ cho điện thoại thông minh Symbian Series 60 có các điểm khác biệt so với lập trình ứng dụng cho máy tính. Chương này trình bày những kĩ thuật lập trình Symbian C++ căn bản mà bất cứ một lâp trình viên nào cũng phải sử dụng.
Nói đến lập trình C++ trên Symbian, vấn đề đầu tiên cần đề cập là các qui ước đặt tên cho lớp, biến, hàm… cũng như các kiểu dữ liệu cơ bản khi phát triển ứng dụng. Tuy nhiên nội dung này đã được nhiều bài viết, luận văn khóa trước trình bày. Vì vậy chúng em không trình bày lại phần này trong nội dung khóa luận mà đưa vào phụ lục để phục vụ như cầu tham khảo.
4.1 Quản lý lỗi
4.1.1 Lỗi lập trình
Lỗi lập trình là lỗi phát sinh trong quá trình phát triển ứng dụng do truyền sai tham số, sai đường dẫn… Loại lỗi này thường gặp trong quá trình lập trình và có thểđược lập trình viên khắc phục hoặc tránh bằng mã nguồn.
Phần lớn các hàm API mà Symbian cung cấp đều trả về kết quả thực thi của hàm đó dưới dạng một mã lỗi hệ thống (System wide error codes). Dựa vào các mã này, lập trình viên có thể biết hàm được thực hiện thành công hay không và nguyên nhân gây lỗi. Cụ thế các mã lỗi thông dụng: KErrNone (không có lỗi, hàm thưc hiện thành công), KErrCancel (giá trị trả về khi hàm bị hủy khi đang thực hiện),
KErrNotSupported (nền hệ thống không hỗ trợ hàm được gọi), KErrBadName (tên tập tin không đúng cú pháp…).
Ngoài ra, hệ điều hành Symbian còn cung cấp một cơ chế dừng tiến trình ứng dụng ngay khi xảy ra lỗi và trả về các giá trị cần thiết để tìm và sửa lỗi gọi là panic. Khi một panic xảy ra, có hai thành phần xác định nguyên nhân là loại lỗi (catagories) và mã lỗi của loại đó (reason codes). Ví dụ liên quan đến việc sử dụng font chữ và ảnh bitmap, có loại lỗi FBSERV (font & bitmap server) với 15 mã lỗi
khác nhau như 1: không kết nối được với dịch vụ quản lý font, 6: thông tin sử dụng font sai. Trong quá trình thực thi ứng dụng trên máy ảo cũng như máy thực, khi xảy ra panic sẽ có một thông báo App Close! cung cấp thông tin về các thành phần trên trước khi đóng ứng dụng.
Ngoài ra, lập trình viên có thể qui định thêm các panic cho ứng dụng bằng cách sử dụng tiện ích User::Panic(aCategory, aReason)để ngừng chương trình và thông báo lỗi.
4.1.2 Lỗi thiếu tài nguyên
Lỗi thiếu tài nguyên là lỗi phát sinh trong quá trình thực thi ứng dụng khi ứng dụng đòi hỏi hệ thống cung cấp một tài nguyên nào đó mà hệ thống không đáp ứng được. Một ứng dụng khi thực thi lần đầu hoạt động tốt, nhưng lần thứ hai có thể gặp lỗi thiếu tài nguyên. Loại lỗi này thường chỉ gặp khi thực thi ứng dụng trên thiết bị thực, vì máy ảo chia sẻ tài nguyên với máy tính nên rất ít khi thiếu.
Trong môi trường điện thoại thông minh Symbian Series 60, các nguồn tài nguyên như bộ nhớ, không gian lưu trữ… rất hạn chế. Trong khi các ứng dụng được viết cho điện thoại di động phải đảm bảo chạy trong thời gian dài mà không khởi động hay nạp lại bộ nhớ.
Vì vậy vấn đề quản lý tài nguyên khi lập trình, nhất là quản lý, thu hồi bộ nhớ khi lỗi xảy ra được Symbian cung cấp hỗ trợ rất kĩ, rất tốt; đây chính là một trong những điểm làm nên thành công của hệđiều hành Symbian.
Cơ chế quản lý lỗi trong Symbian được chia làm nhiều cấp khác nhau như sau:
4.1.2.1 Bẫy lỗi, ngừng 1 hàm nếu xảy ra lỗi bộ nhớ
Cơ chế bắt lỗi cơ bản mà Symbian hỗ trợ gồm:
Hàm User::Leave() có tác dụng ngừng hàm đang thực hiện và trả về mã lỗi.
Macro TRAP và biến thể của nó TRAPD, cho phép đoạn mã chương trình hoạt động dưới dạng bẫy lỗi
Tất cả các hàm có thể khả năng phát sinh lỗi tài nguyên trong ứng dụng (như cấp phát vùng nhớ cho một đối tượng nhưng đã hết bộ nhớ, truyền dữ liệu trong khi dịch vụ chưa sẵn sàng…) gọi là “Leave function”, và có ký tự L cuối tên hàm (hàm L). Lưu ý một hàm có thể ngừng (leave) 1 cách trực tiếp do đoạn mã phát sinh lỗi (leave directly) hoặc do 1 hàm này gọi 1 hàm L khác và hàm L được gọi này có thể gây ra lỗi (leave indirectly).
Ta có thể sử dụng tiện ích User::Leave() để ngừng ngay hàm đang thực hiện và trả về mã lỗi tương ứng (một dạng biệt lệ (exception) trong Symbian). Ví dụ dưới đây cho thấy hàm sẽ ngừng và trả về mã lỗi thiếu bộ nhớ nếu việc cấp phát thông qua toán tửnew không thành công:
void doExampleL() {
CExample* myExample = new CExample;
if (!myExample) User::Leave(KErrNoMemory);
// leave used in place of return to indicate an error // if leave, below code isn’t executed
// do something myExample->iInt = 5;
testConsole.Printf(_LIT("Value of iInt is %d.\n"), myExample->iInt); // delete
delete myExample; }
Tất cả những hàm leave, gồm cả những hàm gọi hàm leave khác đều hoạt động dưới cơ chế bẫy lỗi. Nếu hàm User::Leave() được gọi thì dòng điều khiển chương trình được trả về cho macro TRAP gần nhất. Một biến được dùng đề nhận giá trị lỗi trả về của User::Leave() và nếu không có lỗi gì xảy ra thì biến này sẽ có giá trị KErrNone. (Lưu ý biến trả về của macro TRAP không phải là giá trị hàm trả về).
Xét ví dụ cụ thể sau:
TInt E32Main() {
TInt r; // The leave variable
// Perform example function. If it leaves, // the leave code is put in r
TRAP(r,doExampleL());
if (r) // Test the leave variable
testConsole.Printf(_LIT("Failed: leave code=%d"), r); }
Macro TRAP có 1 biến thể khác cho phép rút gọn code chương trình đó là TRAPD, khi sử dụng TRAPD ta không cần khai báo biến lỗi một cách tường minh:
TRAPD(leaveCode,value=GetSomethingL()); // get a value
Ngoài ra Symbian còn cung cấp 1 dạng toán tửnew mới, sử dụng cơ chế leave trong 1 dòng lệnh, đó là: new (ELeave)… Khi sử dụng new (ELeave)… nghĩa là nếu việc cấp phát vùng nhớ cho 1 đối tượng không thành công thì hàm thực thi toán tử
new này sẽ ngừng ngay lập tức:
void doExampleL() {
// attempt to allocate, leave if could not CExample* myExample = new (ELeave) CExample; // new (ELeave) replaces new followed by check // do something
myExample->iInt = 5; // delete
delete myExample; }
Trong ví dụ trên, nếu việc cấp phát myExample không thành công hàm
doExampleL() sẽ ngừng ngay và trả về giá trị lỗi cho macro TRAP hay TRAPD gọi hàm này (nếu có).
4.1.2.2 Cleanup stack
Khi một hàm leave, dòng diều khiển được chuyển cho macro TRAP và dòng lệnh dưới TRAP được thực thi. Điều này nghĩa là những đối tượng được tạo ra hoặc truyền vào trước khi hàm ngừng sẽ trở nên “mồ côi’, việc hủy chúng không được thực hiện và tài nguyên (bộ nhớ) mà chúng chiếm giữ sẽ không được giải phóng.
Hệđiều hành Symbian cung cấp một cơ chế quản lý những đối tượng này gọi là Cleanup stack. Nhưđã trình bày ở phần qui ước đặt tên, chỉ có các lớp bắt đầu bằng ký tự C là cần và bắt buộc phải hủy khi sử dụng xong.
Như vậy nguy cơđối tượng mồ côi xuất phát từ lớp C, xét ví dụ cụ thể sau:
void doExampleL() {
// An T-type object: can be declared on the stack TBuf<10> buf;
// A C-type object: must be allocated on the heap // Allocate and leave if can not
CExample* myExample = new (ELeave) CExample;
// do something that cannot leave: no protection needed myExample->iInt = 5;
// PROBLEM: do something that can leave !!! myExample->DoSomethingL();
// delete
delete myExample; }
Nếu lúc này hàm myExample->DoSomethingL(); bị lỗi và ngừng lại thì dòng lệnh delete myExamplesẽ không được thực thi, nghĩa là vùng nhớ cấp cho nó không được giải phóng. Ta giải quyết vần đề này bằng kĩ thuật Cleanup stack mà Symbian hỗ trợ như sau: dùng hàm CleanupStack::PushL()để đưa con trỏ đối tượng vào Cleanup stack trước khi gọi bất kì hàm leave nào; sau đó nếu những hàm leave đã hoàn thành mà không có lỗi xảy ra, ta gọi hàm CleanupStack::Pop() để lấy con trỏđối tượng ra khỏi Cleanup stack.
Minh họa lại ví dụ trên:
void doExampleL() {
// allocate with checking
CExample* myExample = new (ELeave) CExample; // do something that cannot leave
myExample->iInt = 5; // cannot leave: no protection needed // do something that can leave: use cleanup stack
CleanupStack::PushL(myExample); // pointer on cleanup stack myExample->DoSomethingL(); // something that might leave CleanupStack::Pop(); // it didn't leave: pop the pointer // delete
delete myExample; }
Lưu ý: hàm CleanupStack::PushL() có thể leave, tuy nhiên nếu hàm này leave thì đối tượng đưa vào stack bởi hàm này cũng sẽ bị hủy.
Cần lưu ý hàm CleanupStack::Pop() tự nó không có ý nghĩa gì, nó chỉđẩy 1 đối tượng ra khỏi Cleanupstack và ta có thể hủy hoặc sử dụng đối tượng nhận được từ stack này sau khi hàm bị ngừng (leave) đảm bảo không có đối tượng mồ côi
Trong nhiều trường hợp ta có thể gọi hàm CleanupStack::PopAndDestroy() để hủy đối tượng sau khi hoàn thành hàm leave:
void doExampleL() {
. . .
// pop from cleanup stack, and destroy, in one operation CleanupStack::PopAndDestroy(); //don’t need call delete }
Để rút gọn mã nguồn chương trình, việc cấp phát và đưa đối tượng vào Cleanp stack thường được thực hiện trong 1 hàm kết thúc bằng ký tự C. Ví dụ hàm TAny* User::AllocLC() gồm việc gọi hàm User::Alloc() để cấp phát vùng nhớ sau đó leave nếu cấp phát không thành công, ngược lại tự động đưa đối tượng vào Cleanup stack. Nếu gọi hàm ___C để cấp phát đối tượng vẫn phải lấy đối tượng ra khỏi Cleanup stack sau khi sử dụng xong.
4.1.2.3 Hàm dựng 2 pha
Trong khi khởi tạo các đối tượng phức tạp (đối tượng lớp C chứa một hoặc nhiều đối tượng lớp C khác cần được khởi tạo) có thể dẫn tới tình trạng leave mà việc quản lý bằng Cleanup stack một cách tường minh gặp khó khăn do ta không biết đối tượng nào đã được khởi tạo và đối tượng nào chưa. Lúc này Symbian tiếp tục cung cấp cơ chế quản lý lỗi khi khởi tạo đối tượng gọi là hàm dựng 2 pha (2 phase constructor) hoạt động với cơ chế như sau:
Cấp phát vùng nhớ cho đối tượng (và leave nếu không đủ bộ nhớ) Khởi tạo các thành phần an toàn (không thể leave)
Đưa con trỏđối tượng vào Cleanup stack
Dùng hàm dựng thứ 2 (2nd phase constructor) để khởi tạo các thành phần có thể leave.
Lưu ý:
Toàn bộ quá trình trên được thực hiện thông qua 2 hàm tĩnh: NewL(), and
NewLC()(tựđưa đối tượng được cấp phát vào Cleanup stack) Hàm dựng thứ 2 có tên là ConstructL().
Ví dụ ta có lớp CSimple là một lớp đơn giản không chứa các đối tượng khác:
class CSimple : public CBase { public: CSimple(TInt); //hàm dựng void Display(); private: TInt iVal; };
Lớp CCompound chứa đối tượng CSimple như là biến thành viên
class CCompound : public CBase {
public:
void Display(); ~CCompound();
static CCompound* NewL(TInt aVal); static CCompound* NewLC(TInt aVal);
protected: CCompound(TInt aVal); void ConstructL(); private: TInt iVal; CSimple* iChild; };
Minh họa nguy cơ từ việc khởi tạo đối tượng CCompound theo cách thông thường:
CCompound::CCompound(TInt aVal) {
iVal=aVal;
iChild = new (ELeave) CSimple(aVal); }
Lúc này, khi khởi tạo 1 đối tượng CCompound , nếu vì lý do nào đó mà việc cấp phát đối tương iChild (CSimple) không thành công. Hàm khởi tạo CCompound(TInt aVal) bị ngừng (leave). Lúc này đối tượng CCompound đã được cấp phát, tuy nhiên không thể truy cập đến vùng nhớ này vì hàm dựng không thành công. Như vậy đã có một đối tượng mồ côi được tạo ra trong vùng nhớ mà ta không thể quản lý được.
Để khắc phục CCompound sử dụng hàm dựng 2 pha, có thể tạo ra một đối tượng CCompound một cách an toàn thông qua hai hàm tĩnh NewL() và NewLC() như sau:
// NewLC with two stage construction CCompound* CCompound::NewLC(TInt aVal) {
// get new, leave if can't
CCompound* self=new (ELeave) CCompound(aVal);
// push onto cleanup stack in case self->ConstructL leaves