Tüm Android uygulamaları, kullanıcı arayüzü işlemlerini yönetmek için bir ana iş parçacığı kullanır. Uzun süreli arama Bu ana iş parçacığındaki işlemler, donmaya ve yanıt verilmemesine neden olabilir. Örneğin, Örneğin, uygulamanız ana iş parçacığından bir ağ isteği gönderirse uygulamanızın kullanıcı arayüzü, ağ yanıtını alana kadar dondurulur. Java kullanıyorsanız Aynı zamanda uzun süreli işlemleri yönetmek için ek arka plan iş parçacıkları ana iş parçacığı kullanıcı arayüzü güncellemelerini işlemeye devam eder.
Bu kılavuzda, Java Programlama Dili kullanan geliştiricilerin ileti dizisi havuzunu kullanın. Bu kılavuz bir iş parçacığında çalıştırılacak kodu nasıl tanımlayacağınızı ve ileti dizilerinden biri ile ana ileti dizisi arasında.
Eşzamanlılık kitaplıkları
İleti dizisi oluşturma ile ilgili temel bilgileri ve iş parçacığının temel unsurlarını mekanizmalar. Bununla birlikte, yüksek seviyeli çok sayıda popüler kütüphane vardır. soyutlamaları ve veri aktarımına ilişkin kullanıma hazır yardımcı programları ileti dizileri arasında geçiş yapın. Bu kütüphaneler arasında Guava ve Java Programlama Dili kullanıcıları ve Coroutines için RxJava Kotlin kullanıcıları için öneririz.
Pratikte, uygulamanıza ve ürününüze en uygun olan yöntemi seçmelisiniz ileti dizisi kuralları aynı kalır.
Örneklere genel bakış
Uygulama mimarisi rehberi doğrultusunda, bu konudaki örnekler ağ isteğini kullanır ve sonucu ana iş parçacığına döndürür; uygulama burada o sonuç ekranda görüntülenebilir.
Özellikle, ViewModel
ana iş parçacığındaki veri katmanını
ağ isteğini tetikler. Veri katmanı,
ana iş parçacığı dışına ağ isteğinin yürütülmesi ve sonucun geri gönderilmesi
ana iş parçacığına yönlendirir.
Ağ isteğinin yürütülmesini ana iş parçacığının dışına taşımak için ileti dizileri oluşturmamıza yardımcı oluyor.
Birden çok ileti dizisi oluşturma
İleti dizisi havuzu, görevleri çalıştıran ve yönetilen bir ileti dizisi koleksiyonudur.
görebilirsiniz. Yeni görevler, mevcut iş parçacıklarında şu şekilde yürütülür:
ileti dizileri boşa çıkar. Bir iş parçacığı havuzuna görev göndermek için şunu kullanın:
ExecutorService
arayüzü. ExecutorService
için yapılacak bir işlem yoktur
Android uygulama bileşeni olan Hizmetler ile.
İleti dizisi oluşturmak pahalı bir işlemdir; bu nedenle, iş parçacığı havuzunu
ilk kullanıma hazırlanmasını sağlar. ExecutorService
örneğini kaydettiğinizden emin olun
Application
sınıfınızda veya bir bağımlılık ekleme kapsayıcısında.
Aşağıdaki örnekte, Google Etiket Yöneticisi'ni kullanarak
arka plan görevlerini
kullanabilir.
public class MyApplication extends Application {
ExecutorService executorService = Executors.newFixedThreadPool(4);
}
İş parçacığı havuzunu beklenenlere bağlı olarak yapılandırmanın başka yolları da vardır. yardımcı olabilir. Daha fazla bilgi için İleti dizisi havuzu yapılandırma bölümüne bakın.
Bir arka plan ileti dizisinde yürütme
Ana iş parçacığında bir ağ isteği yapmak, iş parçacığının beklemesine neden olur veya
engelleme başlıklı makaleyi inceleyin. İleti dizisi engellendiği için işletim sistemi şunları yapamaz:
onDraw()
çağrısı yaptığınızda uygulamanız donar ve bu da Uygulama Notu'na yol açabilir
Yanıtlanıyor (ANR) iletişim kutusu. Bunun yerine, bu işlemi arka planda çalıştıralım.
ileti dizisi.
İstekte bulunma
İlk olarak LoginRepository
dersimize bakalım ve nasıl bir performans gösterdiğine bakalım.
ağ isteği:
// Result.java
public abstract class Result<T> {
private Result() {}
public static final class Success<T> extends Result<T> {
public T data;
public Success(T data) {
this.data = data;
}
}
public static final class Error<T> extends Result<T> {
public Exception exception;
public Error(Exception exception) {
this.exception = exception;
}
}
}
// LoginRepository.java
public class LoginRepository {
private final String loginUrl = "https://example.com/login";
private final LoginResponseParser responseParser;
public LoginRepository(LoginResponseParser responseParser) {
this.responseParser = responseParser;
}
public Result<LoginResponse> makeLoginRequest(String jsonBody) {
try {
URL url = new URL(loginUrl);
HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection();
httpConnection.setRequestMethod("POST");
httpConnection.setRequestProperty("Content-Type", "application/json; charset=utf-8");
httpConnection.setRequestProperty("Accept", "application/json");
httpConnection.setDoOutput(true);
httpConnection.getOutputStream().write(jsonBody.getBytes("utf-8"));
LoginResponse loginResponse = responseParser.parse(httpConnection.getInputStream());
return new Result.Success<LoginResponse>(loginResponse);
} catch (Exception e) {
return new Result.Error<LoginResponse>(e);
}
}
}
makeLoginRequest()
eşzamanlı ve görüşme dizisini engelliyor.
için kendi Result
sınıfımızı oluşturuyoruz.
İsteği tetikle
ViewModel
, kullanıcı örneğin bir cihaza dokunduğunda ağ isteğini tetikler.
bir düğme:
public class LoginViewModel {
private final LoginRepository loginRepository;
public LoginViewModel(LoginRepository loginRepository) {
this.loginRepository = loginRepository;
}
public void makeLoginRequest(String username, String token) {
String jsonBody = "{ username: \"" + username + "\", token: \"" + token + "\" }";
loginRepository.makeLoginRequest(jsonBody);
}
}
Önceki kodla LoginViewModel
,
ağ isteği. Taşımak için örneklendirdiğimiz iş parçacığı havuzunu kullanabiliriz
arka plan iş parçacığına
dönüştürür.
Bağımlılık yerleştirmeyi yönetme
İlk olarak, bağımlılık yerleştirme ilkeleri doğrultusunda LoginRepository
ExecutorService
yerine Executor örneğini alır çünkü
kodu yürütme ve iş parçacıklarını yönetmeme:
public class LoginRepository {
...
private final Executor executor;
public LoginRepository(LoginResponseParser responseParser, Executor executor) {
this.responseParser = responseParser;
this.executor = executor;
}
...
}
Yürütücünün execute() yöntemi bir Runnable alır. Runnable
,
Şurada yürütülen run()
yönteminin kullanıldığı Tek Soyut Yöntemi (SAM) arayüzü
bir ileti dizisidir.
Arka planda çalıştır
Şunu hareket eden makeLoginRequest()
adında başka bir fonksiyon oluşturalım:
yürütme işlemini arka plan ileti dizisine uygular ve yanıtı şimdilik yoksayar:
public class LoginRepository {
...
public void makeLoginRequest(final String jsonBody) {
executor.execute(new Runnable() {
@Override
public void run() {
Result<LoginResponse> ignoredResponse = makeSynchronousLoginRequest(jsonBody);
}
});
}
public Result<LoginResponse> makeSynchronousLoginRequest(String jsonBody) {
... // HttpURLConnection logic
}
...
}
execute()
yönteminde, kod bloğunu içeren yeni bir Runnable
oluştururuz
arka plan iş parçacığında yürütmek istiyoruz; bizim örneğimizde, eşzamanlı ağ
istekte bulunabilirsiniz. Dahili olarak ExecutorService
, Runnable
ve
dosyayı mevcut bir iş parçacığında yürütür.
Dikkat edilmesi gereken noktalar
Uygulamanızdaki tüm ileti dizileri, ana ileti dizisi de dahil olmak üzere diğer iş parçacıklarına paralel olarak çalışabilir. ; bu nedenle, kodunuzun iş parçacığı açısından güvenli olduğundan emin olmanız gerekir. Bu belgenin Örneğin, ileti dizileri arasında paylaşılan değişkenlere yazmaktan, sabit verileri kullanabilirsiniz. Her ileti dizisi Böylece senkronizasyonun karmaşıklığından kaçınmış oluruz.
İleti dizileri arasında durum paylaşmanız gerekiyorsa erişimi yönetirken dikkatli olmanız gerekir. kilit gibi senkronizasyon mekanizmalarını kullanan iş parçacıklarından. Bu, bu kılavuzun kapsamına bakın. Genel olarak, değişken durumu paylaşmaktan kaçınmalısınız. ileti dizileri arasında seçim yapın.
Ana ileti dizisiyle iletişim kurma
Önceki adımda ağ isteği yanıtını yoksaydık.
LoginViewModel
bunu bilmesi gerekir. Bunu planlandığı takdirde
geri çağırma özelliklerini kullanın.
makeLoginRequest()
işlevi, parametre olarak bir geri çağırmayı almalıdır.
bir değeri eşzamansız olarak döndürebilir. Sonucu içeren geri çağırmaya
Ağ isteği tamamlandığında veya bir hata oluştuğunda. Kotlin'de
daha yüksek düzeyli bir işlev kullanın. Ancak, Java'da, yeni bir
arayüzünde aynı işleve sahip olacaktır:
interface RepositoryCallback<T> {
void onComplete(Result<T> result);
}
public class LoginRepository {
...
public void makeLoginRequest(
final String jsonBody,
final RepositoryCallback<LoginResponse> callback
) {
executor.execute(new Runnable() {
@Override
public void run() {
try {
Result<LoginResponse> result = makeSynchronousLoginRequest(jsonBody);
callback.onComplete(result);
} catch (Exception e) {
Result<LoginResponse> errorResult = new Result.Error<>(e);
callback.onComplete(errorResult);
}
}
});
}
...
}
ViewModel
uygulamasının geri çağırmayı şimdi uygulaması gerekiyor. Farklı şekillerde performans gösterebilir
mantığını kullanır:
public class LoginViewModel {
...
public void makeLoginRequest(String username, String token) {
String jsonBody = "{ username: \"" + username + "\", token: \"" + token + "\" }";
loginRepository.makeLoginRequest(jsonBody, new RepositoryCallback<LoginResponse>() {
@Override
public void onComplete(Result<LoginResponse> result) {
if (result instanceof Result.Success) {
// Happy path
} else {
// Show error in UI
}
}
});
}
}
Bu örnekte, geri çağırma işlemi arka plan ileti dizisi. Bu, değişiklik yapamayacağınız veya doğrudan ana iş parçacığına dönene kadar kullanıcı arayüzü katmanında.
İşleyicileri kullanma
Farklı bir cihazda gerçekleştirilecek bir işlemi sıraya koymak için İşleyici kullanabilirsiniz.
ileti dizisi. İşlemin çalıştırılacağı iş parçacığını belirtmek için
Handler
, ileti dizisi için bir Looper kullanarak. Looper
,
ileti dizisi oluşturabilirsiniz. Handler
oluşturduktan sonra
post(Runnable) yöntemini kullanarak
ileti dizisi.
Looper
, şunu alan bir yardımcı işlev (getMainLooper()) içerir:
Ana ileti dizisinin Looper
kadarı. Bunu kullanarak ana iş parçacığında kodu çalıştırabilirsiniz:
Handler
oluşturmak için Looper
. Bu çok sık yaptığınız bir şey olduğundan
Ayrıca, Handler
öğesinin bir örneğini kaydettiğiniz yere de kaydedebilirsiniz
ExecutorService
:
public class MyApplication extends Application {
ExecutorService executorService = Executors.newFixedThreadPool(4);
Handler mainThreadHandler = HandlerCompat.createAsync(Looper.getMainLooper());
}
İşleyiciyi depoya yerleştirmek iyi bir uygulamadır.
daha fazla esneklik sağlayabilir. Örneğin, ileride, bir de dahil olmak üzere
görevleri ayrı bir ileti dizisinde planlamak için farklı Handler
kullanın. Her zaman
ileti dizisine geri bildirimde bulunursanız, Handler
öğesini şuraya aktarabilirsiniz:
deposu kurucusudur.
public class LoginRepository {
...
private final Handler resultHandler;
public LoginRepository(LoginResponseParser responseParser, Executor executor,
Handler resultHandler) {
this.responseParser = responseParser;
this.executor = executor;
this.resultHandler = resultHandler;
}
public void makeLoginRequest(
final String jsonBody,
final RepositoryCallback<LoginResponse> callback
) {
executor.execute(new Runnable() {
@Override
public void run() {
try {
Result<LoginResponse> result = makeSynchronousLoginRequest(jsonBody);
notifyResult(result, callback);
} catch (Exception e) {
Result<LoginResponse> errorResult = new Result.Error<>(e);
notifyResult(errorResult, callback);
}
}
});
}
private void notifyResult(
final Result<LoginResponse> result,
final RepositoryCallback<LoginResponse> callback,
) {
resultHandler.post(new Runnable() {
@Override
public void run() {
callback.onComplete(result);
}
});
}
...
}
Alternatif olarak, daha fazla esneklik istiyorsanız her bir reklamveren için Handler
verebilirsiniz.
işlev:
public class LoginRepository {
...
public void makeLoginRequest(
final String jsonBody,
final RepositoryCallback<LoginResponse> callback,
final Handler resultHandler,
) {
executor.execute(new Runnable() {
@Override
public void run() {
try {
Result<LoginResponse> result = makeSynchronousLoginRequest(jsonBody);
notifyResult(result, callback, resultHandler);
} catch (Exception e) {
Result<LoginResponse> errorResult = new Result.Error<>(e);
notifyResult(errorResult, callback, resultHandler);
}
}
});
}
private void notifyResult(
final Result<LoginResponse> result,
final RepositoryCallback<LoginResponse> callback,
final Handler resultHandler
) {
resultHandler.post(new Runnable() {
@Override
public void run() {
callback.onComplete(result);
}
});
}
}
Bu örnekte, geri çağırma kod deposunun makeLoginRequest
öğesine iletildi
çağrısı, ana iş parçacığında yürütülür. Bu sayede, kullanıcı arayüzünü doğrudan değiştirebilirsiniz.
veya kullanıcı arayüzüyle iletişim kurmak için LiveData.setValue()
işlevini kullanın.
İş parçacığı havuzu yapılandırma
Yürütücü yardımcı işlevlerinden birini kullanarak iş parçacığı havuzu oluşturabilirsiniz. önceki örnek kodda gösterildiği gibi önceden tanımlanmış ayarlarla değiştirin. Alternatif olarak: ileti dizisi havuzunun ayrıntılarını özelleştirmek isterseniz bir ThreadPoolExecutor kullanarak doğrudan örnek verin. Aşağıdakileri yapılandırabilirsiniz: ayrıntılar:
- Başlangıç ve maksimum havuz boyutu.
- Etkin zaman ve zaman birimi Canlı tutma süresi, bir kişinin ileti dizisi kapatılmadan önce boşta kalabilir.
Runnable
görev içeren bir giriş sırası. Bu sıra, Engelleme Kuyruğu arayüzü. Uygulamanızın şartlarına uymak için şunları yapabilirsiniz: mevcut sıra uygulamaları arasından istediğinizi seçin. Daha fazla bilgi edinmek için sınıfa bakın ThreadPoolExecutor genel bakışı.
Aşağıda, toplam işlemci çekirdeği, bir saniyelik canlı tutma süresi ve giriş sırası içerir.
public class MyApplication extends Application {
/*
* Gets the number of available cores
* (not always the same as the maximum number of cores)
*/
private static int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();
// Instantiates the queue of Runnables as a LinkedBlockingQueue
private final BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>();
// Sets the amount of time an idle thread waits before terminating
private static final int KEEP_ALIVE_TIME = 1;
// Sets the Time Unit to seconds
private static final TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;
// Creates a thread pool manager
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
NUMBER_OF_CORES, // Initial pool size
NUMBER_OF_CORES, // Max pool size
KEEP_ALIVE_TIME,
KEEP_ALIVE_TIME_UNIT,
workQueue
);
...
}