Selasa, 25 Oktober 2016

Contoh Thread untuk OS

Multithreading Di Android

Pada program yang saya buat di artikel Belajar Memakai Implicit Intent Di Android, sistem operasi Android akan terlihat seperti hang bila saya membaca file dengan ukuran besar. Tombol yang ada di perangkat keras juga tidak bisa dipakai, misalnya saya tidak membatalkan proses dan tidak bisa kembali ke menu utama. Mengapa demikian? Hal ini berkaitan dengan threading (concurrency). Setiap aplikasi Android dimulai dengan sebuah thread tunggal yang diberi nama ‘main’. Semua operasi yang berkaitan dengan UI seperti event ditangani oleh thread ini. Pada Swing, thread ini memiliki fungsi yang sama seperti event dispatch thread (EDT). Bedanya, di Swing, aplikasi tidak langsung mulai dari EDT melainkan thread non-UI (pengguna memakai SwingUtilities.invokeLater() atauSwingUtilities.invokeAndWait() untuk mengerjakan kode program di EDT). Bila saya mengerjakan sebuah proses yang sangat lama di thread UI, maka ia tidak bisa bekerja menangani event UI. Ia akan menunggu hingga proses lama ini selesai baru bisa lanjut bekerja. Ini yang membuat aplikasi menjadi tidak responsif.
Bila pada aplikasi Java, saya menggunakan JVisualVM (bawaan JDK) untuk memantau thread yang ada di sebuah aplikasi, maka untuk Android, saya dapat menggunakan Android Device Monitor (bawaan Android SDK). Sebagai contoh, bila saya memilih aplikasi dan men-klik icon Update Threads, saya dapat melihat tampilan seperti pada gambar berikut ini di tab Threads:
Melihat thread di aplikasi Android
Walaupun ada banyak thread yang dibuat, satu-satunya thread yang menjalankan kode program aplikasi saya saat ini adalah thread 1 dengan nama ‘main’. Thread lainnya adalah bawaan Android, misalnya thread ‘GC’ untuk membebaskan memori yang tidak dipakai lagi, thread ‘JDWP’ untuk keperluan debugging, dan sebagainya.
Sekarang, saya akan melakukan perubahan pada kode program di method tampilkan() agar ia mengerjakan tugasnya di thread terpisah:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
private void tampilkan(Intent data) {
    Thread.start('ProsesFile') {
        TextView output = (TextView) findViewById(OUTPUT_VIEW_ID)
        Uri uri = data.getData()
        byte[] bytes = getContentResolver().openInputStream(uri).bytes
        StringBuilder hexdump = new StringBuilder()
        StringBuilder ascii = new StringBuilder()
        int i
        for (i = 0; i < bytes.length; i++) {
            hexdump.append(String.format("%02x", bytes[i]))
            hexdump.append(' ')
            ascii.append(Character.isLetterOrDigit(bytes[i]) ? (char) bytes[i] : '.')
            if ((i > 0) && (((i + 1) % 8) == 0)) {
                hexdump.append(ascii.toString())
                hexdump.append('n')
                ascii = new StringBuilder()
            }
 
            output.post({
                output.setText("Memproses byte $i dari ${bytes.length}")
            } as Runnable)
 
            Thread.sleep(1)
        }
        if (ascii) {
            (1..(8 - ((i - 1) % 8))).each { hexdump.append('   ') }
            hexdump.append(ascii.toString())
        }
 
        output.post({
            output.setText(hexdump.toString())
            Intent sendIntent = new Intent(Intent.ACTION_SEND)
            sendIntent.putExtra(Intent.EXTRA_TEXT, hexdump.toString())
            sendIntent.setType('text/plain')
            shareActionProvider.setShareIntent(sendIntent)
        } as Runnable)
 
    }
}  
Pada kode program di atas, saya memakai Thread.start() dari Groovy untuk membuat sebuah thread baru dengan nama ‘ProsesFile’. Selama berada di dalam thread baru ini, saya tidak boleh mengerjakan method yang berhubungan dengan UI secara langsung. Hal ini karena method pada UI tidak thread-safe. Oleh sebab itu, untuk menjadwalkan kode program agar dikerjakan oleh thread ‘Main’ (yang menangani UI), saya menggunakan post().
Bila saya menjalankan aplikasi, ia akan lebih responsif. Saya juga akan menemukan sebuah thread baru dengan nama ‘ProsesFile’ bila memantau aplikasi melalui Android Device Monitor, seperti yang terlihat pada gambar berikut ini:
Membuat thread baru
Masalah lain yang saya hadapi adalah penggunaan memori yang besar karena memproses isi file sekaligus. Oleh sebab itu, saya akan menggunakan seek bar sehingga saya bisa menampilkan isi file per kilobyte sesuai dengan posisi seek bar. Agar lebih mudah dalam merancang UI, saya akan menggunakan designer bawaan Android Studio. Untuk itu, saya membuat folder baru bernama layout di lokasi src/main/res. Setelah itu, saya men-klik kanan pada folder layout dan memilih NewLayout Reource Folder. Saya mengisi dialog yang muncul denganmainscreen pada File name dan RelativeLayout pada Root element. Saya kemudian merancang UI sehingga terlihat seperti berikut ini:
Merancang UI secara visual
Saya perlu mengubah method onCreate() untuk membaca layout dari XML sehingga isinya menjadi seperti:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MainActivity extends Activity implements SeekBar.OnSeekBarChangeListener {
 
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.mainscreen)
    ((SeekBar) findViewById(R.id.posisi)).onSeekBarChangeListener = this
    if (intent.data) {
        tampilkan(intent)
    }
  }
 
  ...
 
  @Override
  void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {}
 
  @Override
  void onStartTrackingTouch(SeekBar seekBar) {}
 
  @Override
  void onStopTrackingTouch(SeekBar seekBar) {}
 
}
Kali ini saya perlu men-share variabel bytes yang berisi data lengkap yang sudah dibaca agar dapat diakses dari thread berbeda. Karena ia bytes hanya ditulis sekali, setelah itu dibaca oleh beberapa thread berbeda, saya boleh saja men-share variabel ini dengan cara yang tidak thread-safe. Akan tetapi, agar lebih aman, saya akan menggunakan ReadWriteLock sehingga pada saat variabel ini ditulis oleh sebuah thread, maka thread lain yang ingin membaca harus menunggu sampai penulisan selesai. Groovy menyediakan AST transformation@WithWriteLock dan @WithReadLock untuk keperluan tersebut. Sebagai contoh, saya bisa menambahkannya seperti:
1
2
3
4
5
6
7
8
9
10
11
12
13
public class MainActivity extends Activity implements SeekBar.OnSeekBarChangeListener {
 
  private byte[] bytes
 
  ...
 
  @Override @WithReadLock
  void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {}
 
  @WithWriteLock
  private void tampilkan(Intent data) { ... }
 
}
Sekarang, fungsi dari kode program tampilkan() hanya untuk membaca isi file saja:
1
2
3
4
5
6
7
8
9
10
11
@WithWriteLock
private void tampilkan(Intent data) {
    Thread.start('BacaFile') {
        Uri uri = data.getData()
        byte[] bytes = getContentResolver().openInputStream(uri).bytes
        SeekBar seekBar = (SeekBar) findViewById(R.id.posisi)
        seekBar.post({
            seekBar.max = bytes.length / 1024
        })
    }
}
Kode program di onProgressChanged akan dikerjakan setiap kali pengguna menggeser seek bar. Karena proses untuk menghasilkan hexdump cukup lama, maka saya akan mengerjakannya pada sebuah thread terpisah, misalnya:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Override @WithReadLock
void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
    Thread.start('ProsesHexdump') {
        TextView output = (TextView) findViewById(R.id.output)
        StringBuilder hexdump = new StringBuilder()
        StringBuilder ascii = new StringBuilder()
        int i = progress * 1024
        int max = Math.min(i + 1024, bytes.length)
        for (; i < max; i++) {
            hexdump.append(String.format("%02x ", bytes[i]))
            ascii.append(Character.isLetterOrDigit(bytes[i]) ? (char) bytes[i] : '.')
            if ((i > 0) && (((i + 1) % 8) == 0)) {
                hexdump.append(ascii.toString())
                hexdump.append('n')
                ascii = new StringBuilder()
            }
        }
        if (ascii) {
            (1..(8 - (i % 8))).each { hexdump.append('   ') }
            hexdump.append(ascii.toString())
        }
 
        output.post({
            String hasil = hexdump.toString()
            output.setText(hasil)
            Intent sendIntent = new Intent(Intent.ACTION_SEND)
            sendIntent.putExtra(Intent.EXTRA_TEXT, hasil)
            sendIntent.setType('text/plain')
            shareActionProvider.setShareIntent(sendIntent)
        } as Runnable)
    }
}
Sekarang, setiap kali membaca file, thread ‘BacaFile’ akan dibuat. Setiap kali seek bar digeser, thread‘ProsesHexDump’ akan tercipta. Thread baru ini juga akan menunggu hingga ‘BacaFile’ selesai dikerjakan bila thread tersebut masih bekerja.

1 komentar:

  1. Casino - Jtm Hub
    Casino - 울산광역 출장마사지 The best place to play slots with real 김제 출장샵 money. Get a quick and easy 아산 출장샵 payment and experience from the casino 파주 출장마사지 directly. Rating: 4.3 · ‎22 votes 청주 출장샵

    BalasHapus