Java iş parçacıklarıyla eşzamansız çalışma

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
    );
    ...
}