Pengalaman menggunakan Asus Zenfone Selfie ZD551KL

Setelah Nexus 5 saya akhirnya tewas (terkena air laut, bisa diperbaiki tapi akhirnya satu demi satu komponennya rusak), saya mulai mencari-cari alternatif.  Mengalami tombol power nexus 5 yang bermasalah dan faktor lain, saya tidak mau beli lagi yang terlalu mahal. Percuma juga.

Kriteria yang saya perlukan:

  1. Dual SIM 4G yang support freq 900 dan 1800
  2. Kamera lumayan.  Jangan sampai seperti Android One yang akhirnya kameranya benar-benar tidak bisa digunakan.
  3. Memori >2GB
  4. Ukuran layar besar
  5. Harga semurah mungkin. Tidak terlalu terkesan dengan LG G3 istri dan Nexus5 yang relatif mahal tapi ternyata punya masalah juga.

Dari semua alternatif, saya akhirnya pilih Asus Zenfone Selfie ZD551KL. Padahal saya jarang sekali selfie hehe. Cuma memang ini yang memenuhi kriteria yang saya inginkan 🙂  Untungnya ada warna hitam, sempat khawatir harus beli yang pink.

Setelah beberapa lama menggunakan, menurut saya kualitasnya diatas ekspektasi:

  1. Setelah 6 bulan menggunakan. Fitur batere Asus ini yang paling top! Lebih bagus daripada semua HP yang saya gunakan. Dengan 3000mAh ternyata jauh awet dari dugaan saya. Saya tidak pernah kehabisan daya di siang hari walaupun digunakan habis-habisan (seperti biasa, malam saat saya tidur dicharge). Removable juga baterenya. Asus menyediakan mobile manager app yang membuat app tidak dapat berjalan di background. Ini membuat kinerja jadi bagus (lebih cepat, lebih irit memori dan batere).
  2. Kualitas layar yang bagus: besar, resolusi bagus dan terang. Saya menggunakan app Splendid bawaan dari Asus agar warna di layar lebih sesuai.
  3. Gorilla glass 4 + oleophobic sangat bermanfaat. Layar jauh lebih bersih dan belum ada baret sampai sekarang.
  4. Kamera lumayan. Agak dibawah espektasi sih, terutama untuk foto dengan pencahayaan kurang. Ternyata laser autofocus hanya cocok sampai jarak 50cm. Jadi untuk foto makro bagus, tapi untuk foto biasa tidak terlalu berpengaruh. Bagusnya ada fungsi manual yang dapat diutak atik.
  5. Banyak bloatware dari Asus, tapi untungnya bisa di-uninstall atau di-disable atau dikurangi konsumsi memorinya dengan autostart manager. Setelah dipangkas habis-habisan, rata-rata memori yang tersisa 1GB.

Dengan harga 3 jt pas, saya puas dengan HP ini.

Membuat App Bluetooth di Android (3): Komunikasi Antar Devices

Bagian ini tingkat kesulitannya cukup tinggi. Sebelumnya diharapkan pembaca telah memahami prinsip-prinsip thread (lihat kembali tutorialnya). Penting: Tutorial ini akan menggunakan project dari tutorial tentang Bluetooth sebelumnya.

Bluetooth menggunakan prinsip master-slave atau client server, dan dapat bertukar posisi. Satu server dapat terhubung sampai dengan tujuh client. Dalam tutorial ini kita akan membuat satu app dapat berfungsi sebagai server maupun sebagai client. Sebaiknya gunakan dua HP dan hubungkan keduanya dengan laptop dalam mode USB debugging, sehingga bisa langsung dicoba. Setiap dicompile, deploy ke kedua device, lalu satu menjadi server dan satu menjadi client.

ui_bluetooth_client_server

Supaya lebih jelas, video eksekusi app dapat dilihat di:[youtube.com]

Tahapan di client adalah sebagai berikut:

  1. Aktifkan BT jika dalam kondisi off
  2. Tampilkan daftar device yang sudah dipair dalam listview
  3. Jika server belum ada (belum pernah dipair sebelumnya) tekan tombol “Start Discovery” → listview akan bertambah dengan device baru. Jika sudah ada, langsung ke langkah berikutnya.
  4. User men-tap device server yang dikehendaki di listview. Jika belum di-pair maka dialog untuk pairing akan otomatis muncul.
  5. Setelah terhubung, user menekan button untuk mengirim data.

Tahap 1 sampai 3 sudah dilakukan di tutorial sebelumnya.

Tahapan di server adalah sebagai berikut:

  1. Aktifkan BT jika dalam kondisi off
  2. Nyalakan mode discoverablity, ini wajib jika client-server belum di-pair sebelumnya. Karena kalau tidak, maka server tidak akan terlihat oleh client.
  3. Tunggu sampai ada client yang terhubung.
  4. Jika ada client yang terhubung, terima data dan tampilkan di user interface.

Akan ada tiga buah thread:

  1. Thread di server untuk menerima koneksi dari client
  2. Thread di client untuk menghubungi server.
  3. Thread di server dan client untuk berkomunikasi data.

 

 

Kita akan mulai dari sisi server. Buka kembali project pada tutorial sebelumnya. Project tersebut akan kita jadikan client dan server.

Pertama tambahkan button, untuk mengenable server. Jika user men-tap button ini, maka device akan masuk ke mode discoverable dan masuk ke loop untuk menerima koneksi.

Tambahkan aksi saat user mentap tombol enable server sebagai berikut:

public static final int REQUEST_ENABLE_DISCOVERY=998;//konstanta

//saat server di enable
public void klikServer(View v) {

   // nyalakan discoverable, agar client dapat melihat (jika belum pair sebelumnya)
   // otomatis akan menyalakan bluetooth
   // hanya untuk server, kalau sebagai client tidak perlu

   Intent discoverableIntent = new
           Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
   discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
   startActivityForResult(discoverableIntent, REQUEST_ENABLE_DISCOVERY);
   //hasil ankan ditangkap di onActivityResult
}

Akan muncul dialog seperti ini

dialog_ijin_phone_visible

Saat user selesai mengenable discoverable, maka onActivityResult akan dipanggil. Pada onActivityResult kita akan mulai menerima koneksi (tambahan code ditunjukkan dengan panah

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
   if (requestCode == REQUEST_ENABLE_BT) {
       String pesan;
       if (resultCode == RESULT_OK) {
           //user menyalakan bluetooth
           pesan = "Bluetooth dinyalakan";
           tampilkanPaired();  
       } else {
           // user deny
           pesan = "Bluetooth tidak mau dinyalakan user!";
       }
       Toast toast = Toast.makeText(getApplicationContext(), pesan, Toast.LENGTH_LONG);
       toast.show();
   } else {
       // <----------------------  untuk server
       //service discovery sudah nyala, lanjutkan dengan menerima koneksi
       if (requestCode == REQUEST_ENABLE_DISCOVERY) {
           if (resultCode != RESULT_CANCELED) {
               //OK, nyala sekarang, panggil method server untuk menerima koneksi
               server();
           }
       }
   }
}

Sekarang untuk menerima koneksi dari client. Pertama siapkan UUID, UUID adalah id yang dianggap akan unik (karena random dan ukurannya yang besar). UUID berfungsi untuk menghubungkan app server dan client. Gunakan situs seperti www.uuidgenerator.net untuk mendapatkan UUID. Ganti UUID di contoh dibawah.

public static final String NAME ="COBASERVER"; //service name
//ganti UUID ini! gunakan generator spt www.uuidgenerator.net
public UUID MY_UUID=UUID.fromString("eb5a63b9-a32b-4ca9-896c-7204f3bfc55b");
 

Karena proses menerima koneksi dilakukan dalam loop, maka tidak mungkin diakukan di thread utama (akan menyebabkan “app not responsive”). Oleh karena itu kita perlu membuat thread terpisah. Buat kelas AcceptThread seperti berikut dan letakkan di kelas MainActivity. Method manageConnectedSocket akan dibuat kemudian.

/**
*    Untuk server.
*    Thread berisi loop sampai menerima hubungan dari client
*/
private  class AcceptThread extends Thread {

       private BluetoothServerSocket mmServerSocket=null;
       
       public AcceptThread() {
           //constructor
           //siapkan socket
           mmServerSocket = null;
           try {
               mmServerSocket = 
                     mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
           } catch (IOException e) {
               Log.e("ywlog",e.getMessage());
               e.printStackTrace();
           }
       }

       public void run() {
           BluetoothSocket socket = null;
           // loop sampai socket terisi (tidak null)
           while (true) {
               try {
                   socket = mmServerSocket.accept();
               } catch (IOException e) {
                   Log.e("ywlog",e.getMessage());
                   break;
               }
               // ada koneksi yang diterima dari client
               if (socket != null) {
                   //proses di thread terpisah
                   manageConnectedSocket(socket);

                   //karena bluetooth cuma menerima satu channel, close saja
                   try {
                       mmServerSocket.close();
                   } catch (IOException e) {
                       Log.e("ywlog",e.getMessage());
                       e.printStackTrace();
                   }
                   break;
               }
           }
       }

       /**  Untuk mematikan koneksi server */
       public void cancel() {
           try {
               mmServerSocket.close();
           } catch (IOException e) {
               Log.e("ywlog",e.getMessage());
               e.printStackTrace();
           }
       }

}


public void server()  {
   //mulai jalankan thread server
   (new AcceptThread()).start();
}

Jika socket sudah terhubung, maka akan dipanggil method manageConnecteSocket, dalam method ini akan dipanggil thread yang menangani komunikasi antara server dan client. Artinya thread ini juga nanti akan digunakan untuk bagian client. Buat thread bernama ConnectedThread dan letakkan di kelas MainActivity.

ConnectedThread ct;

/*
  saat server dan client sudah terhubung, thread yang menangani komunikasi data
*/

private class ConnectedThread extends Thread {
   private BluetoothSocket mmSocket;
   private InputStream mmInStream;
   private OutputStream mmOutStream;
   private String strData;

   public ConnectedThread(BluetoothSocket socket) {
       //siapkan input dan output stream
       mmSocket = socket;
       InputStream tmpIn = null;
       OutputStream tmpOut = null;

       //ambil input dan output stream
       try {
           mmInStream = socket.getInputStream();
           mmOutStream = socket.getOutputStream();
       } catch (IOException e) {
           Log.e("ywlog", e.getMessage());
           e.printStackTrace();
       }
   }

   public void run() {
       byte[] buffer = new byte[1024];  // buffer stream
       int bytes; // jumlah bytes yang dibaca read()

       // SERVER: mendengarkan InputStream
       while (true) {
           try {
               bytes = mmInStream.read(buffer);
               if (bytes>0) {
                   //ada data dari client!
                   
                   String tempS = new String(buffer);
                   strData = tempS;
                   //tampilkan ke textview, harus dengan post 
                   //karena ini thread yg terpisah dengan thread UI
     tvHasil.post(new Runnable() {
                       public void run() {
                           tvHasil.setText(strData);
                       }
                   });
               }
           } catch (IOException e) {
               Log.e("ywlog",e.getMessage());
               e.printStackTrace();
               break;
           }
       }
   }

   
   /* Menulis ke server (string) */
   public void write(String data) {
       //konversi string ke stream
       try {
           mmOutStream.write(data.getBytes());
       } catch (IOException e) {
          e.printStackTrace();
          Log.e("ywlog",e.getMessage());
       }
   }


   /* matikan connection */
   public void cancel() {
       try {
           mmSocket.close();
       } catch (IOException e) {
           Log.e("ywlog",e.getMessage());
           e.printStackTrace();
       }
   }
}


//digunakan baik oleh server maupun client
private void manageConnectedSocket(BluetoothSocket socket) {
   //socket terhubung, mulai terima bagi server dan kirim data bagi client
   ct = new ConnectedThread(socket);
   ct.start();
}

Sekarang kita akan membuat bagian client. Untuk client, user melakukan discovery jika server belum di-pair. Lalu memilih server dari ListView. Jika sudah terhubung. User mentap button untuk mengirim data.

Di onCreate tambahkan code untuk Listview. Jika ditap maka thread client akan dijalankan dengan parameter BlueTooth device server. Thread ini berfungsi menghubungi server.

//siapkan onclik pada listview
//artinya client menghubungi server
lvServer = (ListView) findViewById(R.id.listView);
lvServer.setClickable(true);
lvServer.setOnItemClickListener(new AdapterView.OnItemClickListener() {
   @Override
   public void onItemClick(AdapterView<?>; arg0, 
                           View arg1, int position, long arg3) {
       BluetoothDevice devicePilih = arrayPairedDevices.get(position);
        //devicePilih berisi server yang dipilih di listview
        client(devicePilih); 
   }
});

Code untuk client adalah sebagai berikut. Buat kelas ConnectThread yang juga diletakkan di kelas MainActivity.

/*
   thread untuk client

*/
private class ConnectThread extends Thread {
   private BluetoothSocket mmSocket;
   private BluetoothDevice mmDevice;

   //bluetoothdevice didapat dari proses device discovery atau dari daftar
   public ConnectThread(BluetoothDevice device) {
       mmDevice = device;
        //ambil socket
       try {
          mmSocket = device.createRfcommSocketToServiceRecord(MY_UUID);
       } catch (IOException e) {
           Log.e("ywlog",e.getMessage());
       }
   }

   public void run() {
       //Jika discovery sedang jalan, batalkan karena memperlambat koneksi
       mBluetoothAdapter.cancelDiscovery();
       try {
           //terhubung ke server
           mmSocket.connect();
       } catch (IOException connectException) {
           try {
               mmSocket.close();
           } catch (IOException e) {
              Log.e("ywlog",e.getMessage());
              e.printStackTrace();
           }
           return;
       }

       //terhubung, tangani kirim data
       manageConnectedSocket(mmSocket);
   }

   /* batalkan koneksi  */
   public void cancel() {
       try {
           mmSocket.close();
       } catch (IOException e) {
           Log.e("ywlog",e.getMessage());
           e.printStackTrace();
       }
   }
}

//thread dipanggil saat user mengklik listview, passing device
public void client(BluetoothDevice device) {
   //start thread client
   (new ConnectThread(device)).start();
}

Terakhir, jika sudah terhubung, user akan mengirimkan data melalui ConnectThread , tambahkan button “kirim data”. Lalu berikut aksinya:

public void klikKirimData(View v) {
   ct.write("Halo dari client!");
}

Jika user menakan tombol ini, maka textview di server akan berisi “Halo dari client”. Di saat yang bersamaan, anda juga bisa menjadikan client menjadi server dengan menekan tombol “Enable Server”, lalu gantian tekan tombol kirim data di server.

Source code tutorial 1 sd 3 dapat diambil di: https://github.com/yudiwbs/CobaBluetooth

Membuat App Bluetooth di Android (2): Daftar Paired Devices dan Device Discovery

Menampilkan Paired Devices

Catatan: lanjutan dari tutorial sebelumnya (menyalakan Bluetooth), code yang digunakan dalam tutorial ini adalah lanjutan dari tutorial sebelumnya.

Sebelum dapat bertukar data, device harus ditemukan dan di-pair terlebih dulu (untuk alasan keamanan). Proses ini memperlukan waktu dan daya batere besar.  Oleh karena itu Android menyimpan data device yang sudah dipair (bonded) seperti nama device, class, MAC address  sehingga dapat langsung digunakan tanpa perlu mencari dan mem-pair lagi.

Untuk mendapatkan data device yang sudah di-pair atau bonded, dapat digunakan method BluetoothAdapter.getBondedDevices().

Pada app berikut kita akan menampilkan device yang sudah di-pair dalam listview. Kita perlu siapkan listiviewnya terlebih dulu. Tambahkan widget  Listview (gambar bawah)

Rancagan UI listview menampilkan paired device

Tambahkan ArrayList untuk menyimpan data beserta adapter.


 //untuk listview
 private ArrayList<String> items = new ArrayList<>();
 ArrayAdapter adapter;

 //menyimpan daftar device yg terurut sesuai listview
 private ArrayList<BluetoothDevice> arrayPairedDevices = new ArrayList<>();

Buat method yang mengambil data paired devices dan kemudian update listview.

// cari paired device, dan tambah ke listview
private void tampilkanPaired() {
   //ambil paired/bonded devices, tampung di arraylist
   Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();

   //ada?
   if (pairedDevices.size() > 0) {
       // Loop semua paired devices
       for (BluetoothDevice device : pairedDevices) {
           // tambahkan ke listview
           adapter.add(device.getName() + "\n" + device.getAddress());
           //nanti kalau user mengklik listview, kita tahu device apa yg diklik
           arrayPairedDevices.add(device);
       }
   }
   //refresh listview
   adapter.notifyDataSetChanged();
}

Panggil method tersebut pada saat onCreate.

 @Override
 protected void onCreate(Bundle savedInstanceState) {
     ... sama dengan sebelumnya

     //init listview
     ListView lv = (ListView) findViewById(R.id.listView);
     //set warna abu2, karena default font adalah putih
     lv.setBackgroundColor(Color.LTGRAY);
     adapter = new ArrayAdapter (this,android.
                                 R.layout.simple_expandable_list_item_1,items);
    lv.setAdapter(adapter);
    tampilkanPaired();

     //pindah ke bawah
     if (!mBluetoothAdapter.isEnabled()) {
         //bluetooth dalam kondisi off
         //menampilkan dialog untuk menyalakan bluetooth (jika sedang mati)
         //setelah user memilih method onActivityResult akan ditrigger
         Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
         startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
     }
 }

Dibagian onActivityResult juga tambahkan bagian untuk mengupdate listview saat Bluetooth selesai dinyalakan (bagian panah).

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
   if (requestCode == REQUEST_ENABLE_BT) {
       String pesan;
       if (resultCode == RESULT_OK) {
           //user menyalakan bluetooth
           pesan = "Bluetooth dinyalakan";
           tampilkanPaired();  // <-------------------
       } else {
           // user deny
           pesan = "Bluetooth tidak mau dinyalakan user!";
       }
       Toast toast = Toast.makeText(getApplicationContext(), 
                                    pesan, Toast.LENGTH_LONG);
       toast.show();
   }
}

Jalankan app dan hasilnya akan seperti gambar di bawah. Informasi yang ditampilkan adalah nama device dan MAC address. MAC address adalah alamat unik untuk network interface yang umumnya tersimpan di hardware. Tidak ada device yang memiliki MAC address yang sama.

tampilan paired bluetooth

Menemukan Device (Device Discovery)

PENTING: mulai Android 6 (Marshmallow, API 23), untuk melakukan device discovery diperlukan permission ACCESS_COARSE_LOCATION atau ACCESS_FINE_LOCATION (tentang permission ini lihat tutorial tentang Play Service Location API).

Secara default, umumnya device hanya visible pada device yang sudah ter-pair. Agar dapat ditemukan oleh proses scan, visibilitas device perlu diset terlebih dulu menjadi discoverable. Pada Android 5 dan 6 ini dilakukan dengan membuka setting Bluetooth dan otomatis device akan masuk mode discoverable (gambar bawah).

Menyalakan discoverable

Sedangkan untuk Android versi 4.2 diaktifkan dengan membuka setting BlueTooth lalu tap tulisan “Only Visible to paired device” (gambar bawah)

Menyalakan discoverable di Android 4

Kita akan membuat app yang mencari device baru yang belum di-pair sebelumnya. Sebelum menjalankan app, pastikan ada device lain sudah dalam kondisi discoverable sehingga dapat ditemukan oleh app kita.

Kita akan modfikasi project sebelumnya. Pertama tambahkan permission untuk lokasi ACCESS_COARSE_LOCATION. Ini disebabkan mulai Android 6 (Marshmallow, API 23), untuk dapat melakukan device discovery perlu permission ini. Jika tidak, tidak akan keluar error tetapi tidak akan mendapatkan hasil (membuat sangat sulit didebug!).

Jadi manifest akan seperti ini:

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

Android 6 juga menerapkan runtime permission system untuk beberapa permission yang dianggap “sensititf”. Dengan runtime permission system, user memberikan ijin bukan saat install, tetapi saat fitur akan digunakan (contoh gambar dibawah). Lokasi termasuk dalam kategori runtime permission.

run time permission untuk lokasi

Selanjutkan kita tambahkan kode untuk menangani runtime permission untuk lokasi di onCreate:

//untuk minta ijin lokasi, mulai android 6, scan bluetooth perlu ijin lokasi
//angka bebas tapi jangan lebih dari 1 byte! (<255)
private static final int MY_PERMISSIONS_REQUEST = 98;
boolean isIjinLokasi = false; //sudah mendapat ijin untuk mengakses lokasi?


@Override
 protected void onCreate(Bundle savedInstanceState) {
     ...  sama

     //mulai android 6, device discovery bluetooth perlu ijin lokasi
     //akan keluar dialog yg menanyakan apkah user memberikan ijin?
     if (ContextCompat.checkSelfPermission(this,
             //hati2, jika konstanta tidak cocok dgn manifest, tidak ada runtimeerror
             Manifest.permission.ACCESS_COARSE_LOCATION)
             != PackageManager.PERMISSION_GRANTED) {

         ActivityCompat.requestPermissions(this,
                 new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},
                 MY_PERMISSIONS_REQUEST);
                 // MY_PERMISSIONS_REQUEST adalah konstanta, 
                 //nanti digunakan di  onRequestPermissionsResult
     } else {
         //sudah diijinkan, tidak perlu tanya lagi
         isIjinLokasi = true;
     }

 }

Saat pertamakali menggunakan fitur, user akan ditanya apakah memberikan ijin. Hasilnya akan ditangkap di method onRequestPermissionResult seperti dibawah

//setelah muncul dialog tentang ijin lokasi, 
//user akan merespon (allow atau deny), dan method ini akan dipanggil
@Override
public void onRequestPermissionsResult(int requestCode,
                                      String permissions[], int[] grantResults) {

   if (requestCode == MY_PERMISSIONS_REQUEST) {
       if (grantResults.length > 0
           && grantResults[0] == PackageManager.PERMISSION_GRANTED)
       {
           isIjinLokasi = true; //diijinkan oleh user
       }
       return;
   }
}

Sekarang kita mulai aksi untuk mencari device. Tambahkan button, beri label “Start Discovery”

rancangan button

Isi onClicknya dengan method klikStartDiscovery sebagai berikut. Untuk memulai proses discovery dilakukan hanya dengan memanggil method startDiscovery() yang hasilnya kemudian ditangkap dengan BroadcastReceiver.

public void klikStartDiscovery(View v) {
   //membutuhkan waktu lama (sekitar 12 detik), proses dibackground
   //hasil ditangkap dengan BroadcastReceiver


   if (!isIjinLokasi) {
       //tidak mendapat ijin lokasi. Untuk android versi dibawah 6, tidak masalah tapi
       //di android versi 6 tidak error tapi tidak akan memberikan hasil --> susah didebug!!
       Toast toast = Toast.makeText(getApplicationContext(),
               "Tidak diijinkan untuk akses lokasi. " +
               "Proses device discovery tidak akan memberi hasil!!", Toast.LENGTH_LONG);
       toast.show();
   } else {
       //dapat ijin
       if (mBluetoothAdapter.isDiscovering()) {
           //sedang proses discovery? batalkan.
           mBluetoothAdapter.cancelDiscovery();
       }
       //mulai proses discovery
       //hasilnya akan ditangkap di BroadcastReceiver
       if (mBluetoothAdapter.startDiscovery()) {
           Toast toast = Toast.makeText(getApplicationContext(),
                   "Sukses memulai proses discovery", Toast.LENGTH_LONG);
           toast.show();
       } else {
           Toast toast = Toast.makeText(getApplicationContext(),
                   "GAGAL memulai proses discovery", Toast.LENGTH_LONG);
           toast.show();
       }
   }
}

Selanjutnya kita akan siapkan BroadcastReceiver. Pertama register BroadReceiver agar dipanggil saat device ditemukan, dan saat service discovery dimulai dan selesai.

@Override
protected void onResume() {
   // onResume juga dipanggil saat app dimulai
   // daftar broadcastreciver untuk menerima intent.
   IntentFilter filter = new IntentFilter();
   filter.addAction(BluetoothDevice.ACTION_FOUND);
   filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
   filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
   registerReceiver(mReceiver, filter);
   super.onResume();
}

@Override
protected void onPause() {
   // Unregister karena activity dipause.
   LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
   super.onPause();
}

Kemudian implementasikan BroadcastReceiver. Di method ini akan ditampilkan pesan saat pencarian dimulai dan selesai. Jika ada device baru, maka akan ditambahkan ke listview.

//untuk menangkap hasil method .startDiscovery
//tertrigger untuk setiap*device ditemukan (ACTION_FOUND)
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {

   public void onReceive(Context context, Intent intent) {
       String action = intent.getAction();

       // ada device baru
       if (BluetoothDevice.ACTION_FOUND.equals(action)) {
           // ambil objek BluetoothDevice dari Intent, tulis ke listivew
           BluetoothDevice device = 
               intent.getParcelableExtra (BluetoothDevice.EXTRA_DEVICE);
           
           arrayPairedDevices.add(device); 
    // tulis nama dan MAC address ke ListView
           adapter.add("Baru:" + device.getName() + "\n" + device.getAddress());
           //refresh listview, JANGAN LUPA!!
           adapter.notifyDataSetChanged();
       } else
       // mulai proses discovery, untuk debug saja, memastikan proses dimulai
       if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) {
           Toast toast = Toast.makeText(getApplicationContext(),
                   "Mulai proses discovery", Toast.LENGTH_LONG);
           toast.show();
       } else
       // mulai proses discovery, untuk debug saja, memastikan proses selesai
       if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
           Toast toast = Toast.makeText(getApplicationContext(),
                   "Proses discovery selesai", Toast.LENGTH_LONG);
           toast.show();
       }
   }
};

Jalankan maka hasilnya akan sebagai berikut (pastikan device lain dalam kondisi ter-discoverable). Device yang baru akan diawali kata “Baru”.

tambahan device baru

 

Berlanjut ke bagian 3: komunikasi data antar device

Membuat App Bluetooth di Android (1): Pendahuluan dan Aktifkan BT

logo bluetooth

 

Bluetooth (BT) adalah  standard teknologi wireless untuk komunikasi jarak pendek (10m – 100m) dengan energi rendah untuk data berukuran relatif kecil. BT menggunakan frekuensi 2.4 sampai dengan  2.485Ghz (sama dengan WiFi, tapi berbeda teknologi). Kecepatan Bluetooth mencapai 25Mbps (versi 4.0) Bluetooth diciptakan oleh perusahaan Erricson tahun 1995 dan berkembang cepat sejalan dengan tumbuhnya mobile device dan IoT (Internet of Thing).

Darimana asal kata dan logo Bluetooth? Nama Bluetooth diambil dari Raja Denmark: King Harald Bluetooth yang menyatukan berbagai fraksi di Denmark, Norwegia dan Swedia. Logo Bluetooth merupakan gabungan dari simbol H dan B yang merupakan inisial raja tersebut.

sejarah logo bluetooth

Bluetooh Low Energy (BLE) atau Bluetooth Smart muncul tahun 2010 sejalan dengan versi 4.0. BLE menggunakan daya rendah dan memiliki kecepatan transfer lebih tinggi dibandingkan BT versi lama (klasik). Karena hemat daya, BLE cocok untuk smartphone dan alat yang menggunakan batere koin. Sebagai contoh, batere koin CR2032 bisa digunakan untuk menghidupi alat bluteooth selama 5-10 tahun!. Sayangnya protokol BLE tidak kompatibel dengan BT klasik dan belum semua device support BLE. Tutorial ini masih membahas BT klasik.

Saat ini ada jutaan device yang memiliki BT ini. Mulai dari laptop, mouse, keyboard, smarphone, headphone, headset, fitness tracker, speaker dan lain sebagainya. Jumlah yang besar ini membuat harga hardware BT semakin murah.

Saingan utama BT adalah Wi-Fi Direct. Wi-Fi Direct dapat menghubungkan dua device secara peer to peer tanpa memerlukan access point. Wi-Fi direct memiliki kecepatan lebih tinggi (250Mbps) dan jangkauan lebih jauh (300m). Tapi Wi-Fi directy lebih boros daya dan belum banyak device yang mendukungnya. Android mendukung baik BT, BLE maupun WiFi direct.

Android menyediakan Bluetooth API yang dapat digunakan untuk:

  • Cek apakah device mensupport BT
  • Menampilkan dialog agar user menyalakan BT
  • Scan device yang berada dekat lalu pairing.
  • Membuka channel RFCOMM (Radio Frequency Communications) dan bertukar data.

Ada dua jenis bluetooth yang disupport Android, pertama Classic Bluetooth dan kedua adalah Bluetooth Low Energy, BLE (mulai Android 4.3). Kita akan mulai dari yang klasik terlebih dulu, karena BLE masih belum disupport oleh semua device Android yang ada di pasaran.

Berikutnya kita akan membuat app yang mendeteksi apakah device sudah support BT dan menampilkan dialog untuk menyalakan jika BT dalam kondisi off.

Buat project baru.

Buatlah project baru. Untuk menggunakan Bluetooth diperlukan permission android.permission.BLUETOOTH sedangkan untuk dapat mencari device harus digunakan android.permission.BLUETOOTH_ADMIN. Update manifest sebagai berikut:

<manifest>
...
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
</manifest>

Pengecekan Device apakah telah mendukung Bluetooth

Selanjutnya perlu dicek apakah device sudah men-support bluetooth. Untuk melakukan ini gunakan class BluetoothAdapter. Semua aktivitas yang terkait BT memerlukan kelas ini. Gunakan method getDefaultAdapter, dan jika mengembalikan nilai null, maka device tidak mensupport Bluetooth. Coba code dibawah dibagian onCreate.

BluetoothAdapter mBluetoothAdapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);

   mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
   String pesan;
   if (mBluetoothAdapter == null) {
       pesan = "Tidak ada bluetooth!";
       finish(); //keluar
   } else {
       pesan ="Ada bluetooth!";
   }
   Toast toast = Toast.makeText(getApplicationContext(), pesan, Toast.LENGTH_LONG);
   toast.show();
}

Menyalakan Bluetooth

Langkah berikutnya adalah menyalakan Bluetooth jika dalam kondisi mati. Akan muncul dialog, dan setelah user memilih maka method onActiviyResult akan dipanggil. Berikut codenya:

//konstanta untuk req enable bluetooth, angkanya bebas
public static final int REQUEST_ENABLE_BT=999;

@Override
protected void onCreate(Bundle savedInstanceState) {
   … sama dengan sebelumnya

   if (!mBluetoothAdapter.isEnabled()) {
       //bluetooth dalam kondisi off
       //menampilkan dialog untuk menyalakan bluetooth 
       //setelah user memilih; method onActivityResult akan di-trigger
       Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
       startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
   }
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
//setelah user menjawab dialog menyalakan BT 
//maka method ini akan dipanggil
   if (requestCode == REQUEST_ENABLE_BT) {
       String pesan;
       if (resultCode == RESULT_OK) {
           //user menyalakan bluetooth
           pesan = "Bluetooth dinyalakan";
       } else {
           // user deny
           pesan = "Bluetooth tidak mau dinyalakan user!";
       }
       Toast toast = Toast.makeText(getApplicationContext(), pesan, Toast.LENGTH_LONG);
       toast.show();
   }
}

Bersambung ke bagian 2: menampilkan paired device dan mencari device (device discovery)

Android Thread (3): IntentService

Lanjutan dari tutorial pendahuluan tentang thread dan Asynctask.

Service cocok untuk task yang membutuhkan waktu lama, berjalan di background dan tidak membutuhkan akses ke user interface. Misalnya saat app mengirimkan data ke server, user dapat menekan tombol “send” dan kemudian melanjutkan menggunakan app untuk yang lain. Contoh yang lain adalah streaming musik di backround dan download.

Perbedaan utama antara service dengan Asynctask adalah Asynctask cocok untuk task yang berjalan di background yang terkait dengan user interface sedangkan service cocok untuk task background yang independen terhadap user interface. Untuk memberikan notifikasi ke user dari service, dapat digunakan toast atau notification bar. Perbedaan lain dengan AsyncTask adalah service mendapat prioritas lebih tinggi sehingga lebih kecil kemungkinannya untuk dimatikan, walaupun jika kehabisan memory service dapat dimatikan untuk kemudian direstart lagi.

Fungsi service ada dua: pertama meminta sistem menjadwalkan task di background yang akan terus berjalan sampai service berhenti. Fungsi kedua adalah menyediakan layanan pada app lain. Service bukan thread dan berada di thread utama (UI thread). Itu sebabnya kita tetap perlu membuat thread terpisah di service untuk task yang panjang karena tetap dapat menyebabkan error ANR.

Seperti halnya AsyncTask, Android menyediakan IntentService untuk memudahkan programmer menggunakan service. IntentService secara otomatis akan membuat thread baru (disebut worker thread) dan menangani antrian pemanggilan service (jika service dipanggil berkali-kali). Intinya programmer tidak perlu lagi memikirkan pengelolaan thread di dalam service.

Pada tutorial ini akan dibahas terlebih dulu tentang IntentService, baru kemudian dilanjutkan mengenai service yang lebih fleksibel tapi lebih kompleks.

Kita akan membuat app sederhana, yang akan menampilkan toast saat proses background dimulai dan toast saat proses background selesai.

Untuk menggunakan IntentService, extends kelas IntentService dan override method create() dan onHandleIntent(). Task yang berjalan di backround diletakkan di onHandleIntent().

Tetapi kita perlu membuat kelas khusus untuk menampilkan toast terlebih dulu. Toast tidak dapat langsung digunakan karena onHandleIntent berjalan di worker thread, bukan UI thread. Oleh karena itu perlu digunakan handler. Handler adalah objek yang dapat digunakan untuk berkomunikasi antar thread. Handler akan berada pada thread yang membuatnya. Karena service dicreate oleh UI thread, maka handler otomatis juga ada di UI thread. Jadi kita bisa menggunakan handler untuk berkomunikasi dari worker thread ke UI thread (pusing ya?).

public class DisplayToast implements Runnable {
   private final Context mContext;
   String mText;

   public DisplayToast(Context mContext, String text){
       this.mContext = mContext;
       mText = text;
   }

   public void run(){
       Toast.makeText(mContext, mText, Toast.LENGTH_SHORT).show();
   }
}

Buat project baru, buat kelas CobaService yang mengextend IntentService sebagai berikut :

public class CobaService extends IntentService {
    Handler mHandler; //untuk komunikasi dengan UI thread 


public CobaService() {
   super("cobaservice"); //untuk debug saja
   mHandler = new Handler();
}

@Override
protected void onHandleIntent(Intent intent) {

   //ambil data dari intent
   String pesan;
   Bundle extras = intent.getExtras();
   if(extras == null) {
       pesan= null;
   } else {
       pesan=extras.getString("PESAN");
   }
   
  mHandler.post(new DisplayToast(this, "Service mulai.... Pesan="+pesan));

  //proses background ada disini 
  for (int i = 0; i<50;i++) {
       try {
           Thread.sleep(50);  //simulasikan proses panjang
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
   }
   mHandler.post(new DisplayToast(this, "Service selesai"));
}

}

Selanjutnya, service perlu didaftarkan di dalam manifest, dengan membuat elemen service di dalam elemen application.


<application
   ...
   <service android:name=".CobaService" android:exported="false"/>
</application>

android:exported diset false agar service ini hanya dapat dipanggil lokal oleh app. Ingat bahwa salah satu fungsi service adalah menyediakan layanan bagi app lain. Untuk faktor keamanan, kecuali jika memang ingin service dapat diakses app lain, selalu set exported dengan false.

Selanjutnya kita akan membuat tombol yang saat ditekan akan memanggil service ini: (jangan lupa set onClick button dengan method dibawah).

public void klikMulai(View v) {
   //intent digunakan sebagai paramater
   serviceIntent = new Intent(getApplicationContext(), CobaService.class);
   serviceIntent.putExtra("PESAN", "hello world"); //data yang dikirim
   getApplicationContext().startService(serviceIntent); //mulai jalankan 
}

Coba tekan tombol berulang-ulang, maka akan muncul secara berurutan pesan “service mulai,” “service selesai”, “service mulai” dan seterusnya. Intentservice akan menangani request secara berurutan, bukan paralel. Lalu coba tekan tombol dan langsung hide app, maka walaupun kita berada di app lain, tulisan “service selesai” tetap akan muncul.

Menampilkan Notifikasi

Pada contoh sebelumnya kita menggunakan toast untuk menampilkan pesan bahwa service sudah selesai. Kelemahan toast adalah hanya muncul sebentar. Untuk menjamin agar pesan dibaca user dapat digunakan notification (gambar bawah).

screen1

Untuk menambahkan notifikasi, gunakan NotificationCompat.Builder untuk membuat notifikasi dan NotifcacationManager untuk menampilkannya. Perlu digunakan TaskStackBuilder agar tombol back tetap berfungsi konsisten. Tambahkan kode berikut dibagian akhir dari onHandleIntent.

@Override
protected void onHandleIntent(Intent intent) {
...sama dengan sebelumnya

// sudah selesai, siapkan notif builder
NotificationCompat.Builder notifBuilder =
       new NotificationCompat.Builder(this)
               .setSmallIcon(R.mipmap.ic_launcher)
               .setContentTitle("My notification")
               .setContentText("Selesai! Tap untuk menampilkan app");

// explisit intent untuk memulai activity
Intent resultIntent = new Intent(this, MainActivity.class);
// Gunakan taskstack agar saat user menekan tombol back tetap konsisten
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
stackBuilder.addParentStack(MainActivity.class);
stackBuilder.addNextIntent(resultIntent);

PendingIntent resultPendingIntent =
       stackBuilder.getPendingIntent(
               0,
               PendingIntent.FLAG_UPDATE_CURRENT
       );
notifBuilder.setContentIntent(resultPendingIntent);
notifBuilder.setAutoCancel(true);//agar setelah ditap tutup otomatis

//notifmanager untuk menampilkan
NotificationManager mNotificationManager =
       (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// 999 id untuk jika perlu modif nanti (bagusnya jadi konstanta).
mNotificationManager.notify(999, notifBuilder.build());

}

Menampilkan Notifikasi dengan Progressbar

Untuk proses yang panjang, dari sisi user interface akan lebih bagus jika user mendapat informasi kemajuan proses seperti gambar dibawah:

screen3

Code untuk membuat notification dengan progressbar adalah sebagai berikut. Ubah project sebelumnya dibagian loop.

....
//proses background ada disini
mHandler.post(new DisplayToast(this, "Service mulai.... Pesan="+pesan));

//init notification manager
NotificationManager mNotificationManager =
       (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);


//notifikasi dengan progressbar
NotificationCompat.Builder notifBuilderProgress =
       new NotificationCompat.Builder(this)
               .setSmallIcon(R.mipmap.ic_launcher)
               .setContentTitle("Progress")
               .setContentText("Sedang proses");

//loop proses yang panjang
for (int i = 0; i<100;i++) {
   try {
       Thread.sleep(50);  //simulasi proses yang panjang
       //max diisi 100, false artinya kita tahu selesainya kapan
       notifBuilderProgress.setProgress(100, i, false);
       // tampilkan progress bar
       // 998 adalah id, bisa diganti dengan nilai lain
       mNotificationManager.notify(998, notifBuilderProgress.build());
   } catch (InterruptedException e) {
       e.printStackTrace();
   }
}

// update notif
// buang progress bar
notifBuilderProgress.setContentText("Proses selesai").setProgress(0,0,false);
mNotificationManager.notify(998, notifBuilderProgress.build());

mHandler.post(new DisplayToast(this, "Service selesai"));
...

Video contoh service dengan notification [youtube]: https://youtu.be/XE4HXXDNCpI

Menerima Status IntentService sudah selesai

Terkadang kita memerlukan informasi bahwa service sudah selesai. Masalahnya, tidak ada hubungan langsung antara service dengan activity yang memanggilnya. Untuk ini, dapat digunakan LocalBroadcastManager. Jadi service akan mengirimkan Intent, dan activity akan “menangkapnya”.

Menggunakan contoh tutorial IntentService sebelumnya, kita perlu ubah MainActivity agar dapat menangkap broadcast yang dikirim oleh service. Buat kelas yang meng-extends BroadcastReceiver. Method onReceive() berisi penanganan jika menerima broadcast dari service. Pada contoh ini, saat kita menerima broadcast, yang akan kita lakukan adalah menampilkan pesan yang dikirimkan service.

Kemudian pada onResume, kita mendaftarkan receiver dan meng-unregister-nya pada saat app di pause.

public class MainActivity extends AppCompatActivity {
   Intent serviceIntent;
   TextView tvHasil;

   //konstanta yang mendefinisikan aksi
   public static final String  ACTION_TERIMA = "aksi-cobaservice";

   //definisikan aksi jika mendapat broadcast
   private  class MyBroadcastReceiver extends BroadcastReceiver {
       //terima pesan, update UI
       //tapi jika app berada di background, tidak akan terupdate
       @Override
       public void onReceive(Context context, Intent intent) {
           // ambil extra data yang ada Intent
           String pesan = intent.getStringExtra("PESAN");
           //tulis pesan di textview
           tvHasil.setText(pesan);
       }
   }

   private MyBroadcastReceiver mReceiver;

   @Override
   protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     setContentView(R.layout.activity_main);
     tvHasil = (TextView) findViewById(R.id.tvHasil);
     mReceiver = new MyBroadcastReceiver();
}

@Override
protected void onResume() {
   // onResume juga dipanggil saat app dimulai
   // daftar broadcastreciver untuk menerima intent.
   // tapi khusus untuk action ACTION_TERIMA yang sudah kita definisikan
   LocalBroadcastManager.getInstance(this).registerReceiver(
           mReceiver, new IntentFilter(ACTION_TERIMA));
   super.onResume();
}

@Override
protected void onPause() {
   // Unregister karena activity dipause.
   LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
   super.onPause();
}

   
   //...	
   //yang lain sama dengan sebelumnya   
   //...
   
}

Sekarang kita akan membuat service mengirimkan pesan. Kelas yang digunakan adalah LocalBroadcastManager. Menggunakan contoh sebelumnya, coba buka class CobaIntentService, tambahkan code berikut di onHandleIntent setelah proses berakhir:

@Override
protected void onHandleIntent(Intent intent) {
   //...	
   //sama dengan sebelumnya   
   //...

   //setelah proses selesai, kirim pesan ke MainActivity bahwa service sudah selesai
   
   //kirim pesan ke activity
   Intent localIntent = new Intent(MainActivity.ACTION_TERIMA);
   localIntent.putExtra("PESAN", "Sudah selesai!");
   //menggunakan LocalBroadCastManager 
   //agar pengiriman dijamin tidak bisa dilihat app lain
   LocalBroadcastManager.getInstance(this).sendBroadcast(localIntent);
}

Jalankan app, maka saat sudah selesai textview akan terupdate dengan pesan selesai. Tetapi kelemahannya jika app berada di background, maka textview tidak akan terupdate.

Source code untuk tutorial intentservice ini: https://github.com/yudiwbs/CobaIntentService

Android Thread (4): Java Thread

Jika pertama kali menggunakan thread, dianjurkan untuk membaca dulu bagian pertama yang membahas pendahuluan tentang thread lalu mencoba bagian kedua tentang implementasi thread yang paling sederhana, AsyncTask terlebih dulu.

Cara lain melakukan proses di background adalah menggunakan thread seperti halnya yang disediakan Java. Cara ini lebih fleksibel daripada AsyncTask, tetapi lebih sulit. Sebenarnya Asynctask, IntentService dan lain-lain dibuat menggunakan library java thread ini.

Untuk membuat thread baru, dapat digunakan kelas Thread. Ada dua cara menjalankan thread, pertama dengan menggunakan objek Runnable, kedua dengan meng-override method Run di kelas Thread. Contoh berikut akan lebih menjelaskan (baca saja contohnya, tidak perlu dibuat).

Cara pertama, menggunakan Runnable. Runnable adalah kelas yang berisi potongan code yang harus dijalankan. Berikut contoh class yang meng-implement Runnable:

public class MyRunnable implements Runnable {
   public void run() {
       //code yang akan dijalankan
   }
}

Lalu buat objek thread, dan jadikan objek runnable tersebut menjadi parameter

//versi panjang:
MyRunnable r  = new MyRunnable();
Thread t = new Thread(r); //runnnable jadi parameter
t.start();  //thread dimulai

Cara yang lebih singkat dan lebih umum adalah dengan anonymous class

//versi pendek dengan anonymous class
(new Thread(new MyRunnable())).start();

Cara yang kedua adalah tanpa runnable, dengan meng-extends kelas Thread. Contohnya sebagai berikut:

public class MyThread extends Thread {
   public void run() {
       //code yang akan dijalankan
   }
}

Dan untuk menjalankannya:

(new MyThread()).start();

Cara pertama dengan Runnable lebih umum digunakan karena tidak perlu meng-extends class Thread.

Di Android, untuk mengupdate UI, harus menggunakan method View.post dengan parameter kelas Runnable. Lebih jelasnya dapat dilihat di contoh berikut.

Kita akan membuat app seperti tutorial AsyncTask sebelumnya. Pada app tersebut terdapat satu button yang jika ditekan akan menjalankan 100 loop yang setiap loopnya membutuhkan waktu cukup lama. Setiap akhir loop, kemajuan akan terlihat di progressbar. Isilah onclick pada button sebagai berikut:

ProgressBar pb;
int progress=0;

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);
   pb = (ProgressBar) findViewById(R.id.progressBar);
}

public void onClick(View v) {
   //jika diklik, jalankan thread 
   new Thread(new Runnable() {
       public void run() {
	   //ini proses yg di background	 
           for (int i = 0; i<100;i++) {
               try {
                   Thread.sleep(50);  //delay 0.05 detik, simulasikan proses panjang
                   progress = i;
                   //update user interface harus menggunakan View.post(Runnable)
		   //pb adalah progressbar	
                   pb.post(new Runnable() {
                       public void run() {
                           pb.setProgress(progress);
                       }
                   });
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
       }
   }).start();
}

Latihan: dari program diatas, bagaimana kita menambahkan tulisan “mulai” saat proses bacground dimulai dan “sukses” saat selesai?

 

Handler

Cara lain untuk berkomunikasi dengan UI thread adalah dengan Handler.  Pada contoh diatas kita menggunakan ProgressBar.post(), tetapi semakin kompleks interaksi antara thread  dan UI maka code akan semakin sulit dibaca. Handler dapat digunakan untuk mengatasinya.

Handler adalah objek yang digunakan untuk berkomunikasi antar thread.  Di UI thread kita buat handler, lalu objek handler ini di-pass ke thread. Thread kemudian berkirim pesan melalui handler.

Baik kita langsung buat saja. Pertama buat kelas baru yang berisi thread yang akan memproses dibackground. Kelas ini (ThreadDgnHandler) mengextends kelas Thread. Kelas ini menerima parameter Handler dari UI. Untuk mengirim informasi, gunakan method .obtainMessage() dan .sendToTarget() dari Handler.

public class ThreadDgnHandler extends Thread {
    private final Handler mHandler; //handler dari UI

    //constructor
    public ThreadDgnHandler(Handler mHandler) { 
        this.mHandler = mHandler;
    }

    public void run() {
        //kirim pesan mulai, update ke UI
        //1: konstanta "1" artinya pesan untuk update textivew. konstanta bebas
        //-1: paramter 1, tidak digunakan
        //-1: paramter 2, tidak digunakan
        //"Mulai": paramter 3, bisa objek apapun
        mHandler.obtainMessage(1, -1, -1, "Mulai").sendToTarget();

        //loop peroses background
        for (int i = 0; i<100;i++) {
            try {
                Thread.sleep(50);  //delay 0.05 detik
                //2: konstanta "2" artinya update progres barr
                //i: paramter 1, progressnya
                //paramter yang lain tidak digunakan
                mHandler.obtainMessage(2, i, -1,null).sendToTarget();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //kirim pesan selesai
        mHandler.obtainMessage(1, -1, -1, "Selesai").sendToTarget();

    }
}

Selanjutnya buat objek handler di main activity. Handler ini akan mengupdate textview dengan “mulai””dan “selesai” sekaligus progressbar. Karena handler ini dicreate di UI thread maka handler juga akan berjalan di UI thread dan dapat digunakan untuk mengupdate UI.

    /**
     * Handler yang mendapat informasi dari ThreadDgnHandler
     * Handler ini ada di UI thread, jadi dapat digunakan untuk mengupdate UI
     */
    private final Handler mHandler = new Handler()
        {
            @Override
            public void handleMessage(Message msg) {
                //1: artinya update textview
                //2: artinya update progressbar
                if (msg.what==1) {
                    String pesan = (String) msg.obj;
                    tvHasil.setText(pesan);
                } else
                if (msg.what==2) {
                    pb.setProgress(msg.arg1); //arg1 berisi progress
                }
            }
        };

Sekarang buat tombol yang menggunakan handler (gambar bawah), jangan lupa hubungkan onClicknya dengan method berikut:

ScreenHunter_158 Dec. 12 21.22


 public void klikDgnHandler(View v) {
        //memproses dengan handler
        //kalau contoh sebelumnya menggunakan View.post
        //kalau ini dengan handler

        //handler dipassing jadi paramter
        ThreadDgnHandler tdh = new ThreadDgnHandler(mHandler);
        tdh.start();
 }

Terlihat code lebih bersih dan mudah dibaca dibandingkan dengan contoh pertama.

 

 

 

 

 

 

Source code untuk tutorial ini: https://github.com/yudiwbs/CobaThread

 

Android Thread (2): AsyncTask

Update versi terakhihr (google doc): https://docs.google.com/document/d/1K03-kHsokJ4aE5u2Ec4cxsaYFubheIAlJ8DqgmrvW_s/edit?usp=sharing

Catatan: jika belum mengerti tentang thread, baca dulu bagian 1.

AsyncTask adalah class yang disediakan Android untuk memudahkan  programmer membuat task yang dijalankan di-background. AsyncTask cocok untuk proses background yang relatif  sederhana dan pendek. Misalnya saat mendowload objek dari internet, dan dalam prosesnya UI akan menampilkan kemajuan download.

Sebelum memulai tutorial, ada empat method terpenting dalam AsyncTask.

  1. onPreExecute() dipanggil sebelum task dikerjakan. Biasanya untuk menginisasi user interface.
  2. doInBackground(Params…), berisi task inti yang perlu dijalankan di background dan berpotensi memblok UI. Method ini akan dijalankan di thread background langsung setelah onPreExecute dipanggil. Gunakan  publishProgress(Progress…) untuk mengupdate UI mengenai progress (misal mengupdate progressbar). JANGAN update UI dimethod ini! gunakan onProgressUpdate.
  3. onProgressUpdate(Progress…)  dipanggil setelah publisProgress dijalankan. Berisi code untuk mengupdate user interface.  JANGAN panggil onProgressUpdate langsung!  gunakan publishProgress (tanpa on). Method ini berjalan di UI thread sehingga aman untuk memanipulasi komponen UI.
  4. onPostExecute(Result), dapat digunakan untuk mengupdate user interface setelah task background selesai. Hasil dari komputasi dipassing sebagai parameter.

Supaya lebih jelas kita akan membuat contoh app yang jika tombolnya ditekan maka akan mensimulasikan task yang cukup lama (100 loop yang setiap loopnya berisi sleep beberapa milidetik). Saat setiap satu loop berakhir maka progressbar akan diupdate.

Pertama, tambahkan tiga komponen view seperti gambar di bawah. Satu label, satu progress bar (horizontal) dan satu button:

ScreenHunter_123 Nov. 25 23.15
Tambahkan code berikut, yang akan dijalankan saat button ditekan.

public void klikMulai(View v) {
   //button ditekan
   pb.setMax(100);
   new KerjakanBackgroundTask().execute(); //tidak ada parameter
}

Jangan lupa set property dari button:

ScreenHunter_124 Nov. 25 23.18

Tambahkan code berikut, perhatikan penggunaan extends AsyncTask. Harap baca penjelasan dalam comment.

private class KerjakanBackgroundTask extends AsyncTask&lt;Void, Integer, String&gt; 
/*
 Tiga parameter (Void, Integer, String): 
1. Parameter ke background task; 
2. Pogress saat background task dijalankan
3. Result hasil dari background task
Untuk kasus di app ini (untuk app lain bisa berbeda):
1.Param ke bacground task: Void (tidak ada)
2.Progress saat background task dijalankan: Integer (setiap loop diincrement dan dikirim ke progressbar
3.Result: String (output)
*/
{
   //sebelum proses
   protected void onPreExecute() {
       pb.setProgress(0);
       tvHasil.setText("Mulai");
   }

   //proses
   protected String doInBackground(Void... v) {
       String hasil="-";
       try {
           for (int i = 0; i&lt;100;i++) {
               Thread.sleep(50);  //delay 0.05 detik biar lama
               //update user interface dengan publichProgress
               //JANGAN ada update user interface langsung
               publishProgress(i);
               if (isCancelled()) {  
                   hasil = "batal";
                   break; //user bisa cancel ditengah task
               }
           }
           hasil = "Sukses";
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       return hasil;  //result
   }

   //progress
   protected void onProgressUpdate(Integer... progress) {
   //Update user interface disini karena ini berjalan di UI thread
   //Integer... (tiga titik) artinya parameter bisa inteteger lebih dari satu
   //dan bentuknya array
   //jangan panggil onProgress langsung, gunakan publichProgress!

       pb.setProgress(progress[0]);
   }

   
   protected void onPostExecute(String result) {
   //selesai, bisa update user interface karena ini berjalan di UI thread
       tvHasil.setText(result);
   }
}

Jika dijalankan dan button ditekan, maka progressbar akan bergerak sedikit demi sediikit, dan setelah selesai maka textview akan diisi dengan hasil (string “Sukses”). Result dapat diganti tipenya menjadi integer atau float.

Kelemahan Asynctask adalah jumlah task dalam satu app terbatas. Asynctask juga terkait dengan Activity, sehingga dapat bermasalah jika Activity di-destroy (misal saat device di rotate).

Source code tutorial ini: https://github.com/yudiwbs/AsyncTask_Demo

Lanjut ke materi thread berikutnya: IntentService

todo:loader untuk masalah activity yang didestroy.

Android Thread (1): Pendahuluan

Dengan semakin umumnya penggunaan chip multicore  pada smartphone, penggunaan thread yang memungkinkan beberapa task berjalan secara paralel semakin penting untuk meningkatkan kinerja app. Multicore sudah disupport Android sejak versi HoneyComb (3.0). Selain itu, app Android yang terlalu lama berproses dan membuat user interface “hang”  akan terkena error ANR (Application Not Responding). Thread dapat digunakan untuk mengatasi ini dengan memindahkan aktivitas yang lama pada thread yang terpisah.

Contoh multithread pada desktop adalah aplikasi Word di Windows. Saat kita membuka Word dan melakukan penyimpanan, kita secara bersamaan masih dapat mengedit dokumen. Kedua aktivitas ini dilakukan oleh aplikasi yang sama tapi dalam thread yang terpisah.

Sebelumnya perlu dibedakan antara thread dan proses. Thread dan proses sama-sama merupakan urutan kode yang dieksekusi. Pada Android, yang dasarnya adalah Linux,  setiap app yang dijalankan berada di proses yang terpisah. Satu proses kemudian dapat memiliki satu atau lebih thread. Thread-thread di dalam proses yang sama berbagi memory, walaupun setiap thread punya register dan stack sendiri. Proses independen satu sama lain, sedangkan thread-thread  pada proses yang sama saling terkait (gambar bawah).

 

process_thread

 

Kenapa menggunakan thread? karena untuk men-create proses mahal dari sisi resources dibandingkan thread dan komunikasi antar thread lebih mudah dibandingkan antar proses.  Kasus yang menarik di desktop adalah browser Chrome vs Firefox. Setiap tab pada Chrome menggunakan proses yang berbeda, sedangkan Firefox menggunakan thread yang berbeda untuk setiap tab. Coba lihat task manager saat membuka banyak tab di Chrome, akan banyak proses bernama Chrome.exe.  Pembuat Chrome beralasan dengan arsitektur seperti ini, jika salah satu tab “hang” maka tidak akan mempengaruhi tab yang lain. Tapi efek sampingnya, Chrome membutuhkan lebih banyak memori dibandingkan Firefox. Firefox sebenarnya juga menggunakan multi proses, tapi hanya untuk plugin, karena plugin sering menjadi sumber masalah. 

Terlepas dari keunggulannya, penggunaan multithread atau multiproses juga dapat menimbulkan masalah, misalnya sinkronisasi, deadlock, race condition dan  starvation (dipelajari di kuliah sistem operasi). Program juga lebih sulit didebug dan kadang malah kinerjanya lebih jelek.  Walaupun Java dan Android telah menyediakan berbagai library untuk memudahkan pembuatan program multithreading, tetap saja ini bukan hal yang mudah.

Kembali ke Android, saat setiap app dijalankan maka app tersebut memiliki thread utama yang disebut main thread atau UI thread. UI thread ini mengatur tampilan, menerima event dan sebagainya. Aturan yang harus dipegang adalah:

  1. Jangan memblok UI thread.  Misalnya saat user menekan tombol, maka app menjalankan program yang loop sejuta kali sehingga membuat user interface macet. Ini akan berakibat muncul error ANR (App Not Responding). Resources pada smartphone jauh lebih terbatas daripada PC/laptop, sehingga aturan ini lebih ketat.
  2. Jangan mengakses komponen UI di luar UI thread. Misalnya ada thread kedua dan thread ini mengakses TextView, Button dsb. Ini dapat menyebabkan error yang tidak terduga. Ini akan lebih jelas pada tutorial berikutnya.

Ada beberapa cara teknik paralelisasi di Android: AsyncTask, Java thread dan IntentServices. Mana yang lebih tepat? tergantung masalahnya.

Kita akan mulai tutorialnya dari yang paling mudah terlebih dulu: AsyncTask  jika sudah, dapat dilanjutkan ke Intentservice untuk background task yang tidak memerlukan UI. Terakhir adalah Java thread, untuk kontrol thread yang lebih kompleks.

 

 

Android: Menyimpan file

Android menyediakan tiga cara untuk menyimpan data di device. Jika hanya untuk menyimpan sedikit data (beberapa variabel), gunakan shared preferences.  Jika data cukup kompleks dan sering memerlukan pencarian (akses random), gunakan database. Jika ukuran datanya besar,  tidak perlu pencarian, dapat dishare di SD card untuk dibaca komputer, atau memerlukan format yang yang sangat spesifik, gunakan file seperti yang akan dibahas di posting ini.

File dapat disimpan baik di dalam device (internal) maupun eksternal (SD card).  Perbedaan penyimpanan internal dan eksternal dapat dilihat di tabel berikut

InternalEksternal
Selalu tersediaBelum tentu, user bisa mencabut SD Card
Secara default, tidak dapat diakses oleh app lainBisa diakses oleh siapa saja
Jika app diuninstall, akan ikut terhapus.Tidak terhapus, kecuali disimpan di tempat yang diperoleh dari method getExternalFilesDir
Tidak dapat diakses oleh app lainDapat diakses oleh app lain, termasuk oleh komputer.
Tidak memerlukan permissionMemerlukan permission di manifest

Internal cocok untuk data yang tidak perlu dibaca app lain, sedangkan eksternal cocok jika data berukuran sangat besar atau data tidak masalah dibaca app lain termasuk untuk diakses komputer.

Bagi anda yang telah mengenal stream dan file di Java, anda dapat membaca secara cepat tutorial ini, karena tidak ada perbedaan antara Java dan Android. Class yang mensupport operasi file disediakan di package java.io.*

Pada tutorial ini kita akan menyimpan file teks di media penyimpanan eksternal.

Pertama tambahkan permission untuk menulis di media eksternal

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

Tambahkan code berikut dibagian create


protected void onCreate(Bundle savedInstanceState) {
        //.. code create

   
        //debug untuk melihat tempat penyimpanan
        AlertDialog d= new AlertDialog.Builder(this).create();
        d.setMessage("Dibuat di dir"+getApplicationContext().getExternalFilesDir(null));
        d.show();

        //siapkan file hasil.txt untuk ditulis
        File fileOut = new File(getApplicationContext().getExternalFilesDir(null),"hasil.txt");
        try {
            PrintWriter pw = new PrintWriter(fileOut);
            pw.println("Data baris 1");
            pw.println("Data baris 2");
            pw.close();
            AlertDialog dialog = new AlertDialog.Builder(this).create();
            dialog.setMessage("data selesai ditulis");
            dialog.show();
        } catch (Exception ex) {
            Log.e("yw", ex.getMessage());
        }

        //baca data
        File fileInput = new File(getApplicationContext().getExternalFilesDir(null),"hasil.txt");
        try {
            Scanner sc = new Scanner(fileInput);
            String strBaris;
            StringBuilder sb = new StringBuilder(); //untuk menampung string
            while (sc.hasNextLine()) {
                strBaris = sc.nextLine(); //ambil satu baris
                sb.append(strBaris);
            }
            AlertDialog dialog = new AlertDialog.Builder(this).create();
            dialog.setMessage("data yg dibaca: "+sb.toString());
            dialog.show();
        } catch (FileNotFoundException ex) {
            Log.e("yw", ex.getMessage());
        }

    }

Jalankan, maka akan ditampilkan lokasi data (di hp saya: dir/storage/emulated/0/Android/data/[package app]/files ) dan data yang berhasil dibaca. Lokasi ini sifatnya dapat diakses siapapun, jika kita hubungkan HP dengan komputer, maka kita dapat mengambil file hasil.txt ini. Karena lokasi diambil dengan getExternalFilesDir, jika app diuninstall maka isi dari direktori ini juga akan ikut dihapus.

Karena media eksternal belum tentu tersedia (misal sedang dikeluarkan), maka sebaiknya perlu dicek dulu apakah tersedia atau tidak. Jika output dari method getExternalStorageState menghasilkan MEDIA_MOUNTED maka artinya media siap dibaca dan ditulis. Berikut contohnya.

    String state = Environment.getExternalStorageState();
    if (Environment.MEDIA_MOUNTED.equals(state)) {
        return true;  //bisa digunakan
    }

Internal
Bagaimana jika kita ingin menulis file di media internal? Intinya hanya mengganti lokasi tempat penyimpanan.
Ada dua method yang dapat digunakan

getFilesDir(): menghasilkan direktori internal app
getCacheDir(): menghasilkan direktori internal untuk cache. Anggap saja cache ini sebagai direktori sementara untuk menyimpan file-file sementara yang dapat dihapus jika proses sudah selesai. Android dapat menghapus isi cache ini secara otomatis jika kapasitas menipis, walaupun sebaiknya app sendiri yang membersihkan isi cache jika sudah tidak digunakan.

Untuk mencoba, silahkan ganti getExternalFilesDir pada code sebelumnya dengan getFilesDir()

Menyimpan Data di SharedPreferences

Android menyediakan tiga cara untuk menyimpan data di device. Jika hanya untuk menyimpan sedikit data (beberapa variabel), gunakan shared preferences seperti pada posting ini.  Jika data cukup kompleks dan sering memerlukan pencarian (akses random), gunakan database. Jika ukuran datanya besar,  tidak perlu pencarian, dapat dishare di SD card untuk dibaca komputer, atau memerlukan format yang yang sangat spesifik, gunakan file.

SharedPreferences (SP) adalah mekanisme untuk menyimpan pasangan key-value untuk tipe data primitif (integer, double, string, booelan). SP cocok untuk penggunaan data kecil seperti menyimpan setting aplikasi dan informasi mengenai user interface. Data dalam shared preferences disimpan dalam device android dalam bentuk XML.

Ada tiga mode untuk shared preferences (SP)

MODE_PRIVATE:  hanya aplikasi yang membuat SP yang dapat mengakses data
MODE_WORLD_READABLE: aplikasi lain boleh membaca
MODE_WORLD_WRITEABLE: aplikasi lain boleh membaca sekaligus menulis.

Objek SharedPreference untuk activity diperoleh melalui method getPreferences(). Kode berikut memperlihatkan cara penggunaan SP, tambahkan dibagian create. Jangan lupa memanggil commit!



protected void onCreate(Bundle savedInstanceState) {
    //... code create ....
    
    SharedPreferences sp = getSharedPreferences("edu.upi.yudiwbs.dataku",MODE_PRIVATE);
    SharedPreferences.Editor ed = sp.edit();

    //tulis
    ed.putString("nama", "Budi Martami");
    ed.putInt("umur", 20);
    ed.commit();
    
    //baca
    String nama = sp.getString("nama","");
    int umur = sp.getInt("umur", 0);

    AlertDialog ad = new AlertDialog.Builder(this).create();
    ad.setMessage("nama:"+nama+" umur"+umur);
    ad.show();
}

Jalankan maka akan ditampilkan data yang disimpan.  Sekarang coba komentari bagian tulis

/*
ed.putString("nama", "Budi Martami");
ed.putInt("umur", 20);
ed.commit();
*/

Jalankan maka data akan tetap muncul, artinya data tersimpan secara permanen walaupun sudah keluar dari app.