Android’de Thread ve Handler, çoklu iş parçacığı (multithreading) yönetiminde birlikte kullanılan iki temel yapıdır.
Android’de UI (kullanıcı arayüzü) işlemleri sadece ana thread’de (UI thread) yapılabilir. Arka plan işlemleri (ağ isteği, veritabanı işlemi, dosya okuma/yazma) için ayrı thread’ler açmanız gerekir. Ancak bu thread’ler UI’ı doğrudan güncelleyemez.
Handler, farklı thread’ler arasında mesaj ve runnable nesneleri göndermenizi/işlemenizi sağlayan bir mekanizmadır. Genellikle bir arka plan thread’inden UI thread’ine veri göndermek için kullanılır.
public class MainActivity extends AppCompatActivity {
// UI thread'de oluşturulan Handler
private Handler uiHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
// Bu kod UI thread'de çalışır
switch (msg.what) {
case 1:
String data = msg.getData().getString("key");
textView.setText(data);
break;
}
}
};
private void startBackgroundWork() {
new Thread(new Runnable() {
@Override
public void run() {
// Bu kod arka plan thread'inde çalışır
String result = doHeavyWork();
// UI'ı güncellemek için Handler'a mesaj gönder
Message message = new Message();
message.what = 1;
Bundle bundle = new Bundle();
bundle.putString("key", result);
message.setData(bundle);
uiHandler.sendMessage(message);
}
}).start();
}
}
public class MainActivity extends AppCompatActivity {
private Handler uiHandler = new Handler(Looper.getMainLooper());
private void startBackgroundWork() {
new Thread(new Runnable() {
@Override
public void run() {
// Arka plan işlemi
String result = doHeavyWork();
// UI güncellemesini UI thread'e post et
uiHandler.post(new Runnable() {
@Override
public void run() {
// Bu kod UI thread'de çalışır
textView.setText(result);
}
});
}
}).start();
}
}
HandlerThread, kendi message queue’su ve looper’ı olan bir thread’dir. Uzun ömürlü arka plan işlemleri için idealdir.
public class MyActivity extends AppCompatActivity {
private Handler backgroundHandler;
private HandlerThread backgroundThread;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// HandlerThread oluştur ve başlat
backgroundThread = new HandlerThread("MyBackgroundThread");
backgroundThread.start();
// Bu Handler arka plan thread'inde çalışır
backgroundHandler = new Handler(backgroundThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
// Bu kod BACKGROUND thread'inde çalışır
switch (msg.what) {
case 1:
doHeavyWork();
break;
}
}
};
}
private void startBackgroundTask() {
// Arka plan thread'ine mesaj gönder
backgroundHandler.sendEmptyMessage(1);
}
@Override
protected void onDestroy() {
super.onDestroy();
// Thread'i temizle
backgroundThread.quitSafely();
}
}
Looper, bir thread’in mesaj kuyruğunu (message queue) işleten döngüdür. Her thread’in bir looper’ı olmayabilir.
- UI Thread: Otomatik olarak bir Looper’a sahiptir
- Normal Thread: Varsayılan olarak Looper’ı yoktur
// Kendi Looper'ı olan thread oluşturma
class LooperThread extends Thread {
public Handler mHandler;
@Override
public void run() {
// Looper'ı hazırla
Looper.prepare();
// Handler'ı oluştur
mHandler = new Handler(Looper.myLooper()) {
@Override
public void handleMessage(Message msg) {
// Mesajları işle
}
};
// Döngüyü başlat
Looper.loop();
}
}
Handler ile gecikmeli işlemler de yapabilirsiniz:
// 3 saniye sonra çalışacak işlem
uiHandler.postDelayed(new Runnable() {
@Override
public void run() {
// Gecikmeli işlem
textView.setText("3 saniye geçti");
}
}, 3000);
// Zamanlayıcıyı iptal etme
uiHandler.removeCallbacksAndMessages(null);
Günümüz Android geliştirmesinde, raw Thread + Handler yerine coroutines tercih edilir:
lifecycleScope.launch(Dispatchers.IO) {
val result = doHeavyWork()
withContext(Dispatchers.Main) {
textView.text = result
}
}
| Bileşen | Görevi | Çalıştığı Thread |
|---|---|---|
| Thread | Arka plan işlemini çalıştırır | Kendi thread’i |
| Handler | Mesaj/runnable gönderir ve işler | Oluşturulduğu thread |
| Looper | Mesaj kuyruğunu işletir | Bulunduğu thread |
| Message | Veri taşıyan nesne | – |
| Runnable | Çalıştırılacak kod bloğu | Handler’ın thread’i |
Executors.newFixedThreadPool(), aslında new ThreadPoolExecutor()‘un önceden ayarlanmış, basit bir versiyonudur. Ancak arka planda bazı kritik farklar var.
| Özellik | new ThreadPoolExecutor(...) | Executors.newFixedThreadPool() |
|---|---|---|
| Core pool size | İstediğiniz değeri verebilirsiniz | nThreads (3) |
| Max pool size | İstediğiniz değeri verebilirsiniz | nThreads (3) (core ile aynı) |
| Keep alive time | Siz belirlersiniz (non-core thread’lerin boşta bekleme süresi) | 0 (sıfır) – çünkü max = core olduğu için non-core thread yok |
| Zaman birimi | Siz seçersiniz (saniye, milisaniye vs.) | TimeUnit.MILLISECONDS |
| Kuyruk tipi | Herhangi bir BlockingQueue (örneğin PriorityBlockingQueue) | Sınırsız LinkedBlockingQueue |
| Thread factory | İsteğe bağlı (özel thread isimlendirme vs.) | Varsayılan DefaultThreadFactory |
| Reddetme politikası | Siz seçersiniz (AbortPolicy, CallerRunsPolicy vs.) | Varsayılan AbortPolicy (dolu kuyrukta hata fırlatır) |
newFixedThreadPool() sınırsız bir kuyruk (LinkedBlockingQueue) kullanır.
- Eğer n thread de meşgulse, gelecek yeni görevler kuyruğa eklenir.
- Bellek riski: Görevler çok hızlı eklenir ve işlenemezse, kuyruk sonsuz büyüyebilir → OutOfMemoryError alabilirsiniz.
new ThreadPoolExecutor() ile sınırlı bir kuyruk (örneğin ArrayBlockingQueue(10)) belirleyerek bu riski kontrol altına alabilirsiniz.
newFixedThreadPool(n):
- Sabit sayıda thread. İhtiyaçtan fazla thread açılmaz.
- Görev sayısı artarsa sadece kuyruk büyür.
new ThreadPoolExecutor() ile örneğin şöyle bir konfigürasyon yapabilirsiniz:
corePoolSize = 2 maxPoolSize = 5 keepAliveTime = 30 saniye Kuyruk = ArrayBlockingQueue(10)
Bu durumda:
- 2 thread her zaman canlı.
- Kuyruk dolana kadar görevler kuyruğa girer.
- Kuyruk dolunca, max 5’e kadar yeni thread açılır.
- 30 saniye boş kalan thread’ler sonlandırılır.
Bu esneklik newFixedThreadPool ile mümkün değildir.
- Basit ve sabit sayıda işlem yapacaksanız (örneğin 3 ardışık network isteği) →
Executors.newFixedThreadPool(3)yeterlidir. - Kuyruk taşması riski olan, kaynakları kontrol etmek istediğiniz durumlarda →
new ThreadPoolExecutor()kullanarak kuyruğu sınırlandırın ve reddetme politikası ekleyin.
// 1. Hazır fixed thread pool:
ExecutorService fixed = Executors.newFixedThreadPool(3);
// 2. Aynı davranışı elle yazmak (newFixedThreadPool'un içi aslında budur):
ExecutorService manual = new ThreadPoolExecutor(
3, 3,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>() // ← Sınırsız kuyruk!
);
// 3. Daha kontrollü, sınırlı kuyruklu versiyon:
ExecutorService controlled = new ThreadPoolExecutor(
2, 4,
30L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10), // ← En fazla 10 görev kuyruklanır
new ThreadPoolExecutor.CallerRunsPolicy() // ← Kuyruk dolarsa çağıran thread işi yapar
);
Executors.newFixedThreadPool(n)– Kod sadeliği ve sabit sayıda thread için iyidir, ancak sınırsız kuyruk nedeniyle bellek taşmasına yol açabilir.new ThreadPoolExecutor()– Tüm parametreleri kontrol etmenizi sağlar: thread sayıları, kuyruk tipi ve boyutu, bekleme süreleri, reddetme politikaları. Kritik sistemlerde ve Android’de (mobil cihazlarda sınırlı RAM nedeniyle) tercih edilmelidir.
ThreadPoolExecutor() için tanımlanan kuyruk dizisi dolduğunda bir reddetme politikası ekleyerek, kuyruk dolduğunda ne olacağını belirleyebilirsiniz:
mTaskQueue = new LinkedBlockingQueue<Runnable>(100);
mForBackgroundTasks = new ThreadPoolExecutor(
NUMBER_OF_CORES,
NUMBER_OF_CORES * 5,
1,
TimeUnit.SECONDS,
mTaskQueue,
new BackgroundThreadFactory(),
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// Çağıran thread'in işi yapmasını bekle (backpressure)
if (!executor.isShutdown()) {
try {
executor.getQueue().put(r); // Bu bloklayıcıdır!
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
);
ya da Java’nın hazır politikalarından birini kullanabilirsiniz:
// Seçenek A: Kuyruk dolunca çağıran thread işi yapsın (backpressure oluşturur)
mForBackgroundTasks = new ThreadPoolExecutor(
NUMBER_OF_CORES,
NUMBER_OF_CORES * 5,
1,
TimeUnit.SECONDS,
mTaskQueue,
new BackgroundThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy() // ← Çağıran thread'de çalıştır
);
// Seçenek B: Kuyruk dolunca en eski görevi atıp yeni görevi ekle
mForBackgroundTasks = new ThreadPoolExecutor(
NUMBER_OF_CORES,
NUMBER_OF_CORES * 5,
1,
TimeUnit.SECONDS,
mTaskQueue,
new BackgroundThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy() // ← En eski görevi sil
);
// Seçenek C: Sessizce reddet (görev kaybolur - burada dikkatli olmak gerekiyor!)
mForBackgroundTasks = new ThreadPoolExecutor(
NUMBER_OF_CORES,
NUMBER_OF_CORES * 5,
1,
TimeUnit.SECONDS,
mTaskQueue,
new BackgroundThreadFactory(),
new ThreadPoolExecutor.DiscardPolicy() // ← Görevi yok say
);