Thứ Ba, 19 tháng 11, 2019

Sử dụng Vagrant

Công cụ vagrant là gì


Vagrant là một công cụ tương tác với các nền tảng ảo hóa như VirtualBox, HyperV, VM ..., nó giúp tạo và quản lý các máy ảo trên các nền tảng đó. Vagrant cung cấp một cấu hình đơn giản tạo và quản lý, tương tác với máy ảo mà không có nhiều sự khác biệt dù bạn đang sử dụng VirtualBox, VM hay HyperV
Vagrant

Cài đặt Vagrant

Vagrant làm việc với một nền tảng ảo hóa nào đó như VirtualBox, HyperV, VM ... nên bạn cũng cần có trên hệ thống của mình, ví dụ cần cài đặt VirtualBox nếu dùng nền tảng này, ở phần này coi như bạn đã đang có VirtualBox hay HyperV trên hệ thống

Cài đặt vagrant trên Windows / macOS

Tải Vagrant đúng bản dành cho Windows (32 hay 64bit) hay macOS tại Tải Vagrant, sau đó chạy bộ cài đặt, có thể cần khởi động lại máy
Trên Windows có thể dùng PowerShell hoặc nếu muốn cài GitBash để có môi trường dòng lệnh tương tác với Vagrant. Sau đó chạy lệnh để kiểm tra vagarant đang có trên hệ thống:
vagrant version

Cài đặt Vagrant trên Ubuntu

sudo apt-get install vagrant
Cũng kiểm tra bằng lệnh:
vagrant version

Sử dụng Vagrant tạo và quản lý máy ảo Linux

Cách sử dụng thông thường là khai báo các máy ảo trong một file cấu hình có tên là Vagrantfile, quy tắc viết cấu hình ở dưới, nhưng trước tiên cần xác định máy ảo đó chạy hệ điều hành gì ... để file cấu hình khởi tạo máy ảo, các nguồn để khởi tạo máy ảo được gọi là các Box, bạn cần tìm Box muốn sử dụng tại: Tìm Box Vagrant
Ví dụ, muốn khởi tạo một máy ảo từ hệ điệu hành CentOS, thì có thể tìm và chọn Box có tên centos/7 nó phù hợp cho cả VirtualBox và HyperV. Muốn một máy ảo Ubuntu có thể chọn Box có tên ubuntu/trusty64 Box này lại chỉ phù hợp với Virtual Bõ. Tương tự như vậy có thể tìm kiếm chọn ra Box phù hợp! Hướng dẫn này giả sử sẽ tạo một máy ảo chạy CentOS/7, dùng nền tảng VirtualBox (trên máy phải cài đặt sẵn VirualBox).
Tạo một thư mục dự án đạt tên là vagrant-exam, từ dòng lệnh (terminal, powershell ...) vào thư mục đó gõ lệnh sau để nó sinh ra file cấu hình Vagrantfile:
vagrant int
vagrant
Sau lệnh này, nó sinh ra file Vagrantfile trong đó có chứa nội dung hướng dẫn cấu hình theo mẫu. Nội dung file đó có dạng:
# -*- mode: ruby -*-
# vi: set ft=ruby :
# All Vagrant configuration is done below. The "2" in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what
# you're doing.
Vagrant.configure("2") do |config|
# The most common configuration options are documented and commented below.
# For a complete reference, please see the online documentation at
# https://docs.vagrantup.com.
# Every Vagrant development environment requires a box. You can search for
# boxes at https://vagrantcloud.com/search.
config.vm.box = "base"
# Disable automatic box update checking. If you disable this, then
# boxes will only be checked for updates when the user runs
# `vagrant box outdated`. This is not recommended.
# config.vm.box_check_update = false
# Create a forwarded port mapping which allows access to a specific port
# within the machine from a port on the host machine. In the example below,
# accessing "localhost:8080" will access port 80 on the guest machine.
# NOTE: This will enable public access to the opened port
# config.vm.network "forwarded_port", guest: 80, host: 8080
# Create a forwarded port mapping which allows access to a specific port
# within the machine from a port on the host machine and only allow access
# via 127.0.0.1 to disable public access
# config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1"
# Create a private network, which allows host-only access to the machine
# using a specific IP.
# config.vm.network "private_network", ip: "192.168.33.10"
# Create a public network, which generally matched to bridged network.
# Bridged networks make the machine appear as another physical device on
# your network.
# config.vm.network "public_network"
# Share an additional folder to the guest VM. The first argument is
# the path on the host to the actual folder. The second argument is
# the path on the guest to mount the folder. And the optional third
# argument is a set of non-required options.
# config.vm.synced_folder "../data", "/vagrant_data"
# Provider-specific configuration so you can fine-tune various
# backing providers for Vagrant. These expose provider-specific options.
# Example for VirtualBox:
#
# config.vm.provider "virtualbox" do |vb|
# # Display the VirtualBox GUI when booting the machine
# vb.gui = true
#
# # Customize the amount of memory on the VM:
# vb.memory = "1024"
# end
#
# View the documentation for the provider you are using for more
# information on available options.
# Enable provisioning with a shell script. Additional provisioners such as
# Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the
# documentation for more information about their specific syntax and use.
# config.vm.provision "shell", inline: <<-SHELL
# apt-get update
# apt-get install -y apache2
# SHELL
end
view rawVagrantfile hosted with ❤ by GitHub
Trong file trên chứa các hướng dẫn cấu hình, đa số ở trạng thái comment, nếu muốn cấu hình phần nào thì bỏ comment (xóa #) ở phần đó. Ở đây bạn thay nội dung cấu hình thành như sau để tạo máy ảo centos/7 - ý nghĩa có giải thích ở comment sau mỗi cấu hình
Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure(2) do |config|                # Bắt đầu khai báo máy ảo
  config.vm.box = 'centos/7'                    # Sử dụng Box centos/7 tạo máy ảo

  config.vm.provider "virtualbox" do |vb|       # Máy ảo dùng nền tảng virtualbox, với các cấu hình bổ sung thêm cho provider
     vb.name = "may-ao-01"                      # đặt tên máy ảo tạo ra
     vb.cpus = 2                                # cấp 2 nhân CPU
     vb.memory = "2048"                         # cấu hình dùng 2GB bộ nhớ
  end                                           # hết cấu hình provider

end                                             #  hết cấu hình tạo máy ảo

vagrant up

Thi hành tạo máy ảo: gõ với lệnh vagrant up, lệnh này sẽ tạo máy ảo (cập nhật) được cấu hình trong Vagrantfile, nếu mở VirtualBox Manager sẽ thấy tên và trạng thái máy ảo này. Ở đây là máy ảo có tên may-ao-01
vagrant
Nhìn vào các thông báo quá trình tạo máy ảo, mặc định nó sẽ chia sẻ thư mục chứa file Vagrantfile ở máy host vào máy ảo ở đường dẫn /vagrant của máy ảo. Có nghĩa tại máy ảo truy cập vào thư mục /vagrant/ thì trong đó chính là dữ liệu trong thư mục có file Vagrantfile của máy host

vagrant ssh

Khi máy ảo đang chạy, vẫn đang ở dòng lệnh tại thư mục chứa file Vagrant để kết nối đến máy ảo bằng giao thức ssh gõ lệnh sau:
vagrant ssh
Bạn sẽ đăng nhập vào máy ảo với tài khoản user có tên là vagrant, từ tài khoản này nếu muốn chuyển sang root gõ lệnh:
sudo -i
vagrant

Lệnh vagrant hay dùng

vagrant initSinh file cấu hình máy ảo mới Vagrantfile
vagrant upThực hiện tạo / hoặc chạy máy ảo với cấu hình từ Vagrantfile
vagrant sshKết nối ssh vào máy ảo, tài khoản kết nối là vagrant
vagrant haltDừng máy ảo (shutdown)
vagrant reloadKhởi động lại máy ảo, có đọc lại cấu hình trong Vagrantfile
vagrant destroyXóa máy ảo

Đồng bộ thư mục

Mặc định khi chạy máy ảo, nó đã đồng bộ qua lại giữa thư mục chứa file Vagrantfile vào thư mục /vagrant/ của máy ảo. Nếu muốn cấu hình đồng bộ sử dụng config.vm.synced_folder, ví dụ cần đồng bộ thư mục máy host hiện tại . vào thư mục /data/mydata/ của máy ảo
config.vm.synced_folder '.', '/data/mydata/'
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure(2) do |config|                # Bắt đầu khai báo máy ảo
  config.vm.box = 'centos/7'                    # Sử dụng Box centos/7 tạo máy ảo

  config.vm.synced_folder '.', '/data/mydata/'  # Chia sẻ thư mục máy host và máy ảo

  config.vm.provider "virtualbox" do |vb|       # Máy ảo dùng nền tảng virtualbox, với các cấu hình bổ sung thêm cho provider
     vb.name = "may-ao-01"                      # đặt tên máy ảo tạo ra
     vb.cpus = 2                                # cấp 2 nhân CPU
     vb.memory = "2048"                         # cấu hình dùng 2GB bộ nhớ
  end                                           # hết cấu hình provider

end                                             #  hết cấu hình tạo máy ảo
Sau khi sửa file cấu hình, nạp lại máy ảo bằng vagrant reload
Nếu có lỗi có thể cần cài đặt Plugin vagrant-vbguest
vagrant plugin install vagrant-vbguest

Forward cổng máy ảo ra host

Nếu muốn chuyển cổng từ máy ảo ra máy host, ví dụ cổng máy ảo là 80 ra cổng máy host 8080 (có nghĩa là từ máy host truy cập cổng 8080 - locahost:8080 - thì có nghĩa là truy cập cổng 80 của máy ảo)
config.vm.network "forwarded_port", guest: 80, host: 8080
Ngoài ra bạn cũng có thể thiết lập cho máy ảo có cấu hình với địa chỉ IP do bạn chỉ định và NAT giúp máy host (các máy khác trong LAN) truy cập đến địa chỉ này của máy ảo mà không cần forward cổng.
config.vm.network "private_network", ip: "192.168.10.155"
Với cấu hình trên, thì địa chỉ máy ảo là 192.168.10.155, bạn có thể truy cập đến các cổng của máy ảo với địa chỉ IP này, ví dụ http://192.168.10.155 (tức cổng 80)

Provision - chạy lệnh khi tạo máy ảo

Trong quá trình tạo máy ảo, sau khi nạp Box, bạn có thể chạy các lệnh, các script của hệ điều hành, nếu chạy một script từ file myscript.sh thì cấu hình là:
config.vm.provision "shell", path: "./myscript.sh"
Cũng có thể cấu hình chạy trực tiếp các lệnh, ví dụ:
config.vm.provision "shell", inline: <<-SHELL
    # các lệnh cần chạy
SHELL
Ví dụ sau tạo máy ảo CentOS, cài Apache, PHP:
Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure(2) do |config|                # Bắt đầu khai báo máy ảo
  config.vm.box = 'centos/7'                    # Sử dụng Box centos/7 tạo máy ảo

  config.vm.network "private_network", ip: "192.168.10.55"   # Lập IP cho máy ảo
  config.vm.hostname = "master.xtl"             # Đặt hostname cho máy ảo

  config.vm.synced_folder '.', '/var/www/public/' # Chia sẻ thư mục máy host và máy ảo

  config.vm.provider "virtualbox" do |vb|       # Máy ảo dùng nền tảng virtualbox, với các cấu hình bổ sung thêm cho provider
     vb.name = "may-ao-01"                      # đặt tên máy ảo tạo ra
     vb.cpus = 2                                # cấp 2 nhân CPU
     vb.memory = "2048"                         # cấu hình dùng 2GB bộ nhớ
  end                                           # hết cấu hình provider

# Chạy các lệnh cài đặt
config.vm.provision "shell", inline: <<-SHELL
    # cài đặt Apache, PHP
    yum update -y
    yum install httpd php -y
    systemctl start httpd
    systemctl enable httpd

    # Tat SELinux cua CentOS
    setenforce 0
    sed -i --follow-symlinks 's/^SELINUX=enforcing/SELINUX=disabled/' /etc/sysconfig/selinux


    # Đổi root password thành 123 và cho phép login SSH qua root
    echo "123" | passwd --stdin root
    sed -i 's/^PasswordAuthentication no/PasswordAuthentication yes/' /etc/ssh/sshd_config
    systemctl reload sshd

    # Tạo file cấu hình vhost lưu vào /etc/httpd/conf.d/vhost.conf để Apache nạp
    echo '<VirtualHost *:80>
      DocumentRoot /var/www/public
      AllowEncodedSlashes On

      <Directory /var/www/public>
        Options +Indexes +FollowSymLinks
        DirectoryIndex index.php index.html
        Order allow,deny
        Allow from all
        AllowOverride All
        </Directory>
    </VirtualHost>' > /etc/httpd/conf.d/vhost.conf
    systemctl start httpd
SHELL

end                                             #  hết cấu hình tạo máy ảo
Xóa máy ảo cũ, thực hiện vagrant up tạo lại máy ảo. Sau khi tạo máy ảo có thể cần khởi động lại (reload) để cấu hình như Selinux, Apache có hiệu lực
Tạo file index.php ở thư mục dự án với nội dung:
<?php
    echo "Hello World!";
    phpinfo();
Giờ truy cập http://192.168.10.55
vagrant
Thứ Tư, 6 tháng 3, 2019

Thuộc tính style trong các View lập trình Android

Chọn cách thiết lập thuộc mềm dẻo hơn cho các View

Những thuộc tính trên các View ảnh hướng đến sự hiện thị của nó như kích thước, màu sắc có thể gán trực tiếp giá trị mong muốn trong layout xml. Ví dụ TextView sau gán màu #DD2C00, khoảng padding là 10dp, khoảng margin là 8dp
 <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"

        android:textColor="#DD2C00"
        android:padding="10dp"
        android:layout_margin="8dp"  />
Code trình bày trên với cách nhập cố định các giá trịnh thuộc tính như vậy vẫn có được giao diện theo yêu cầu, nhưng có một bất lợi về tính mềm dẻo, khi bạn cần thay đổi giá trị bạn cần tìm tất cả các thuộc tính này và thay đổi từng cái một.
Giải pháp là các giá trị thuộc tính đó được gộp lại ở một file tài nguyên xml như colors.xml để định nghĩa các giá trị màu, dimens.xml để định nghĩa các kích thước ..., làm điều ngoài việc tập trung các giá trị thuộc tính lại một chỗ để dễ quản lý nó còn có lợi ích khi nhiều phần tử View sử dùng cùng giá trị thuộc tính nào đó thì ta chỉ việc thay đổi giá trị này khi cần thiết.
Ví dụ:
values/colors.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="mytextviewcolor">#DD2C00</color>
</resources>
values/dimens.xml
<resources>
    <dimen name="mytextviewpadding">10dp</dimen>
    <dimen name="mytextviewmargin">8dp</dimen>
</resources>
Lúc này quay trở lại layout, áp dụng các giá trị này sẽ là:
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Hello World!"

    android:textColor="@color/mytextviewcolor"
    android:padding="@dimen/mytextviewpadding"
    android:layout_margin="@dimen/mytextviewmargin"  />

Thuộc tính style cho các View

Cách trên đã mang lại sự mềm dẻo đáng kể, tuy nhiên còn có cách thức mạnh mẽ hơn đó là gộp nhiều thuộc tính lại một chỗ trong file styles.xml, sau đó áp dụng vào View - Ví dụ định nghĩa một style có tên mytextviewstyle, trong nó có định nghĩa nhiều thuộc tính có thể gán vào View như: layout_with, textcolor ...
values/styles.xml
<resources>
    <style name="mytextviewstyle">
        <item name="android:layout_width">wrap_content</item>
        <item name="android:layout_height">wrap_content</item>
        <item name="android:textColor">@color/mytextviewcolor</item>
        <item name="android:layout_margin">@dimen/mytextviewmargin</item>
        <item name="android:padding">@dimen/mytextviewpadding</item>
    </style>
</resources>
Giờ muốn View nào áp dụng style này chỉ việc gán cho nó thông qua thuộc tính android:style, ví dụ trên trở thành:
<TextView 
    android:text="Hello World!" 
    style="@style/mytextviewstyle"/>
Sự linh hoạt và tập trung hoá cách sử dụng này được khuyến khích sử dụng, nhất là khi ứng dụng có độ phức tạp đáng kể nó sẽ trở lên rất quan trong vì tiết kiệm thời gian, dễ dàng giám sát, dễ dàng chỉnh sửa cập nhật. Hãy nhìn lại cách tạo ra TextView 3 trường hợp trên để nhận ra ưu điểm của nó:
<!--Nhập giá trí cố định, kém linh hoạt -->
 <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"

        android:textColor="#DD2C00"
        android:padding="10dp"
        android:layout_margin="8dp"  />
        
<!--Giá trị các thuộc tính được tập trung một nơi và sử dụng khi cần-->
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Hello World!"

    android:textColor="@color/mytextviewcolor"
    android:padding="@dimen/mytextviewpadding"
    android:layout_margin="@dimen/mytextviewmargin"  />

<!--Các thuộc tính và giá trị của nó được gộp lại thành các style, cách ưu điểm nhất-->
<TextView 
    android:text="Hello World!" 
    style="@style/mytextviewstyle"/>
Thứ Năm, 14 tháng 2, 2019

Sử dụng Broadcast Intent BroadcastReceiver trong Android

Khái quát về Broadcast Intent

Các Broadcast Intent là các đối tượng (có chứa tên hành động - action - và dữ liệu) được lan truyền trong ứng dụng thông qua qua goi các phương thức của Activity là: sendBroadcast()sendOrderedBroadcast(). Khi đối tượng Broadcast Intent được tạo và gửi đi như vậy thì các ứng dụng trong hệ thống hoặc ở một phần khác của ứng dụng có thể nhận được đối tượng này nếu có đăng khi nhận bằng cách sử dụng Broadcast Receiver (Xem phần sau).
Ví dụ sau, tạo ra một Broadcast Intent sau đó gửi đi (lan truyền) trong ứng dụng:
String actionName = "my-first-broadcastintent";
Intent intent = new Intent(actionName);
//Thiết lập tên để cho Receiver nhận được thì biết đó là loại Intent
intent.setAction(actionName);
//Dữ liệu gắn vào Intent thiết lập bằng putExtra với (tên, dữ liệu), dữ liệu là
//các kiểu cơ bản Int, String ... hoặc các loại đối tượng lớp kế thừa từ Serializable
intent.putExtra("dataname", "Hello, How are you?");
//Thực hiện lan truyền Intent trong hệ thống
sendBroadcast(intent);

Gửi Broadcast Intent

Trong Activity có các phương thức để lan truyền đối tượng Broadcast Intent, tuỳ mục đích mà sử dụng một phương thức phù hợp
Phương thứcSử dụng
sendBroadcast(Intent intent)Gửi đi intent mà tất cả các BroadcastReceiver sẽ nhận được. Việc sử dụng phương thức này sẽ thực hiện luồng bất đồng bộ, nó sẽ trả về ngay lập tức và logic code sau lời gọi tiếp tục chạy còn phía nhận (BroadcastReceiver) chạy một quá trình khác khi nhận được intent - chúng nhận được theo một thứ tự không xác định thường là cùng lúc, tất nhiên bạn sẽ không nhận được kết quả trả về nào từ BroadcastReceiver và BroadcastReceiver cũng không thể ngăn chặn quá trình lan truyền.
sendOrderedBroadcastsendOrderedBroadcast (Intent intent, String receiverPermission) - việc lan truyền intent sẽ theo thứ tự, gửi đến từng BroadcastReceiver, thứ tự này có thể điều chỉnh qua thuộc tính android:priority tương tứng khi khai báo intent-filter, khi các BroadcastReceiver thi hành nó có thể truyền kết quả của của đến một BroadcastReceiver khác thậm chí ngắt luôn quá trình làn truyền intent để các BroadcastReceiver không nhận được intent.

Khái quát Broadcast Receiver

Các Broadcast Receiver là các đối tượng đăng ký với ứng dụng của bạn, nó có chức năng lắng nghe - chờ các Broadcast Intent gửi đến (Intent từ ứng dụng, từ ứng dụng khác, từ hệ thống Android), khi nhận được Intent nó thi hành các chức năng do bạn xây dựng.
Để xây dựng một Receiver tạo ra đối tượng từ lớp BroadcastReceiver hoặc từ các lớp tự xây dựng kế từ từ BroadcastReceiver, sau đó đăng ký nó với hệ thống bằng phương thức registerReceiver của Activity
Ví dụ sau tạo ra một lớp kế thừa từ BroadcastReceiver, bao giờ cũng phải triển khai phương thức onReceive của lớp này.
class MyBroadcastReceiver extends BroadcastReceiver {

    public static final String ACTION_FIRST_ACTION  = "my-first-broadcastintent";

    MyBroadcastReceiver() {
    }
    //Code thi hành khi Receiver nhận được Intent
    @Override
    public void onReceive(Context context, Intent intent) {

        //Kiểm tra Action của Intent nhận được có tên irst-broadcastintent
        if (intent.getAction().equals(MyBroadcastReceiver.ACTION_FIRST_ACTION)) {
            //Đọc dữ liệu trong Intent
            String d = intent.getStringExtra("dataname");
            Toast.makeText(context, d, Toast.LENGTH_SHORT).show();
        }
    }
}

Đăng ký BroadcastReceiver

Tạo ra đối tượng lớp MyBroadcastReceiver và đăng ký nó với hệ thống:
MyBroadcastReceiver myBroadcastReceiver = new MyBroadcastReceiver();

IntentFilter intentFilter = new IntentFilter();

//Thiết lập các Action filter mà MyBroadcastReceiver sẽ nhận
intentFilter.addAction(MyBroadcastReceiver.ACTION_FIRST_ACTION);
registerReceiver(myBroadcastReceiver, intentFilter);

Huỷ Đăng ký BroadcastReceiver

Khi BroadcastReceiver không còn sử dụng nữa, cần thiết phải huỷ đăng ký nó khỏi hệ thống bằng phương thức unregisterReceiver, thường làm điều này trong onDestroy của Activity
@Override
protected void onDestroy() {
    super.onDestroy();
    unregisterReceiver(myBroadcastReceiver);
}
Ứng dụng ví dụ:
Intent Android
Xem đoạn code tại: MainActivity.java hoặc tải ví dụ mẫu tại: android-broadcastintent

Lắng nghe một số Intent của hệ thống Android

Có một số sự kiện hệ thống Android xảy ra như thay đổi kết nối mạng (mất mạng, có mạng), khởi động máy, tắt máy ..., nếu muốn bắt được các sự kiện đó hoàn toàn có thể dùng BroadcastReceiver như trên và đăng ký nhận Intent với Intent Filter có Action phù hợp, danh sách đầy đủ các Action xem tại: broadcast_actions và broadcast_actions
Một số Action quan trọng như:
  • android.intent.action.BATTERY_CHANGED (Intent.ACTION_BATTERY_CHANGED) thay đổi trạng thái PIN
  • android.intent.action.BATTERY_LOW (Intent.ACTION_BATTERY_LOW) PIN ở mức thấp
  • android.intent.action.BATTERY_OKAY (Intent.ACTION_BATTERY_OKAY) PIN vào trạng thái tốt (sạc lên mức cao)
  • android.intent.action.BOOT_COMPLETED (Intent.ACTION_BOOT_COMPLETED) Khởi động xong máy
  • android.intent.action.CALL_BUTTON (Intent.ACTION_CALL_BUTTON) bấm nút gọi
  • android.intent.action.DATE_CHANGED (Intent.ACTION_DATE_CHANGED) ngày thay đổi
  • android.intent.action.REBOOT (Intent.ACTION_REBOOT) khởi động máy
Ví dụ sau lắng nghe sự kiện khi PIN thay đổi (tăng, giảm):
MyBroadcastReceiver myBroadcastReceiver = new MyBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
registerReceiver(myBroadcastReceiver, intentFilter);

Danh mục Gửi liên hệ

Loading...