Java थ्रेड के साथ एसिंक्रोनस काम

सभी Android ऐप्लिकेशन, यूज़र इंटरफ़ेस (यूआई) से जुड़ी कार्रवाइयों को मैनेज करने के लिए, मुख्य थ्रेड का इस्तेमाल करते हैं. लंबे समय से चल रही कॉल के लिए कॉल इस मुख्य थ्रेड पर की गई कार्रवाइयों की वजह से, यह फ़्रीज़ हो सकता है और काम नहीं करता है. इसके लिए उदाहरण के लिए, अगर आपका ऐप्लिकेशन मुख्य थ्रेड से नेटवर्क का अनुरोध करता है, तो आपके ऐप्लिकेशन का यूज़र इंटरफ़ेस (यूआई) तब तक फ़्रीज़ किया जाता है, जब तक इसे नेटवर्क से प्रतिक्रिया नहीं मिल जाती. अगर आप Java का इस्तेमाल करते हैं, तो लंबे समय तक चलने वाली कार्रवाइयों को मैनेज करने के लिए, अतिरिक्त बैकग्राउंड थ्रेड बना सकते हैं मुख्य थ्रेड, यूज़र इंटरफ़ेस (यूआई) अपडेट को हैंडल करना जारी रखती है.

यह गाइड बताती है कि Java प्रोग्रामिंग भाषा का इस्तेमाल करने वाले डेवलपर किसी Android ऐप्लिकेशन में एक से ज़्यादा थ्रेड को सेट अप और इस्तेमाल करने के लिए, थ्रेड पूल का इस्तेमाल किया जाता है. यह गाइड यह भी दिखाता है कि थ्रेड पर कोड कैसे रन किया जाए और कम्यूनिकेट कैसे किया जाए इनमें से किसी एक थ्रेड और मुख्य थ्रेड के बीच में खुलेगा.

कॉन करंसी लाइब्रेरी

थ्रेडिंग की बुनियादी बातें और इसके बुनियादी बातें समझना बहुत ज़रूरी है मैकेनिज़्म. हालांकि, ऐसी कई मशहूर लाइब्रेरी हैं जो उच्च स्तर की इन सिद्धांतों और इस्तेमाल के लिए तैयार यूटिलिटी के बारे में कम शब्दों में बताना डालें. इन लाइब्रेरी में Guava और Java प्रोग्रामिंग लैंग्वेज के उपयोगकर्ताओं और Coroutine के लिए, RxJava, हम Kotlin उपयोगकर्ताओं को इस्तेमाल करने का सुझाव देते हैं.

असल में, आपको अपने ऐप्लिकेशन और अपने ऐप्लिकेशन के लिए सबसे सही डेवलपमेंट टीम के साथ काम करता है. हालांकि, थ्रेडिंग के नियम पहले जैसे ही हैं.

उदाहरण के तौर पर दी गई खास जानकारी

ऐप्लिकेशन के आर्किटेक्चर की गाइड के आधार पर, इस विषय में दिए गए उदाहरणों से नेटवर्क अनुरोध करने और नतीजे को मुख्य थ्रेड पर लौटाते हैं, जहां ऐप्लिकेशन इसके बाद उस नतीजे को स्क्रीन पर दिखा सकता है.

खास तौर पर, ViewModel मुख्य थ्रेड पर मौजूद डेटा लेयर को कॉल करता है, ताकि नेटवर्क अनुरोध को ट्रिगर कर सकता है. डेटा लेयर को मुख्य थ्रेड से नेटवर्क अनुरोध को एक्ज़ीक्यूट किया जाता है और नतीजे को वापस पोस्ट किया जाता है को मुख्य थ्रेड में ले जा सकते हैं.

नेटवर्क अनुरोध के एक्ज़िक्यूशन को मुख्य थ्रेड से बाहर ले जाने के लिए, हमें हमारे ऐप्लिकेशन में दूसरे थ्रेड बनाएं.

एक से ज़्यादा थ्रेड बनाएं

थ्रेड पूल, मैनेज की गई थ्रेड का कलेक्शन होता है. इसमें टास्क बनाए जाते हैं सूची के समानांतर. नए टास्क, मौजूदा थ्रेड पर ही लागू होते हैं थ्रेड कुछ समय से इस्तेमाल में नहीं हैं. थ्रेड पूल में टास्क भेजने के लिए, ExecutorService इंटरफ़ेस. ध्यान दें कि ExecutorService के पास करने के लिए कुछ नहीं है सेवाएं के साथ, जो Android ऐप्लिकेशन का कॉम्पोनेंट है.

थ्रेड बनाना महंगा है. इसलिए, आपको सिर्फ़ एक बार थ्रेड पूल बनाना चाहिए तो आपका ऐप्लिकेशन शुरू हो जाता है. ExecutorService का इंस्टेंस सेव करना न भूलें आपकी Application क्लास या डिपेंडेंसी इंजेक्शन कंटेनर में. नीचे दिए गए उदाहरण में चार थ्रेड का एक थ्रेड पूल बनाया गया है. इसका इस्तेमाल हम कर सकते हैं बैकग्राउंड टास्क चलाए जा सकते हैं.

public class MyApplication extends Application {
    ExecutorService executorService = Executors.newFixedThreadPool(4);
}

उम्मीद के मुताबिक, थ्रेड पूल को कॉन्फ़िगर करने के अन्य तरीके भी हैं वर्कलोड. ज़्यादा जानकारी के लिए, थ्रेड पूल को कॉन्फ़िगर करना देखें.

बैकग्राउंड थ्रेड में एक्ज़ीक्यूट करें

मुख्य थ्रेड पर नेटवर्क अनुरोध करने पर, थ्रेड को इंतज़ार करना पड़ता है या ब्लॉक करें, जब तक कि उसे कोई जवाब न मिल जाए. थ्रेड को ब्लॉक किया गया है. इसलिए, ओएस यह नहीं कर सकता onDraw() को कॉल करने पर, आपका ऐप्लिकेशन फ़्रीज़ हो जाता है. इससे, हो सकता है कि ऐप्लिकेशन बंद हो जाए जवाब दिया जा रहा है (ANR) डायलॉग. इसके बजाय, इस कार्रवाई को बैकग्राउंड पर चलाएं थ्रेड.

अनुरोध करें

आइए, सबसे पहले LoginRepository की हमारी क्लास देखते हैं और जानते हैं कि यह कितनी अच्छी तरह से पढ़ाई कर रहा है नेटवर्क अनुरोध:

// 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() सिंक्रोनस है और कॉल थ्रेड को ब्लॉक करता है. मॉडल बनाने के लिए नेटवर्क अनुरोध की प्रतिक्रिया के बाद, हमारे पास अपनी Result क्लास है.

अनुरोध को ट्रिगर करना

जब उपयोगकर्ता टैप करता है, तो ViewModel नेटवर्क अनुरोध को ट्रिगर करता है. उदाहरण के लिए, चालू बटन:

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

पिछले कोड से, LoginViewModel बनाते समय मुख्य थ्रेड को ब्लॉक किया जा रहा है नेटवर्क अनुरोध की तरह. हम उस थ्रेड पूल का इस्तेमाल कर सकते हैं जिसे हमने एक जगह से दूसरी जगह ले जाने के लिए इंस्टैंशिएट किया है को लागू करता है.

डिपेंडेंसी इंजेक्शन मैनेज करना

सबसे पहले, डिपेंडेंसी इंजेक्शन के सिद्धांतों का पालन करते हुए, LoginRepository ExecutorService के बजाय एक्ज़िकेटर का इंस्टेंस लेता है, क्योंकि यह कोड को एक्ज़ीक्यूट कर रहे हैं, लेकिन थ्रेड मैनेज नहीं कर रहे हैं:

public class LoginRepository {
    ...
    private final Executor executor;

    public LoginRepository(LoginResponseParser responseParser, Executor executor) {
        this.responseParser = responseParser;
        this.executor = executor;
    }
    ...
}

एक्ज़ीक्यूटिव के execut() तरीके को Runnable तरीके की ज़रूरत होती है. Runnable सिंगल ऐब्स्ट्रैक्ट मेथड (एसएएम) इंटरफ़ेस, जिसमें run() मेथड है. इसे एक्ज़ीक्यूट किया जाता है शुरू किए जाने पर थ्रेड.

बैकग्राउंड में लागू करें

चलिए, makeLoginRequest() नाम का एक दूसरा फ़ंक्शन बनाते हैं जो बैकग्राउंड थ्रेड को एक्ज़ीक्यूट करता है और अभी के लिए रिस्पॉन्स को अनदेखा करता है:

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() तरीके में, हम कोड के ब्लॉक वाला एक नया Runnable बनाते हैं हमें बैकग्राउंड थ्रेड में एक्ज़ीक्यूट करना है—हमारे मामले में, सिंक्रोनस नेटवर्क अनुरोध का तरीका. अंदरूनी तौर पर, ExecutorService, Runnable और इसे उपलब्ध थ्रेड में एक्ज़ीक्यूट करता है.

ज़रूरी बातें

आपके ऐप्लिकेशन का कोई भी थ्रेड, अन्य थ्रेड के साथ-साथ चल सकता है. इसमें मुख्य थ्रेड भी शामिल है थ्रेड के रूप में ब्राउज़ किया है, इसलिए आपको यह पक्का करना चाहिए कि आपका कोड थ्रेड के हिसाब से सुरक्षित है. ध्यान दें कि हमारे जिसमें हम थ्रेड के बीच शेयर किए गए वैरिएबल पर लिखने से बचने, के बजाय, नहीं बदला जा सकता. यह एक अच्छा तरीका है, क्योंकि हर थ्रेड इसके साथ काम करता है अपने खुद के इंस्टेंस से जुड़ी जानकारी शामिल करता है और हम सिंक्रोनाइज़ेशन की जटिलता से बचाते हैं.

अगर आपको थ्रेड के बीच स्थिति शेयर करनी है, तो ऐक्सेस मैनेज करते समय आपको सावधानी बरतनी होगी सिंक करने के तरीके, जैसे कि लॉक का इस्तेमाल करते हुए थ्रेड के लिए. यह इसके बाहर है के बारे में ज़्यादा जानकारी मिलेगी. आम तौर पर, आपको म्यूटेबल स्टेट को शेयर करने से बचना चाहिए का इस्तेमाल करें.

मुख्य थ्रेड से बातचीत करें

पिछले चरण में, हमने नेटवर्क के अनुरोध के रिस्पॉन्स को अनदेखा कर दिया था. दिखता है, तो LoginViewModel को इसके बारे में जानने की ज़रूरत है. हम ऐसा करके ऐसा कर सकते हैं कॉलबैक का इस्तेमाल करके.

makeLoginRequest() फ़ंक्शन को पैरामीटर के तौर पर कॉलबैक लेना चाहिए, ताकि यह वैल्यू को एसिंक्रोनस रूप से दिखा सकती है. नतीजे के साथ कॉलबैक को कॉल किया जाता है नेटवर्क अनुरोध पूरा होने पर या कोई गड़बड़ी होने पर. Kotlin में, हम ये काम कर सकते हैं हाई-ऑर्डर फ़ंक्शन का इस्तेमाल करते हैं. हालांकि, Java में हमें एक नया कॉलबैक बनाना पड़ता है इंटरफ़ेस पर समान फ़ंक्शन काम करता है:

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 को अभी कॉलबैक लागू करना होगा. इसकी परफ़ॉर्मेंस अलग-अलग हो सकती है नतीजे के हिसाब से लॉजिक:

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

इस उदाहरण में, कॉलबैक को कॉलिंग थ्रेड में एक्ज़ीक्यूट किया जाता है, जो बैकग्राउंड थ्रेड. इसका मतलब है कि कॉन्टेंट में बदलाव नहीं किया जा सकता या सीधे तौर पर कम्यूनिकेट नहीं किया जा सकता यूज़र इंटरफ़ेस (यूआई) लेयर का इस्तेमाल करके, मुख्य थ्रेड पर वापस लौटा जा सकता है.

हैंडलर का इस्तेमाल करना

किसी दूसरे प्लैटफ़ॉर्म पर की जाने वाली कार्रवाई के लिए, सूची में हैंडलर का इस्तेमाल किया जा सकता है थ्रेड. जिस थ्रेड पर कार्रवाई करनी है उसे तय करने के लिए, थ्रेड के लिए लूपर का इस्तेमाल करने के लिए Handler. Looper एक ऐसा ऑब्जेक्ट है जो चलता है यह मैसेज लूप की मदद से बनाया जाता है. Handler बनाने के बाद, आपको इसके बाद, कोड के ब्लॉक को चलाने के लिए post(Runnable) तरीके का इस्तेमाल करें संबंधित थ्रेड.

Looper में एक हेल्पर फ़ंक्शन, getMainLooper() शामिल होता है, जो मुख्य थ्रेड का Looper. इसका इस्तेमाल करके, कोड को मुख्य थ्रेड में चलाया जा सकता है Handler बनाने के लिए, Looper. यह कुछ ऐसा है जिसे आप अक्सर कर सकते हैं, Handler के इंस्टेंस को उसी जगह पर सेव किया जा सकता है जहां आपने ExecutorService:

public class MyApplication extends Application {
    ExecutorService executorService = Executors.newFixedThreadPool(4);
    Handler mainThreadHandler = HandlerCompat.createAsync(Looper.getMainLooper());
}

हैंडलर को रिपॉज़िटरी में इंजेक्ट करना एक अच्छा तरीका है, क्योंकि यह आपको और ज़्यादा सुविधाएं मिलेंगी. उदाहरण के लिए, हो सकता है कि भविष्य में आप अलग थ्रेड पर टास्क शेड्यूल करने के लिए, अलग Handler. अगर आप हमेशा उसी थ्रेड पर वापस बातचीत करके, आप Handler को रिपॉज़िटरी कंस्ट्रक्टर, जैसा कि नीचे दिए गए उदाहरण में दिखाया गया है.

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

इसके अलावा, अगर आपको ज़्यादा विकल्प चाहिए, तो हर एक को Handler दें फ़ंक्शन:

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

इस उदाहरण में, डेटा स्टोर करने की जगह के makeLoginRequest में कॉलबैक पास किया गया है कॉल को मुख्य थ्रेड पर चलाया जाता है. इसका मतलब है कि यूज़र इंटरफ़ेस (यूआई) में सीधे बदलाव किया जा सकता है कॉलबैक से या यूज़र इंटरफ़ेस (यूआई) से संपर्क करने के लिए, LiveData.setValue() का इस्तेमाल करें.

थ्रेड पूल को कॉन्फ़िगर करना

आपके पास एक्ज़िकेटर हेल्पर फ़ंक्शन में से किसी एक का इस्तेमाल करके, थ्रेड पूल बनाने का विकल्प है जैसा कि पिछले उदाहरण कोड में दिखाया गया है. इसके अलावा, अगर आपको थ्रेड पूल की जानकारी में अपनी ज़रूरत के हिसाब से बदलाव करना है, तो का इस्तेमाल करें. इन्हें कॉन्फ़िगर किया जा सकता है विवरण:

  • पूल का शुरुआती और ज़्यादा से ज़्यादा साइज़.
  • लाइव टाइम और टाइम यूनिट को बनाए रखें. ऐक्टिव रखने का समय, ज़्यादा से ज़्यादा हो सकता है कि थ्रेड बंद न होने से पहले उसे इस्तेमाल न किया जा रहा हो.
  • ऐसी इनपुट सूची जिसमें Runnable टास्क होते हैं. इस सूची में ब्लॉक करने की सूची का इंटरफ़ेस. अपने ऐप्लिकेशन की ज़रूरी शर्तों को पूरा करने के लिए, ये काम किए जा सकते हैं यहां दिए गए विकल्पों में से किसी एक को चुनें. ज़्यादा जानने के लिए, क्लास देखें ThreadPoolexeutor के बारे में खास जानकारी.

यहां एक उदाहरण दिया गया है, जिसमें थ्रेड पूल का साइज़, प्रोसेसर कोर, एक सेकंड का बनाए रखने का समय, और एक इनपुट सूची.

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