1. Trang chủ
  2. » Tất cả

Giáo trình lập trình di động 2 dành cho bậc cao đẳng ngành công nghệ thông tin

102 5 0
Tài liệu đã được kiểm tra trùng lặp

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Tiêu đề Giáo trình Lập trình di động 2 dành cho bậc cao đẳng ngành công nghệ thông tin
Tác giả Tiêu Kim Cương
Trường học Trường Cao Đẳng Công Nghệ Thủ Đức
Chuyên ngành Công Nghệ Thông Tin
Thể loại Giáo trình
Năm xuất bản 2021
Thành phố Hồ Chí Minh
Định dạng
Số trang 102
Dung lượng 3,95 MB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Đặc biệt, trong suốt quá trình học tập và thực hiện dự án sản xuất của mình, sinh viên cũng rèn luyện các kỹ năng nghề nghiệp tích hợp từ việc hình thành ý tưởng, yêu cầu của dự án đến p

Trang 1

ỦY BAN NHÂN DÂN TP HỒ CHÍ MINH

TRƯỜNG CAO ĐẲNG CÔNG NGHỆ THỦ ĐỨC

KHOA CÔNG NGHỆ THÔNG TIN

GIÁO TRÌNH HỌC PHẦN: LẬP TRÌNH DI ĐỘNG II NGÀNH/NGHỀ: CÔNG NGHỆ THÔNG TIN

Trang 2

LỜI GIỚI THIỆU

Giáo trình Lập trình di động 2 được dùng cho học phần cùng tên Lập trình di động

2, ngành Công nghệ Thông tin, trường Cao đẳng Công nghệ Thủ Đức Đây là học phần

tự chọn theo hướng Phát triển ứng dụng trên các thiết bị di động với thời lượng 60 tiết Tuy nhiên, nội dung học phần lại chứa đựng nội dung tương đối nhiều, phù hợp với yêu cầu của doanh nghiệp Cho nên, giáo trình sẽ viết theo hướng tích hợp và tái sử dụng những ứng dụng đã có trước đây, cải tiến, hoàn thiện thêm, do vậy sẽ tiết kiệm được rất nhiều thời gian học tập trên lớp của sinh viên

Ngoài ra, trong quá trình học, giáo trình có mở rộng thêm nhiều chức năng mới, nhằm giúp sinh viên có khả năng tự học, tự nghiên cứu thông qua các bài tập làm thêm, đáp ứng chuẩn đầu ra CDIO và nhu cầu thực tế tại doanh nghiệp

Thành phố Hồ Chí Minh, ngày 07 tháng 11 năm 2021

Giảng viên biên soạn

Tiêu Kim Cương

Trang 3

MỤC LỤC

TRANG

DANH MỤC CÁC TỪ VIẾT TẮT 5

DANH MỤC BẢNG BIỂU SỐ LIỆU VÀ HÌNH VẼ 6

CHƯƠNG 1 MỘT SỐ KỸ NĂNG THIẾT KẾ GIAO DIỆN NÂNG CAO 9

1.1 Theme và Style 9

& Bài tập thực hành số 1 9

& Bài tập thực hành số 2 12

1.2 Widget tạo Widget mới 12

& Bài tập thực hành số 3 13

& Bài tập thực hành số 4 13

& Bài tập thực hành số 5 14

& Bài tập thực hành số 6 15

& Bài tập thực hành số 7 15

1.3 Multi-Screen and Orientation 16

& Bài tập thực hành số 8 17

& Bài tập thực hành số 9 20

1.4 RecyclerView and Card View 20

& Bài tập thực hành số 10 21

& Bài tập thực hành số 11 22

& Bài tập thực hành số 12 25

1.5 Tuỳ biến giao diện hệ thống 25

& Bài tập thực hành số 13 26

& Bài tập thực hành số 14 26

1.6 Câu hỏi và bài tập làm thêm chương 1 26

CHƯƠNG 2 LÀM VIỆC VỚI TÀI NGUYÊN TRÊN MẠNG INTERNET 28

2.1 Kết nối vào mạng Internet 28

& Bài tập thực hành số 15 29

2.2 Thực hiện các thao tác trên mạng với tiến trình chạy ngầm sử dụng Asynchronous Tasks và trích xuất dữ liệu từ Webservice 30

& Bài tập thực hành số 16 30

& Bài tập thực hành số 17 30

& Bài tập thực hành số 18 35

2.3 Sử dụng Download Manager 35

& Bài tập thực hành số 19 37

2.4 Câu hỏi và bài tập chương 2 37

CHƯƠNG 3 SỬ DỤNG CONTENT PROVIDER, BROADCAST RECEIVER, SERVICE VÀ FIREBASE 39

3.1 Sử dụng Intent để gửi các sự kiện Broadcast và thực hiện chúng 39

& Bài tập thực hành số 20 41

& Bài tập thực hành số 21 43

3.2 Xây dựng và sử dụng các Receiver 46

& Bài tập thực hành số 22 48

3.3 Tạo các Content Provider 49

3.4 Sử dụng các Content Provider 54

3.4.1 Sử dụng Content Provider tự xây dựng 54

Trang 4

& Bài tập thực hành số 23 54

3.4.2 Sử dụng Content Provider có sẵn trong Android 55

& Bài tập thực hành số 24 56

& Bài tập thực hành số 25 56

& Bài tập thực hành số 26 58

& Bài tập thực hành số 27 59

3.5 Chia sẻ file dùng File Provider 60

& Bài tập thực hành số 28 62

3.6 Service và các ứng dụng chạy ngầm 62

& Bài tập thực hành số 29 63

& Bài tập thực hành số 30 67

3.7 Lập lịch cho ứng dụng chạy ngầm 69

& Bài tập thực hành số 31 70

3.8 Notifications 75

& Bài tập thực hành số 32 77

3.9 Kết nối Firebase với ứng dụng Android 77

& Bài tập thực hành số 33 80

& Bài tập thực hành số 34 82

& Bài tập thực hành số 35 85

3.10 Câu hỏi và bài tập chương 3 85

CHƯƠNG 4 ĐIỀU KHIỂN MÀN HÌNH TƯƠNG TÁC VÀ CẢM BIẾN 87

4.1 Đặt vấn đề 87

4.2 Xử lý một số dạng cơ bản của màn hình tương tác 87

& Bài tập thực hành số 36 88

& Bài tập thực hành số 37 89

4.3 Liệt kê các loại cảm biến trên điện thoại 91

& Bài tập thực hành số 38 91

4.4 Sử dụng Sensor Framework đọc dữ liệu từ cảm biến và ra quyết định 91

& Bài tập thực hành số 39 93

4.5 Câu hỏi và bài tập chương 4 93

CHƯƠNG 5 BẢN ĐỒ TRỰC TUYẾN MAP VÀ LOCATION 95

5.1 Đặt vấn đề 95

5.2 Kỹ thuật lấy vị trí hiện tại 95

& Bài tập thực hành số 40 96

5.3 Viết ứng dụng trên bản đồ trực tuyến Map 96

& Bài tập thực hành số 41 98

5.4 Câu hỏi và bài tập chương 5 100

TÀI LIỆU THAM KHẢO 101

PHỤ LỤC 102

Trang 5

4 MIME Multipurpose Internet Mail Extensions

5 FCM Firebase Cloud Messaging

Trang 6

DANH MỤC BẢNG BIỂU SỐ LIỆU VÀ HÌNH VẼ

Hình 1.1.1 Bài tập thực hành số 1 10

Hình 1.2.1 Widget Player 13

Hình 1.3.1 Giao diện ứng dụng hỗ trợ Multi-Orientation 16

Hình 1.3.2 Tạo biến thể cho giao diện ứng dụng theo chiều ngang 17

Hình 1.3.3 Giao diện ứng dụng khi xoay theo chiều ngang 17

Hình 1.3.4 Thiết kế giao diện hỗ trợ nhiều loại màn hình 18

Hình 1.3.5 Phân loại màn hình theo mật độ điểm ảnh 18

Hình 1.3.6 Lựa chọn hỗ trợ nhiều màn hình theo mật độ điểm ảnh 19

Hình 1.3.7 Lựa chọn một chế độ màn hình để tạo layout 19

Hình 1.6.1 Kết quả tuỳ biến thanh ActionBar 27

Hình 2.2.1 Màn hình giao diện ứng dụng WeatherForecast 30

Hình 3.1.1 Android tìm và đưa ra danh sách ứng dụng 40

Hình 3.1.2 Màn hình của PersonPickerActivity 42

Hình 3.1.3 Giao diện ứng dụng broadcast intent 43

Hình 3.1.4 Activity trong ứng dụng Contact được gọi 44

Hình 3.1.5 Activity được gọi khi tap “Pick a Person” 45

Hình 3.1.6 Kết quả trả về từ “Pick an Contact” và “Pick a Person” 46

Hình 3.3.1 Cấu hình Content Provider 49

Hình 3.4.2.1 Mối quan hệ các bảng dữ liệu trong Danh bạ điện thoại 58

Hình 3.6.1 Màn hình giao diện ứng dụng MusicPlayer 64

Hình 3.6.2 Tạo một Service mới cho ứng dụng MusicPlayer 64

Hình 3.7.1 Màn hình giao diện ứng dụng xử lý ảnh 70

Hình 3.7.2 Vòng đời của view model trong Android 72

Hình 3.9.1 Mở công cụ Firebase Assistant trong Android Studio 78

Hình 3.9.2 Thông báo kết nối Firebase thành công 78

Hình 3.9.3 Ví dụ lưu đối tượng sinh viên trên firebase 79

Hình 3.9.4 Lưu bảng sinh viên trên Firebase 80

Hình 3.9.5 Đăng ký tài khoản Firebase cho ứng dụng 84

Hình 4.3.1 Ứng dụng Bài thực hành 38 91

Hình 4.5.1 Ứng dụng Compass, bài tập 6 93

Hình 5.3.1 Tạo mã API Key cho bản đồ trực tuyến 97

Hình 5.3.2 Hiển thị bản đồ trực tuyến 98

Hình 5.3.3 Ứng dụng hoàn thiện Bài tập TH 41 99

Trang 7

GIÁO TRÌNH HỌC PHẦN Tên học phần: LẬP TRÌNH DI ĐỘNG 2

Mã học phần: CNC107315

Vị trí, tính chất, ý nghĩa và vai trò của học phần:

- Vị trí: Là học phần chuyên ngành tự chọn, ngành Công nghệ Thông tin Học phần cần

được học sau Lập trình di động 1 và Cơ sở dữ liệu

- Tính chất: Học phần nhằm giúp sinh viên có khả năng hoàn thiện, tìm hiểu sâu hơn

các kiến thức và kỹ năng đã học về lập trình di động; đồng thời hiểu và vận dụng thêm một số chuyên đề nâng cao vào việc phát triển ứng dụng vừa và nhỏ trên điện thoại di động Android

- Ý nghĩa và vai trò của môn học/mô đun: Thông qua các hoạt động học tập được tổ

chức, sinh viên rèn luyện nâng cao khả năng tự học, kỹ năng làm việc trong nhóm phát triển phù hợp với yêu cầu của doanh nghiệp, kỹ năng thuyết trình một vấn đề, kỹ năng viết báo cáo theo mẫu và tính chủ động tích cực trong mọi tình huống Đặc biệt, trong suốt quá trình học tập và thực hiện dự án sản xuất của mình, sinh viên cũng rèn luyện các kỹ năng nghề nghiệp tích hợp từ việc hình thành ý tưởng, yêu cầu của dự án đến phân tích, thiết kế, cài đặt, kiểm thử và vận hành phần mềm làm được; qua đó thấm dần các quy tắc, quy định, hành vi mà một lập trình viên chuyên nghiệp cần có để khi ra trường các em hoàn toàn hoà nhập được vào các nhóm phát triển tại doanh nghiệp

+) Viết được các ứng dụng sử dụng các tài nguyên trên mạng Internet;

+) Vận dụng được Content Provider, Broadcast Receiver, Service và Firebase trong các ứng dụng Android;

Trang 8

+) Vận dụng được cách điều khiển màn hình tương tác (Touchscreen) và các cảm biến;

+) Viết được các ứng dụng android dựa trên bản đồ trực tuyến của Google

Trang 9

CHƯƠNG 1 MỘT SỐ KỸ NĂNG THIẾT KẾ GIAO DIỆN NÂNG CAO

Mô tả nội dung: Trình bày chuyên sâu về cách tuỳ biến giao diện hệ thống và giao diện

ứng dụng dựa trên Themes và Styles

1.1 Theme và Style

Style có thể hiểu là tập hợp các thuộc tính được thiết lập sẵn và có thể tái sử dụng cho

nhiều loại giao diện màn hình khác nhau Do vậy, trong trường hợp có nhiều thành phần giao diện có những thuộc tính được thiết lập giống nhau thì nên gom chúng vào trong các Styles để có thể tái sử dụng mà không cần thiết lập lại

Còn Theme là tập hợp nhiều Styles khác nhau tạo thành cấu trúc chung cho toàn

bộ ứng dụng Mỗi ứng dụng, mỗi Activity khi được tạo ra bao giờ cũng được gắn với một Theme nhất định và mọi thuộc tính đã được định nghĩa cho từng Style trong Theme

sẽ hiệu ứng cho mọi phần tử tương ứng trong ứng dụng đó Tuy nhiên, chúng ta vẫn hoàn toàn có thể điều chỉnh một số thuộc tính theo ý mình bằng cách định nghĩa lại những thuộc tính tương ứng đó

Theme và Styles trong một ứng dụng Android thường được định nghĩa trong thư

mục chứa tài nguyên hệ thống (res): res/values/styles.xml Styles thường được sử dụng

trong các định nghĩa của layout và Theme thì được dùng trong file cấu hình

Manifest.xml của ứng dụng Android (Lưu ý: Sinh viên có thể tham khảo thêm nguồn

tài liệu phong phú tại: https://developer.android.com/guide)

& Bài tập thực hành số 1

Vận dụng những kiến thức, kỹ năng đã được học trong Lập trình di động 1, hãy thiết kế giao diện cho ứng dụng sau (Hình 1.1.1) Hãy cho nhận xét!

Trang 10

Rõ ràng với giao diện mới thiết kế có rất nhiều thuộc tính lặp lại (giống với lập trình tuyến tính ngày trước) dẫn tới các file giao diện trông rất phức tạp và không hiệu quả Ngoài

ra, chỉ cần một chút thay đổi nhỏ của giao diện (thao tác thường xuyên được người sử dụng yêu cầu khi viết phần mềm) cũng có thể dẫn tới việc điều chỉnh lặp đi lặp lại các thuộc tính đó nhiều lần, rất khó cho quá trình bảo trì sau này Với các ứng dụng dạng này, chúng ta thường dùng Styles để định nghĩa cho các thuộc tính chung và có thể tái sử dụng chúng trong toàn chương trình

<item name="attributes_name_1">attributes value_1</item>

<item name="attributes_name_2">attributes value_2</item>

<item name="attributes_name_n">attributes value_n</item>

</style>

Dạng 2 Style mới kế thừa từ những thuộc tính của Style khác (Parent’s Style)

<style name="NameOfStyle" parent="Name of the Parent’s Style">

<item name="attributes_name_1">attributes value_1</item>

<item name="attributes_name_2">attributes value_2</item>

tự tuỳ biến các thuộc tính của hệ thống theo ý mình – Mục 1.5 sẽ đề cập vấn đề này)

Ví dụ 1: Với giao diện được thiết kế như Hình 1.1.1 chúng ta có thể tối ưu code bằng

cách định nghĩa một Style cho Button như sau:

Trang 11

Trong file giao diện, để sử dụng Style mới định nghĩa chúng ta dùng thẻ “style =

@styles/NameOfStyle” Với định nghĩa Style ở trên, chúng ta sẽ có thiết kế cho một hàng 4 buttons như sau (Button 1, 2, 3 và “+”):

có và bổ sung thêm thuộc tính lề phải cho nó:

<style name="MarginRightButtonStyle" parent="ButtonStyle" >

nó chỉ có thuộc tính lề trái, trên và dưới (Nên nó bổ sung mà không định nghĩa lại)

Trang 12

& Bài tập thực hành số 2

Dựa trên định nghĩa dạng 1 và 2 cùng ví dụ 1, hãy hoàn thiện lại giao diện ứng dụng như trong hình 1.1.1 sao cho mọi thuộc tính chung trong đó đều phải được đưa vào Style

1.2 Widget tạo Widget mới

Không được nhầm lẫn Widget ở đây với App Widget (Các chương trình chạy ẩn trên

màn hình Home, ví dụ như chương trình dự báo thời tiết…) Có nhiều quan điểm, khái niệm khác nhau về Widget trong lập trình di động trên Android: Có quan điểm cho rằng Widget là những phần tử giao diện cơ bản, được định nghĩa trước trong bộ Android SDK Nhưng cũng có quan điểm cho rằng Widget là những phần tử giao diện mở rộng thêm cho những phần tử giao diện cơ bản (như Button, EditText,…) để tạo ra một thư viện các phần tử giao diện mới với nhiều phần tử ứng dụng đầu cuối (như ImageView, WebView, VideoView, ProgressBar, CalendarView, RatingBar…) Tuy nhiên, dù theo quan điểm nào thì cũng không có sự phân biệt rõ ràng và minh bạch giữa phần tử giao diện cơ bản với những phần tử giao diện khác (vì theo quan điểm hiện đại chúng cũng

là phần tử giao diện cơ bản)

Trong giáo trình này, chúng tôi coi widget là những phần tử giao diện cơ bản, hoạt động như một đối tượng độc lập, đã được xây dựng trước (eg button, text, label,

image, check, …) có thể dùng để hiển thị thông tin và tương tác với người dùng trong các ứng dụng Android Như vậy, mọi phần tử giao diện cơ bản từ các Button đến các CalendarView hay ProgressBar, RatingBar… đều được coi là các Widgets

Một Widget về nguyên tắc cũng có thể được người dùng tự xây dựng thêm để bổ sung vào thư viện các Widgets có sẵn trong Android SDK (còn có thể được gọi là các

traditional widgets) Nhu cầu tạo ra các widget mới trong thực tế xảy ra rất nhiều, do

các widgets có sẵn chưa đủ đáp ứng cho các ứng dụng trong thực tiễn Ví dụ như trước đây muốn có một RatingBar cho ứng dụng Android chúng ta phải tự xây dựng, tuy nhiên

ở phiên bản mới nhất, widget này đã được bổ sung vào thư viện Do đó, kỹ năng xây dựng widget là một trong những mục tiêu quan trọng của giáo trình này

Có ba cách xây dựng ra một Widget mới:

- Cách 1: Xây dựng Widget mới bằng việc kết hợp những widgets đã có thành một

Widget phức hợp (Compound View);

Trang 13

- Cách 2: Xây dựng Widget mới bằng cách kế thừa từ một trong những widgets đã có;

- Cách 3: Xây dựng mới hoàn toàn bằng cách kế thừa từ lớp View đã có

Trong giáo trình này, chúng tôi sẽ dùng cách 1 để xây dựng một Widget mới có tên

Player mô phỏng hoạt động của các nút điều khiển (play, pause, stop next, back) của

thiết bị nghe nhạc, xem phim… (Widget có giao diện giống như Hình 1.2.1)

Bước 1: Thiết kế giao diện của Widget

& Bài tập thực hành số 3

Với kiến thức, kỹ năng đã học trong Lập trình di động 1, hãy thiết kế giao diện của Widget Player trông giống như Hình 1.2.1 (Chỉ riêng phần Widget Player)

Yêu cầu: Widget mới được xây dựng từ 5 đối tượng

ImageView (tương ứng với 5 button của Player) sao cho các

ImageView này có thể co dãn tuỳ theo layout sẽ chứa nó có kích thước như thế nào, nhưng vẫn phải đảm bảo sự đồng đều giữa các button với nhau (Ảnh nền của các image view cũng phải co dãn theo cho phù hợp kích thước)

Hình 1.2.1 Widget Player

Về lý thuyết, mỗi button trong Widget Player sẽ có 5 trạng thái: Bình thường (normal), được nhấn/tap (pressed), được chọn (selected), nhận focus (focus) và không tương tác

(disable) Với ứng dụng của chúng ta thì mỗi button sẽ chỉ xử lý 3 trạng thái đó là bình

thường (màu đen giống như trong hình 1.2.1), khi button được nhấn/tap (màu xanh

nhạt giống như ) và trạng thái được chọn (Có cùng màu với trạng thái pressed) Trong

Android, để thực hiện điều này, cần viết cho mỗi button một Selector có dạng:

<?xml version="1.0" encoding="utf-8"?>

<selector xmlns: android ="http://schemas.android.com/apk/res/android">

<item android :drawable="@drawable/ic_next" android :state_pressed="false"

android :state_selected="false" /> <item android :drawable="@drawable/ic_next_pressed" android :state_pressed="true" /> <item android :drawable="@drawable/ic_next_pressed" android :state_selected="true" />

</selector>

& Bài tập thực hành số 4

Hãy viết tất cả các Selector cho tất cả các button trong Widget Player và hãy thay thế

thuộc tính ảnh của mỗi button (android:src) trong layout đã thiết kế trong Bài tập thực

hành số 3 ở trên bằng các selector tương ứng với chúng

Trang 14

Bước 2: Định nghĩa file Java điều khiển cho Widget Player

Nếu xây dựng Widget mới theo cách 2 và 3 thường chúng ta phải tạo lớp điều khiển tương ứng hoặc kế thừa từ lớp View hoặc kế thừa từ đối tượng chúng ta định mở rộng

và sau đó tự định nghĩa hàm draw và tự “vẽ” mọi thứ theo yêu cầu Tuy nhiên, xây dựng

Widget theo cách 3 thì thường chúng ta sẽ kế thừa từ các ViewGroup mà từ đó các đối

tượng của Widget mới được tích hợp vào Với ứng dụng này, chúng ta sẽ kế thừa từ lớp LinearLayout (Do các đối tượng button của Widget Player được tích hợp vào trong một LinearLayout)

public class Player extends LinearLayout {

public Player ( Context context) {

lý sự kiện cho các đối tượng – các button…)

Trong phương thức init(), dựa trên kiến thức và kỹ năng đã học trong Lập trình

di động 1 hãy gắn giao diện của Widget Player được tạo trong Bài tập thực hành số 3 cho Widget này Đồng thời, khai báo các biến thành phần của lớp chính là các đối tượng ImageView và dùng hàm findViewById() để lấy các đối tượng button và lưu vào các

Trang 15

biến thành phần tương ứng (btnNext, btnPrevious, btnPlay, btnPause và btnStop) Hãy tạo một Activity mới để chạy thử cho Widget vừa tạo (Layout giống hình 1.2.1) Chạy thử chương trình và cho nhận xét!

Bước 3: Xử lý sự kiện và hoàn thiện Widget Player

Về mặt giao diện, Widget Player đã giống với yêu cầu đặt ra, các button cũng đã thể hiện được hai trạng thái (normal và pressed), còn trạng thái selected chưa hoạt động Muốn các button của Widget Player có trạng thái Selected thì mỗi khi nhấp/tap trên mỗi button chúng ta cần bắt sự kiện đó và chuyển trạng thái selected của button tương ứng

về true nếu trước đó nó đang là false và ngược lại

& Bài tập thực hành số 6

Dựa trên kiến thức, kỹ năng đã học trong Lập trình di động 1, hãy tiến hành bắt sự kiện click/tap trên mỗi button và kiểm tra trạng thái isSelected của nó, nếu trước đó nó có giá trị false thì chuyển thành true và ngược lại Chạy thử và cho nhận xét! Hãy thực hiện bài tập số 10 để hoàn thiện thêm cho Widget Player

Với Widget Player đã hoàn thiện trong Bài tập thực hành số 6 thì mỗi khi người sử

dụng tap/nhấp trên một button ngoài giao diện và chuyển trạng thái ra, các chức năng của Widget chưa thực sự hoạt động Vì đây là một Control chung nên chúng ta không thể biết trước người sử dụng muốn làm chính xác điều gì mỗi khi họ tap/nhấp trên một button cụ thể Điều này trong iOS được thực hiện dễ dàng thông qua cơ chế uỷ quyền (Delegation) Tuy nhiên, trong Android không có cơ chế uỷ quyền do đó chúng ta sẽ

thực hiện chúng bằng cách mô phỏng cơ chế uỷ quyền thông qua sức mạnh của interface

trong Java (Tương tự như Protocol trong iOS)

& Bài tập thực hành số 7

Dựa trên kiến thức, kỹ năng đã học trong Lập trình di động 1, Lập trình di động trên iOS

và Lập trình Java, hãy xây dựng một interface có tên PlayerDelegation trong đó định

nghĩa tất cả các phương thức cần thiết cho Widget Player Tiếp theo định nghĩa thêm

một biến thành phần có tên delegate trong lớp Player.java và sửa lại chức năng xử lý sự

kiện khi tap/nhấn vào mỗi button của control sao cho mỗi khi button đó có trạng thái

selected thì đồng thời cũng gọi hàm tương ứng với nút đó trong delegate Bổ sung thêm

phương thức setDelegate() để mỗi khi người sử dụng thực hiện uỷ quyền cho các chức

Trang 16

năng của Widget Player thì phương thức này sẽ được gọi Hãy kiểm tra lại hoạt động của Widget Player bằng cách thực hiện giao diện PlayerDelegation trong Activity sử dụng nó, tự viết lấy các phương thức tương ứng của giao diện để thực hiện các chức năng tương ứng của Widget Player, đồng thời gọi phương thức uỷ quyền của đối tượng

player Chạy thử chương trình và cho nhận xét!

1.3 Multi-Screen and Orientation

Khác với lập trình di động trên iOS, thiết kế giao diện cho các ứng dụng trong Android rất phức tạp do sự đa dạng của các loại màn hình với nhiều độ phân giải khác nhau Do vậy, thiết kế ra các loại giao diện hỗ trợ nhiều loại màn hình khác nhau là một yêu cầu bắt buộc với lập trình viên Android Ngoài ra, do đặc thù nhỏ bé của màn hình điện thoại, nhiều khi giao diện ứng dụng cũng cần thay đổi khác nhau với các chế độ màn hình khác nhau (chế độ dọc và chế độ xoay ngang)

Thiết kế giao diện hỗ trợ Multi-Orientation

Hình 1.3.1 Giao diện ứng dụng hỗ trợ Multi-Orientation

Hình 1.3.1 là một ví dụ điển hình cho việc hỗ trợ giao diện ứng dụng với các loại màn

có độ phân giải khác nhau cùng các chế độ khác nhau (chiều dọc khác chiều ngang) Trong phần này chúng ta sẽ thực hiện thiết kế giao diện ứng dụng hỗ trợ các chế độ màn hình khác nhau

Bước 1: Thiết kế giao diện ứng dụng theo chiều dọc như bình thường Trong ví dụ này

chúng ta lấy lại luôn giao diện đã thiết kế trong hình 1.2.1 trước đây

Trang 17

Bước 2: Mở file giao diện, nhấp chọn biểu tượng và chọn Create Landscape Variation

(Hình 1.3.2) => Android Studio sẽ tạo ra một thư mục trong res/layout-land và copy

file layout tương ứng vào đó (cùng tên, khác thư mục) Lúc này, mọi thay đổi trên file

layout trong thư mục res/layout-land sẽ được hiệu ứng cho ứng dụng mỗi khi màn hình

điện thoại được xoay theo chiều ngang

Hình 1.3.2 Tạo biến thể cho giao diện ứng dụng theo chiều ngang

& Bài tập thực hành số 8

Dựa trên kiến thức, kỹ năng đã học trong Lập trình di động 1, hãy thiết kế giao diện trong res/layout-land có dạng như Hình 1.3.3 Chạy thử chương trình và xoay màn hình

từ dọc thành ngang và ngược lại Cho nhận xét!

Hình 1.3.3 Giao diện ứng dụng khi xoay theo chiều ngang

Thiết kế giao diện hỗ trợ Multi-Screen

Muốn hỗ trợ nhiều độ phân giải hay kích thước màn hình khác nhau, thực hiện như ở

Bước 2 trong phần trên nhưng chọn Create other… (Hình 1.3.4)

Trang 18

Hình 1.3.4 Thiết kế giao diện hỗ trợ nhiều loại màn hình

Trong Android, có nhiều cách khác nhau để phát triển các ứng dụng hỗ trợ cho nhiều loại kích thước màn hình khác nhau như: Sử dụng chiều rộng nhỏ nhất (The smallest width qualifier); sử dụng với các loại kích thước chiều rộng khác nhau (The available width qualifier),… Trong giáo trình này chúng ta sử dụng việc thiết kế giao diện hỗ trợ

đa màn hình dựa trên mật độ điểm ảnh (dpi)

Trong Android, người ta lấy các loại màn hình có mật độ điểm ảnh trung bình là

160dpi làm màn hình cơ sở và đặt tên là mdpi (medium-density) Những loại màn hình

có mật độ điểm ảnh thấp hơn (vào khoảng 120dpi) được gọi là ldpi (low-density) và những loại màn hình có mật độ điểm ảnh cao hơn (vào khoảng 240dpi) thì được gọi là màn hình loại hdpi (high-density)… Dưới đây là một số phân loại cơ bản các màn hình dựa trên mật độ điểm ảnh (Hình 1.3.5)

Hình 1.3.5 Phân loại màn hình theo mật độ điểm ảnh

Với mỗi loại tài nguyên khác nhau trong Android khi ở chế độ hỗ trợ nhiều màn hình sẽ tạo ra một thư mục tương ứng với đuôi gắn thêm “-TenLoaiManHinh” Ví dụ nếu là icon cho loại màn hình hdpi thì sẽ nằm trong thư mục mipmap-hdpi; nếu là layout hỗ trợ cho màn hình loại xxhdpi thì nó sẽ được đặt trong thư mục layout-xxhdpi… Trong

ví dụ ở trên, sau khi chọn “Create Other…” sẽ xuất hiện Dialog hình 1.3.6

Trang 19

Hình 1.3.6 Lựa chọn hỗ trợ nhiều màn hình theo mật độ điểm ảnh

Lựa chọn Density (theo mật độ điểm ảnh) rồi click vào nút để lựa chọn, màn hình Hình 1.3.7 xuất hiện, chọn loại chế độ màn hình sẽ hỗ trợ (từ Low Density đến XXX-High Density) và nhấp OK

Hình 1.3.7 Lựa chọn một chế độ màn hình để tạo layout

Android Studio sẽ tạo một thư mục mới trong res/layout-xxxhdpi và copy file layout trước đó vào thư mục này Điều chỉnh giao diện theo yêu cầu với loại màn hình này, khi

đó mỗi khi chạy chương trình trên các điện thoại có màn hình loại xxxhdpi (tương đương 640dpi trở lên) thì ứng dụng sẽ lấy giao diện vừa tạo và hiển thị ra màn hình tương ứng

Một số lưu ý trong thiết kế giao diện trên Android

- Không hard-code với kích thước (rộng và cao) của các đối tượng view, mà chỉ dùng

“match_parent” hoặc “wrap_content”;

Trang 20

- Sử dụng android:layout_weight với Linearlayout để phân chia không gian màn hình

cho phù hợp mỗi khi thay đổi loại màn hình khác nhau (Nhưng tránh lồng nhau sẽ làm giảm hiệu năng của UI);

- Sử dụng ConstraintLayout thay vì các GroupView khác (Chú ý không dùng

match_parent cho layout loại này mà thiết lập 0dp – tương tự match_parent trong các loại layout khác);

- Với những resource như file ảnh, nếu hệ thống không tìm thấy độ phân giải phù hợp với loại màn hình, nó sẽ lựa chọn tài nguyên mặc định và pre-scaling cho phù hợp với loại màn hình đã cho (Ví dụ nếu icon có kích thước 50x50 ở mdpi nếu pre-scaling lên hdpi sẽ có kích thước là 75x75) Nếu không muốn hệ thống tự động pre-scaling tài

nguyên nào, thì đặt tài nguyên đó trong thư mục -nodpi (ví dụ drawable-nodpi);

- Có thể tắt chức năng pre-scaling ảnh/dimension bằng cách thiết lập chế độ

android:anyDensity về false trong Manifest hoặc dùng code thiết lập inScaled về false

cho đối tượng mong muốn (Chỉ tắt chức năng này khi thực sự cần thiết);

- Nếu trong layout bắt buộc có các kích thước cụ thể, để hỗ trợ đa màn hình cần thực hiện tương tự với file dimen.xml;

- Trường hợp màn hình có cùng độ phân giải nhưng khác nhau bởi thanh bar cứng và

mềm cần dùng stretchable nine-patch bitmaps (Chỉ hỗ trợ ảnh loại PNG)

& Bài tập thực hành số 9

Dựa trên những kiến thức và kỹ năng đã học, thiết kế giao diện ứng dụng trong Hình 1.2.1 thành ứng dụng có thể hỗ trợ 6 loại màn hình ldpi, mdpi, hdpi, xhdpi, xxhdpi và xxxhdpi Điều chỉnh để ứng dụng vừa hỗ trợ 6 loại màn hình trên vừa hỗ trợ cả hai hướng của màn hình (Dọc và xoay ngang)

1.4 RecyclerView and Card View

RecyclerView là một cải tiến của ListView nhằm hạn chế điểm yếu của ListView (hiệu năng sử dụng thấp khi không có View holder) và bổ sung thêm một số tính năng khác giúp “ListView” hấp dẫn và hiệu quả hơn Thông thường, khi phát triển các ứng dụng

sử dụng RecyclerView, mỗi phần tử của nó sẽ lấy CardView làm nền tảng thiết kế (Người dùng cũng có thể tự định nghĩa lấy layout phần tử của riêng mình)

Trang 21

RecyclerView chỉ có thể được sử dụng và hỗ trợ bởi các hệ điều hành Android từ 5.0

trở về sau (Các phiên bản trước chưa có) Do vậy, các Project ví dụ sẽ phải tạo với các API level từ 21 trở lên

Về nguyên tắc hoạt động của RecyclerView cũng hoàn toàn giống với ListView nhưng do RecyclerView tích hợp sẵn việc định nghĩa cho các View Holder (là bắt buộc chứ không như ListView) nên cách xây dựng và thiết kế sẽ khác Nguyên tắc hoạt động

chung: Hệ thống sẽ tính toán số phần tử có thể được hiện ra màn hình tại một thời điểm

rồi sau đó Adapter lần lượt đọc dữ liệu từ mảng Datasource, tạo các phần tử tương ứng

và hiển thị ra màn hình (cho đến hết màn hình nếu số phần tử của mảng dữ liệu đủ lớn) Không phải tất cả các phần tử của ListView/RecyclerView được tạo cùng một lượt, mà chỉ những phần tử đang hiện ra trên màn hình mới được tạo Mỗi khi người dùng cuốn màn hình để di chuyển đến các phần tử trước/sau, khi đó những phần tử bị cuốn ẩn đi sẽ được tái sử dụng và dữ liệu mới được đổ vào dẫn đến ta có cảm giác đó giống như là một phần tử mới vừa được tạo ra và theo cách đó hiệu năng của ListView/RecyclerView

sẽ tăng lên đáng kể (do tiết kiệm được rất nhiều nguồn lực cho việc thực hiện các câu

lệnh findViewById không cần thiết)

Ngoài ra, RecyclerView còn cho phép điều khiển cách xuất hiện của các phần tử của nó thông qua các LayoutManager Có 3 loại LayoutManager được hỗ trợ bởi

RecyclerView đó là: LinearLayoutManager (Cho phép các phần tử trong danh sách được hiển thị 1 chiều theo hướng dọc hoặc ngang); GridLayoutManager (Cho phép

các phần tử trong danh sách hiển thị theo hai chiều - giống ma trận, nếu tổ chức theo chiều dọc thì các phần tử trong mỗi hàng có cùng kích thước, nếu tổ chức theo chiều ngang thì các phần tử trong mỗi cột có cùng kích thước); và cuối cùng là

StaggeredGridLayoutManager (Giống với GridLayoutManager nhưng kích thước

của các phần tử có thể khác nhau) Thêm nữa, cùng với sự kết hợp giữa RecyclerView

và CardView sẽ cho phép lập trình viên tạo ra nhiều dạng danh sách linh động khác nhau

mà Listview không thể làm được

Trong lập trình Android, để có thể thiết kế một RecyclerView theo ý muốn, hãy thực hiện lần lượt các bước sau:

Bước 0: Chuẩn bị

& Bài tập thực hành số 10

Trang 22

Dựa trên kiến thức và kỹ năng đã học trong Lập trình di động 1, hãy tạo một Project có

tên RecyclerViewExample (Android API 24), kiểm tra nếu chưa có thì thêm hai thư viện chứa RecyclerView và CardView vào Project

Bước 1: Thiết kế giao diện RecyclerView

Cũng giống Customize Listview, với RecyclerView chúng ta cũng cần thiết kế layout cho mỗi phần tử của nó Tuy nhiên, khác với Listview trước đây, chúng ta sẽ sử dụng CardView như một ViewGroup thay vì LinearLayout trước đây

& Bài tập thực hành số 11

Thiết kế lại giao diện của ứng dụng Quản lý nhân sự như trong Lập trình di động 1 sao

cho Listview sẽ được thay bằng RecyclerView và layout của mỗi phần tử trong Listview

trước đó sẽ thay LinearLayout bao ngoài bằng CardView

Gợi ý: Cardview là một ViewGroup mới được bổ sung thêm một số thuộc tính của riêng

nó Muốn sử dụng chúng cần thêm namespaces vào file layout như sau:

xmlns: card_view ="http://schemas.android.com/apk/res-auto"

Khi đó, chúng ta có thể sử dụng thêm một số thuộc tính mới như:

card_view :cardCornerRadius="4dp" // Bo góc cho phần tử

card_view :cardElevation="4dp" // Tạo độ bóng cho phần tử …

Lưu ý: Do Cardview giống như một FrameLayout nên thường bên trong nó cần sử dụng

thêm các ViewGroup khác cho dễ thiết kế (LinearLayout, ConstraintLayout…)

Bước 2: Xây dựng Adapter và View Holder

Giống như Customize Listview chúng ta cần xây dựng lớp Adapter riêng cho mỗi RecyclerView Để thực hiện điều đó, hãy thực hiện các bước nhỏ sau:

- Tạo lớp mới trong package “adapters”có tên RecyclerViewAdapter, kế thừa từ lớp

RecyclerView.Adapter của thư viện RecyclerView

- Thực hiện các phương thức mặc định bắt buộc của lớp:

Trang 23

public void onBindViewHolder (@NonNull RecyclerView ViewHolder holder, int position) {

public static class MyViewHolder extends RecyclerView ViewHolder {

private ImageView degreeImage ;

private TextView lblName ;

private TextView lblHoppies ;

private CheckBox chkPerson ;

public MyViewHolder (@NonNull View itemView) {

super(itemView);

degreeImage = itemView.findViewById( R id degreeImage);

lblName = itemView.findViewById( R id lblName);

lblHoppies = itemView.findViewById( R id lblHoppies);

chkPerson = itemView.findViewById( R id chkChosen);

}

}

- Điều chỉnh lại định nghĩa cho lớp RecyclerViewAdapter và thêm Constructor:

public class RecyclerViewAdapter extends

RecyclerView Adapter < RecyclerViewAdapter MyViewHolder > {

private Activity context ;

private int layoutID ;

private ArrayList < Person > members ;

public RecyclerViewAdapter ( Activity context, int layoutID, ArrayList < Person > members) { this context = context;

this layoutID = layoutID;

this members = members;

}

}

- Hoàn thiện RecyclerViewAdapter:

Mỗi RecyclerViewAdapter cần xây dựng 4 phương thức cơ bản cho nó Thiếu một trong những phương thức này có thể RecyclerView hoạt động không theo ý muốn

Phương thức 1: getItemCount() – Phương thức trả về số các phần tử của danh sách

@Override

public int getItemCount () {

return members size();

}

Phương thức 2: getItemViewType() – Phương thức trả về layout của item tại vị trí position Trong ví dụ này các phần tử có cùng giao diện chứa trong layoutID

Trang 24

@Override

public int getItemViewType (int position) {

return layoutID ;

}

Phương thức 3: onCreateViewHolder() – Phương thức này sẽ được gọi mỗi khi

RecyclerView cần tạo một phần tử mới (chứ không phải tái sử dụng lại)

Phương thức 4: onBindViewHolder() – Phương thức này sẽ được gọi mỗi khi cần đổ

dữ liệu vào các item mới

@Override

public void onBindViewHolder (@NonNull MyViewHolder holder, int position) {

// Get person from datasource

Person person = members get(position);

// Fill data from the person to the view

if ( person getDegree().equalsIgnoreCase( QuanLyNhanSuActivity CAODANG)) {

holder lblName setText( person getName());

holder lblHoppies setText( person getHobbies());

// Processing the checkbox's event

holder chkPerson setOnClickListener(new View OnClickListener () {

Trang 25

Sử dụng RecyclerView cũng giống như cách làm với ListView Có một điểm khác duy nhất đó là trước khi gắn Adapter cần thiết lập LayoutManager cho RecyclerView

// Create Layoutmanager

LinearLayoutManager layoutManager = new LinearLayoutManager(this);

layoutManager setOrientation( RecyclerView VERTICAL);

listView setLayoutManager( layoutManager );

adapter = new RecyclerViewAdapter(this, R layout listview_item, listMembers );

listView setAdapter( adapter );

& Bài tập thực hành số 12

Thay đổi hướng của RecyclerView dùng LayoutManager ở trên Tìm hiểu hai dạng LayoutManager còn lại (GridLayoutManager và StaggeredGridLayoutManager) xây dựng RecyclerView với 2, 3, 4 cột (với kích thước giống và khác nhau)

1.5 Tuỳ biến giao diện hệ thống

Giao diện hệ thống thông thường được định nghĩa trước bởi các Theme sử dụng cho ứng dụng đó Tuy nhiên, chúng ta hoàn toàn có thể thay đổi hệ thống theo ý mình tuỳ theo nhu cầu sử dụng cụ thể Trong giáo trình này sẽ hướng dẫn cách tuỳ biến thanh ActionBar và cách hiển thị tiêu đề của ứng dụng Những thuộc tính khác của giao diện

Trong đó tag <stroke> cho phép định nghĩa đường viền ngoài và tag <gradient> dùng

để định nghĩa sự biến đổi màu sắc theo góc 90 độ và thay đổi liên tục từ màu startColor qua màu middleColor và sang màu enColor Kết quả chúng ta có một màu nền biến đổi theo thuật toán gradient như thiết kế ở trên

Bước 1: Định nghĩa lại thuộc tính của giao diện hệ thống

Muốn thay đổi thuộc tính mặc định của thanh ActionBar, cần định nghĩa lại thuộc tính background của ActionBar thành màu đã định nghĩa ở trên

Trang 26

& Bài tập thực hành số 13

Dựa trên kiến thức, kỹ năng đã học trong mục 1.1, hãy xây dựng một style mới có tên

“MyActionBar” và kế thừa từ những định nghĩa mặc định cho ActionBar của hệ thống:

parent="Widget.AppCompat.ActionBar.Solid"

Trong Style AppTheme của hệ thống, thêm thuộc tính mới định nghĩa lại cho ActionBar

bằng cách thay đổi actionBarStyle bằng Style mới định nghĩa lại:

<item name="actionBarStyle">@style/MyActionBar</item>

Chạy chương trình và cho nhận xét!

Để hoàn thiện cho thanh ActionBar mới, chúng ta cần điều chỉnh lại các thuộc tính của

Title cho ActionBar (Thay đổi cỡ chữ, kiểu chữ đậm và màu chữ cho phù hợp hơn)

& Bài tập thực hành số 14

Hãy xây dựng một Style mới có tên MyTextActionBarTitle kế thừa từ những thuộc

tính đã định nghĩa mặc định cho Title của ActionBar:

parent="TextAppearance.AppCompat.Widget.ActionBar.Title"

Trong đó chúng ta thay đổi lại các thuộc tính android:textSize, android:textColor và android:textStyle cho phù hợp

Hãy sửa lại thuộc tính titleTextStyle trong MyActionBar ở trên bằng Style vừa

định nghĩa lại cho Title của ActionBar Chạy chương trình và cho nhận xét!

1.6 Câu hỏi và bài tập làm thêm chương 1

1 Nếu Style A có thuộc tính a, b và c Style B kế thừa từ A trong đó có định nghĩa cho hai thuộc tính là a và d Khi đó, Style B sẽ có những thuộc tính nào?

2 Hãy xây dựng lại giao diện cho ứng dụng Calculator đã thiết kế trong Lập trình di động 1 bằng cách sử dụng Style!

3 Hãy xây dựng lại giao diện cho ứng dụng Trắc nghiệm khách quan trong Lập trình di động 1 bằng cách sử dụng Style!

4 hãy xây dựng lại giao diện ứng dụng Quản lý nhân sự trong Lập trình di động 1 bằng cách sử dụng Style!

Trang 27

5 Hãy điều chỉnh ứng dụng Quản lý nhân sự trong Lập trình di động 1 bằng cách sử dụng RecyclerView thay thế cho ListView!

6 Hãy tuỳ biến giao diện hệ thống của ba ứng dụng trong Lập trình di động 1 theo cách

mà bản thân mình mong muốn!

7* Hãy tìm hiểu cách xây dựng Widget theo cách 3, viết game đơn giản Snack!

8* Hãy tìm hiểu thêm cách xây dựng Widget theo cách 1, tuỳ biến đối tượng EditText thành một đối tượng mới có hai thành phần: ImageView (nằm ở phía đầu của EditText)

và chính bản thân EditText đó (phía sau)!

9 Tuỳ biến lại Widget mới xây dựng Player sao cho nó hoạt động giống với bộ điều khiển play nhạc, video trong thực tế nhất!

10 Sửa lại yêu cầu trong Bài tập thực hành số 6 sao cho trong một thời điểm chỉ có một button được phép ở trạng

thái selected

11* Hãy tìm hiểu thêm cách xây dựng Widget theo cách

2, tuỳ biến đối tượng Button

12 Tìm hiểu thêm về ConstraintLayout và thực hiện Bài tập thực hành số 9 bằng cách sử dụng layout này!

13* Sử dụng RecyclerView xây dựng một ứng dụng cho phép chạy Banner quảng cáo một cách tự động

14 Hãy tuỳ biến giao diện hệ thống bằng cách ẩn thanh ActionBar? Ẩn cả thanh ActionBar và thanh trạng thái của điện thoại?

Hình 1.6.1 Kết quả tuỳ biến thanh ActionBar

15 Hãy tuỳ biến thanh ActionBar của ứng dụng đã hoàn thiện trong mục 1.4 sao cho kết quả thu được giống như Hình 1.6.1 Khi nhấp/tap chọn biểu tượng sẽ hiển thị các phần tử còn lại của Option Menu (Nền của popup menu màu ghi, chữ màu xanh)

Trang 28

CHƯƠNG 2 LÀM VIỆC VỚI TÀI NGUYÊN TRÊN MẠNG INTERNET

2.1 Kết nối vào mạng Internet

Để có thể kết nối vào mạng Internet, cần khai báo permission trong manifest file Có ba loại permission trong Android đó là install-time permission, runtime permission và special permission Install-time permission lại gồm hai loại là normal permission và signature permission Normal permission cho phép ứng dụng thực hiện các hành vi hoặc truy xuất dữ liệu ít rủi ro cho hệ thống (và chỉ cần khai báo trong Manifest file) Signature permission cho phép một ứng dụng khai báo quyền truy xuất/ thực hiện thao tác đã được định nghĩa từ trước bởi một ứng dụng khác và nếu hệ thống so sánh chúng tương thích nhau thì sẽ cấp quyền cho ứng dụng đó ngay khi cài đặt chương trình Runtime permission sẽ cấp quyền cho những thao tác có nguy cơ cao đến an toàn của hệ thống hơn do đó chỉ khi chạy chương trình người dùng mới được yêu cầu cấp quyền truy xuất/ thực hiện (Sự khác biệt chỉ với Android 6.0 – API 23 trở về sau) Special permission cho phép ứng dụng có thể thực hiện một số thao tác đặc biệt với hệ thống (Ví dụ khởi động lại hệ thống, unlock màn hình…) và đồi hỏi cũng cần có những thiết lập đặc biệt Các permission này chúng ta sẽ lần lượt thực hiện trong suốt giáo trình

Với việc truy xuất tài nguyên Internet, chúng ta chỉ cần khai báo hai install-time permission sau trong Manifest file:

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

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

Về cơ bản, có hai kỹ thuật kết nối Internet đó là Mobile Internet (GPRS, EDGE, 3G, 4G, and LTE Internet access) và Wifi (Private and public Wi-Fi access points) Tuy

nhiên trong kết nối Android với Internet thông thường không cần chỉ định tường minh

sẽ sử dụng kỹ thuật nào để kết nối

Trang 29

Muốn kết nối ứng dụng với Internet cần thực hiện các bước sau:

Bước 1: Kiểm tra url cần kết nối thuộc loại http hay https:

boolean isHTTPs = URLUtil.isHttpsUrl(url);

Bước 2: Kiểm tra Proxy mạng

proxyAddress = System.getProperty( "http.proxyHost" );

String portStr = System.getProperty( "http.proxyPort" );

proxyPort = Integer.parseInt( ( portStr != null ? portStr : "-1" ) );

Bước 3: Mở kết nối HttpUrlConnection

URL standardUrl = new URL(url);

// Create URL Connection

Bước 4: Kiểm tra trạng thái kết nối, download và xử lý dữ liệu

int resCode = httpURLConnection getResponseCode();

if ( resCode == HttpURLConnection HTTP_OK) {

InputStream inputStream = httpURLConnection getInputStream();

processInternetDownload(inputStream );

}

httpURLConnection disconnect();

Lưu ý: Việc mở kết nối Internet và việc Download dữ liệu về là những thao tác xuất

hiện Exceptions Do vậy, tất cả các câu lệnh trên cần để trong khối try … catch:

Hãy hoàn thiện việc thực hiện kết nối Internet và xử lý việc download dữ liệu từ Internet

về bằng việc viết hàm public void internetConnection (String url, Proxy proxy), hàm private Proxy getProxy () và hàm private void processInternetDownload(InputStream

Trang 30

inputStream) Thử nghiệm hoạt động của các hàm mới viết, chạy thử chương trình và cho nhận xét!

2.2 Thực hiện các thao tác trên mạng với tiến trình chạy ngầm sử dụng

Asynchronous Tasks và trích xuất dữ liệu từ Webservice

Chương trình chạy thử trong Bài thực hành số 15 sẽ cho một Exception dạng android.os.NetworkOnMainThreadException Muốn tránh lỗi này cần thiết kế tiến trình thực hiện việc kết nối và download dữ liệu về từ Internet chạy ngầm (song song với tiến trình chính của UI - Background thread) Để làm được điều này cần thiết kế đối tượng

AsyncTask (hiện nay đang được khuyến cáo không sử dụng) hoặc dựa trên những thư

viện khác như ExecutorService, ThreadPoolExecutor, … nhằm tạo ra các tiến trình chạy

ẩn cho quá trình download dữ liệu từ mạng Internet Việc thực hiện này đòi hỏi nhiều

kỹ thuật phức tạp và tốn nhiều công sức (xây dựng AsyncTask, parse dữ liệu trả về từ AsyncTask, chuyển đổi từ các đối tượng json/xml sau khi parse sang các đối tượng Java…) Giáo trình này sẽ hướng dẫn chúng ta một cách làm khác dễ dàng hơn với bộ

công cụ rất mạnh hiện nay: Retrofit

Với Retrofit lập trình viên không cần phải quản lý các tiến trình (giống như làm với AsyncTask), cũng không cần phải thực hiện việc parse dữ liệu và chuyển đổi qua lại giữa các đối tượng json/xml và đối tượng Java nữa mà chỉ cần thực hiện một vài bước khá đơn giản và dễ hiểu

Bước 1: Xây dựng giao diện chương trình

& Bài tập thực hành số 16

Hãy tạo một Project mới có tên WeatherForecast với giao diện giống như Hình 2.2.1 Trong đó Spinner dùng để chọn thành phố cần dự báo, và một RecyclerView dùng để hiển thị kết quả dự báo

Hình 2.2.1 Màn hình giao diện ứng dụng WeatherForecast

Bước 2: Tích hợp thư viện Retrofit vào Project

& Bài tập thực hành số 17

Hãy tích hợp Retrofit2, Retrofit2:converter-gson và gson vào trong Project

Trang 31

Bước 3: Xây dựng giao diện Retrofit

Tạo lớp Java có tên WeatherAPI và thiết kế nội dung giao diện như sau:

public interface WeatherAPI {

String BASE_URL = "http://api.openweathermap.org/";

@GET("data/2.5/forecast?")

Call < WeatherWrapper < Weather >> getWeathers (@Query("q") String city,

@Query("appid") String key);

}

Trong đó BASE_URL là API gốc để gọi Webservice dự báo thời tiết Giao diện sẽ định nghĩa một hàm có tên getWeathers với hai tham số là city (tên thành phố muốn dự báo)

và key (API Key của trang openweathermap.org) Hai tham số này nhờ vào các chỉ báo

@Query của Retrofit kết hợp với @GET để xây dựng nên các tham số cho Webservice

API như mong muốn Hàm sẽ trả về một mảng các đối tượng kiểu Weather (sẽ định

nghĩa sau cho phù hợp với cấu trúc trả về của Webservice) được lưu trữ trong một đối tượng có tên WeatherWrapper (Viết dưới dạng generic trong Java):

import com.google.gson.annotations SerializedName;

Trong đó thư viện SerializeName của Retrofit sẽ cho phép ánh xạ giữa mảng đối tượng

JSON có tag “list” với mảng các đối tượng tương ứng items trong Java (Đối tượng sẽ

được xác lập cụ thể khi khai báo biến)

Bước 4: Xây dựng cấu trúc Datamodel Weather.java

Dựa trên cấu trúc trả về của file Json từ Webservice openweather.org chúng ta định nghĩa lớp Weather như sau:

Lớp Weather sẽ có 3 lớp con tương ứng với 3 đối tượng Json nhỏ trong file Json trả về

public Main (float nhietDo, float nhietDoMax, float nhietDoMin) {

this nhietDo = nhietDo;

this nhietDoMax = nhietDoMax;

this nhietDoMin = nhietDoMin;

Trang 32

public void setNhietDo (float nhietDo) {

this nhietDo = nhietDo;

}

public void setNhietDoMax (float nhietDoMax) {

this nhietDoMax = nhietDoMax;

}

public void setNhietDoMin (float nhietDoMin) {

this nhietDoMin = nhietDoMin;

public WeatherItem ( String weather) {

this weather = weather;

}

public String getWeather () {

return weather ;

}

public void setWeather ( String weather) {

this weather = weather;

public Wind (float tocDoGio) {

this tocDoGio = tocDoGio;

}

public float getTocDoGio () {

return tocDoGio ;

}

public void setTocDoGio (float tocDoGio) {

this tocDoGio = tocDoGio;

Trang 33

}

}

Lớp này dùng để trích xuất các thông tin liên quan đến tốc độ gió Ngoài ra, các thông tin cần thiết khác như tầm nhìn xa, ngày tháng dự báo…nằm ở đối tượng Json ngoài cùng trong cây cấu trúc của Json nên được lấy trực tiếp trong lớp Weather Cuối cùng, lớp Weather được xây dựng như sau:

public class Weather {

this weatherItems = weatherItems;

this wind = wind;

this tamNhinXa = tamNhinXa;

this thoiGian = thoiGian;

}

public void setMain ( Main main) {

this main = main;

}

public void setWeatherItems ( ArrayList < WeatherItem > weatherItems) {

this weatherItems = weatherItems;

}

public void setWind ( Wind wind) {

this wind = wind;

}

public void setTamNhinXa (int tamNhinXa) {

this tamNhinXa = tamNhinXa;

}

public void setThoiGian ( String thoiGian) {

this thoiGian = thoiGian;

Trang 34

public String getThoiGian () {

Cấu trúc lớp data-model này rất quan trọng, giúp Retrofit tự động lấy dữ liệu về, parse

dữ liệu và chứa chúng vào các đối tượng Java tương ứng Do đó, nếu làm sai, chương trình sẽ không thể lấy được dữ liệu

Lưu ý: Chỉ định nghĩa các biến cần lấy từ file Json, không nhất thiết phải định nghĩa

toàn bộ các biến trong cấu trúc cây của nó

Bước 5: Xây dựng hàm lấy dữ liệu về từ Webservice sử dụng Retrofit và đưa vào trong

ứng dụng để có kết quả như trong Hình 2.2.1 trước đây

private void getWeatherData ( String city) {

list clear();

Retrofit retrofit = new Retrofit Builder()

baseUrl( WeatherAPI BASE_URL)

addConverterFactory( GsonConverterFactory.create())

build();

weatherAPI = retrofit create( WeatherAPI class);

Call < WeatherWrapper < Weather >> call = weatherAPI getWeathers(city, "key");

call enqueue(new Callback < WeatherWrapper < Weather >>() {

@Override

public void onResponse ( Call < WeatherWrapper < Weather >> call,

Response < WeatherWrapper < Weather >> response) {

if (response.isSuccessful()) {

WeatherWrapper < Weather > weathers = response.body();

assert weathers != null;

list addAll( weathers items );

});

}

Hàm sẽ truyền vào tham số là tên thành phố sẽ dự báo (tên này sẽ được lấy từ Spinner) Trước tiên, cần xoá mọi dữ liệu cũ trong danh sách list Tiếp theo khai báo một đối tượng Retrofit và khởi tạo nó với URL cơ sở của Webservice và bộ chuyển đổi qua lại Java-Json (JsonConverterFactory) Tiếp đến cần khởi tạo cho giao diện Retrofit mà chúng ta

đã định nghĩa trước đó để chuẩn bị cho việc lấy dữ liệu từ Webservice bằng cách hiện

thực hoá cho hàm getWeathers(city, key) với city là tham số truyền vào và key lấy từ tài khoản đã đăng ký trong trang openweathermap.org Hàm này cần được enqueue để

Trang 35

Retrofit tự động lấy dữ liệu về và kết quả sẽ trả về từ hàm Callback tương ứng Nếu có lỗi xảy ra trong quá trình lấy dữ liệu, Retrofit sẽ gọi đến hàm onFailure() Nếu muốn

biết thông tin chi tiết hãy thêm dòng throwable.printStackTrace(); vào trong hàm đó

Trường hợp không có lỗi truy xuất mạng, khi đó Retrofit sẽ gọi đến hàm onResponse()

và kết quả lấy về sẽ để trong biến response Cuối cùng đưa dữ liệu vào datasource của

RecyclerView và báo cho Adapter việc dữ liệu đã thay đổi để cập nhật dữ liệu mới

& Bài tập thực hành số 18

Để có thể thử nghiệm và sau đó hoàn thiện chương trình cần thực hiện:

- Dựa trên datamodel mới xây dựng Weather, hãy hoàn thiện RecyclerView sao cho mỗi phần tử của nó có thể hiển thị thông tin như Hình 2.2.1 trước đây

- Hãy hoàn thiện bắt sự kiện cho Spinner sao cho mỗi khi người dùng nhấp chọn một phần tử của nó (mỗi phần tử là tên một thành phố) thì sẽ gọi lại hàm getWeatherData()

ở trên để lấy dữ liệu dự báo thời tiết mới nhất cho thành phố đó và hiển thị lên Recyclerview như Hình 2.2.1

Mặc định, các files download bởi Download Manager được lưu trữ trong thư

mục share chung (có thể lấy thông qua Environment.getDownloadCacheDirectory) Để

có thể sử dụng Download Manager cần gửi yêu cầu DOWNLOAD_SERVICE:

DownloadManager downloadManager =( DownloadManager ) getSystemService(DOWNLOAD_SERVICE);

Đồng thời xin cấp quyền truy xuất Internet với install-permission sau trong Manifest:

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

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

Tiếp đến, cần gửi yêu cầu download files cho hệ thống cùng với việc cấu hình đường dẫn URI đến file sẽ download và gửi nó đến đối tượng Download Manager’s enqueue

cụ thể như dưới đây

Trang 36

Uri uri = Uri

.parse("http://developer.android.com/shareables/icon_templates-v4.0.zip");

DownloadManager Request request = new DownloadManager Request( uri );

long downloadReference = downloadManager enqueue( request );

Biến downloadReference sẽ được dùng sau này để thực hiện download, huỷ bỏ việc download hoặc kiểm tra trạng thái của quá trình download

Lưu ý: Chúng ta có thể dễ dàng thêm HTTP Header hoặc thay đổi kiểu mime type trả

về từ server bằng việc gọi request.addRequestHeader() hoặc request.setMimeType()

tương ứng Ngoài ra, chúng ta hoàn toàn có thể thiết lập điều kiện kết nối mạng cho việc

download thông qua phương thức request.setAllowedNetworkTypes() (Sử dụng Wifi

hay sử dụng Mobile network) Trong trường hợp có roaming hoặc kết nối ghép cặp diễn

ra trong lúc đang download, để đảm bảo tiến trình download không bị ảnh hưởng chúng

ta có thể sử dụng hai phương thức tương ứng: request.setAllowedOverRoaming() và phương thức request.setAllowedOverMetered() Sau khi gọi phương thức enqueue,

thì tiến trình download sẽ được diễn ra ngay sau khi điều kiện kết nối cho việc download

đó được thoả mãn

Ví dụ, nếu ta thiết lập việc download ở trên chỉ diễn ra khi mà có kết nối Wifi với câu lệnh dưới đây, thì sau khi gọi enqueue, nếu điện thoại được kết nối Wifi thì việc download sẽ diễn ra:

request setAllowedNetworkTypes( DownloadManager Request NETWORK_WIFI);

Tuy nhiên, nếu chúng ta chạy trên máy ảo thì việc download sẽ không thể diễn ra được

do máy ảo không có visual Wifi Hardware

Theo mặc định thì tiến trình download đang diễn ra hay đã hoàn thành sẽ được thiết lập qua cơ chế Notifications trong Android Việc này đòi hỏi cần thiết lập một

Broadcast Receiver nghe ngóng cho action ACTION_NOTIFICATION_CLICKED để

mỗi khi người dùng nhấn chọn một download từ Notification Tray hoặc từ một ứng dụng download thì một tín hiệu broadcast tương ứng sẽ được gửi đi trong đó có chứa một EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS extra với reference ID của đối tượng đang thực hiện download Mỗi khi tiến trình download hoàn thành, đối tượng download manager sẽ gửi tín hiệu broadcast ACTION_DOWNLOAD_ COMPLETE cùng với một EXTRA_DOWNLOAD_ID có chứa reference ID của tiến trình download

Trang 37

đó (Chương 3 sẽ đề cập đến vấn đề này) Chúng ta cũng có thể tuỳ biến cho việc

Notification cho tiến trình download:

- Thay đổi tên và mô tả cho tiến trình download

//Setting title of request

request setTitle("Data Download");

//Setting description of request

request setDescription("The file "+ fileName + " is downloading…");

- Thay đổi nơi lưu trữ files downloaded

Trong Manifest thay đổi cho phép quyền truy xuất sdcard

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

Tìm hiểu về SharedPreferences và viết đoạn chương trình dùng để lưu trữ reference

ID của mỗi tiến trình download cũng như việc lấy lại nó khi cần thiết

2.4 Câu hỏi và bài tập chương 2

1 Có những loại permission nào? Cách thực hiện của từng loại?

2 Ở bước 5, mục 2.2 trong hàm getWeatherData(String city) nếu nhập vào tên sai và không lấy được dữ liệu thì kết quả sẽ như thế nào? Để sửa lỗi này cần chuyển lệnh xoá các dữ liệu cũ trong list ở vị trí nào là thích hợp nhất?

3 Trong hàm onResponse() trong bước 5 mục 2.2 nếu bỏ câu lệnh assert weathers != null; thì vấn đề gì sẽ xảy ra?

4 Có bao nhiêu cách truy xuất mạng từ Android?

5 Tìm hiểu về Volley và so sánh việc truy xuất mạng sử dụng Retrofit và Volley?

6 Trong Android có bao nhiêu dạng permission? Điểm khác biệt của chúng là gì? Sử dụng khi nào?

Trang 38

7 Điểm khác biệt của các permission giữa các phiên bản Android khác nhau là gì? Sự khác biệt này bắt đầu từ Android API level bao nhiêu?

8 Cải tiến ứng dụng WeatherForecast trong mục 2.2 theo ý muốn của mình!

9 Phân biệt giữa Retrofit với Download Manager?

10 Sử dụng Download Manager, tìm hiểu trước về Broadcast Receiver và Notifications, xây dựng ứng dụng download files trong Android

Trang 39

CHƯƠNG 3 SỬ DỤNG CONTENT PROVIDER, BROADCAST RECEIVER,

Mô tả nội dung: Trình bày cách tạo và sử dụng Content Provider, Broadcast receiver,

service và firebase trong các ứng dụng Android

3.1 Sử dụng Intent để gửi các sự kiện Broadcast và thực hiện chúng

Intent là đối tượng quen thuộc với chúng ta trong Lập trình di động 1 dùng để start cho các Activities và truyền tham số giữa chúng với nhau (gọi tường minh) Tuy nhiên, trong Android, Intent còn được dùng cho rất nhiều trường hợp khác nữa Ví dụ có thể dùng Intent để chạy ứng dụng gọi điện thoại trong Android (gọi không tường minh):

Intent intent = new Intent( Intent ACTION_DIAL, Uri.parse("tel:0977356235"));

startActivity( intent );

Trong trường hợp này khi thực hiện câu lệnh startActivity(intent) ứng dụng không chỉ

ra rõ ràng Activity nào sẽ được thực hiện mà nó sẽ gửi một “tín hiệu broadcast” vào môi trường Android và hệ thống sẽ tự tìm kiếm và chọn ra Activity phù hợp để thực hiện Theo cơ chế này, Activity được lựa chọn để thực hiện sẽ là Activity có Intent-Filter tương thích với Intent trong lời gọi và đã được khai báo trong Manifest của ứng dụng Như trong ví dụ trên, sau lời gọi startActivity(intent) hệ thống sẽ tìm kiếm các ứng dụng

đã cài đặt trong máy xem có Activity nào được khai báo Action Dial và scheme của nó

có phải là “tel://” hay không? Nếu có hệ thống sẽ gọi thực hiện Activity đó Trong trường hợp có nhiều hơn một ứng dụng phù hợp với lời gọi, thì Android sẽ hiển thị một danh sách các ứng dụng phù hợp cho người dùng lựa chọn Ví dụ nếu thực hiện đoạn chương trình sau:

Intent intent = new Intent( Intent ACTION_SEND);

intent setType("text/plain");

intent putExtra( Intent EXTRA_TEXT, "Messages");

startActivity( intent );

Trang 40

Một danh sách các ứng dụng đã được cài đặt trong điện thoại

mà phù hợp với lời gọi sẽ được hiển thị cho người dùng lựa chọn như Gmail, Bluetooth, Copy to Clipboard, Messages, Save to Driver… (Hình 3.1.1) Lựa chọn một ứng dụng trong danh sách => Just One (Chỉ nhớ lựa chọn cho một lần chạy) hoặc Always (Android sẽ nhớ lựa chọn này cho lần chạy sau), Android sẽ gọi thực hiện ứng dụng tương ứng

Do gọi ứng dụng không tường minh nên ứng dụng gọi không biết trước ứng dụng được gọi đã cài đặt hay chưa, nên cần có

cơ chế kiểm tra tính khả dụng của nó Xem đoạn lệnh sau:

Hình 3.1.1 Android tìm và đưa ra danh sách ứng dụng

//Check if the app is availble or not

PackageManager packageManager = getPackageManager();

ComponentName componentName = intent resolveActivity( packageManager );

if ( componentName == null) {

Log.d("pickPerson", "The Activity must be installed before or it must declare

the right intent-filter"); }

<intent-filter> sẽ chứa ba thành phần đó là action, category và data Trong đó:

- action: Sử dụng android:name để chỉ ra tên của action sẽ được thực hiện Mỗi

<intent-filter> bao giờ cũng phải chứa ít nhất một action và mỗi action phải là một chuỗi duy nhất trong hệ thống Action thường đã được Android định nghĩa sẵn, tuy nhiên chúng ta cũng có thể tự định nghĩa action riêng của mình

- category: Cũng sử dụng android:name để chỉ ra ngữ cảnh mà action sẽ được thực hiện

Mỗi khối <intent-filter> có thể chứa nhiều <category> khác nhau và chúng cũng có thể được định nghĩa bởi lập trình viên

Ngày đăng: 18/03/2023, 08:15

TÀI LIỆU CÙNG NGƯỜI DÙNG

TÀI LIỆU LIÊN QUAN

🧩 Sản phẩm bạn có thể quan tâm