Κάθε προγραμματιστής Android, σε ένα ή το άλλο σημείο, πρέπει να ασχοληθεί με τα θέματα στην εφαρμογή τους.
Όταν ξεκινά μια εφαρμογή στο Android, δημιουργεί το πρώτο νήμα εκτέλεσης, γνωστό ως «κύριο» νήμα. Το κύριο νήμα είναι υπεύθυνο για την αποστολή συμβάντων στα κατάλληλα widget διεπαφής χρήστη, καθώς και για την επικοινωνία με στοιχεία από το κιτ εργαλείων Android UI.
Για να διατηρήσετε την εφαρμογή σας απόκριση, είναι σημαντικό να αποφύγετε τη χρήση του κύριου νήματος για να εκτελέσετε οποιαδήποτε λειτουργία που μπορεί να καταλήξει να τη διατηρεί αποκλεισμένη.
Οι λειτουργίες δικτύου και οι κλήσεις βάσης δεδομένων, καθώς και η φόρτωση ορισμένων στοιχείων, είναι κοινά παραδείγματα λειτουργιών που πρέπει να αποφεύγετε στο κύριο νήμα. Όταν κληθούν στο κύριο νήμα, καλούνται συγχρονισμένα, πράγμα που σημαίνει ότι το περιβάλλον χρήστη θα παραμείνει εντελώς ανενεργό έως ότου ολοκληρωθεί η λειτουργία. Για το λόγο αυτό, εκτελούνται συνήθως σε ξεχωριστά νήματα, τα οποία αποφεύγουν έτσι το μπλοκάρισμα του UI ενώ εκτελούνται (δηλαδή, εκτελούνται ασύγχρονα από το UI).
Το Android παρέχει πολλούς τρόπους δημιουργίας και διαχείρισης νημάτων και υπάρχουν πολλές βιβλιοθήκες τρίτων μερών που κάνουν τη διαχείριση νημάτων πολύ πιο ευχάριστη. Ωστόσο, με τόσες πολλές διαφορετικές προσεγγίσεις, η επιλογή της σωστής μπορεί να είναι αρκετά συγκεχυμένη.
πώς να χρησιμοποιήσετε το flexbox στο css
Σε αυτό το άρθρο, θα μάθετε για ορισμένα κοινά σενάρια στο Ανάπτυξη Android όπου το νήμα καθίσταται απαραίτητο και μερικές απλές λύσεις που μπορούν να εφαρμοστούν σε αυτά τα σενάρια και άλλα.
Στο Android, μπορείτε να κατηγοριοποιήσετε όλα τα στοιχεία νήματος σε δύο βασικές κατηγορίες:
AsyncTask
είναι το πιο βασικό στοιχείο Android για νήμα. Είναι απλό στη χρήση και μπορεί να είναι καλό για βασικά σενάρια.
Χρήση δείγματος:
public class ExampleActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); new MyTask().execute(url); } private class MyTask extends AsyncTask { @Override protected String doInBackground(String... params) { String url = params[0]; return doSomeWork(url); } @Override protected void onPostExecute(String result) { super.onPostExecute(result); // do something with result } } }
AsyncTask
, ωστόσο, υπολείπεται εάν χρειάζεστε την αναβαλλόμενη εργασία σας για να εκτελέσετε πέρα από τη διάρκεια ζωής της δραστηριότητας / τμήματος. Αξίζει να σημειωθεί ότι ακόμη και κάτι τόσο απλό όσο η περιστροφή της οθόνης μπορεί να προκαλέσει την καταστροφή της δραστηριότητας.
Οι φορτωτές είναι η λύση για το παραπάνω πρόβλημα. Οι φορτωτές μπορούν να σταματήσουν αυτόματα όταν καταστραφεί η δραστηριότητα και μπορούν επίσης να επανεκκινήσουν μετά την αναδημιουργία της δραστηριότητας.
Υπάρχουν κυρίως δύο τύποι φορτωτών: AsyncTaskLoader
και CursorLoader
. Θα μάθετε περισσότερα για το CursorLoader
αργότερα σε αυτό το άρθρο.
AsyncTaskLoader
είναι παρόμοιο με το AsyncTask
, αλλά λίγο πιο περίπλοκο.
Χρήση δείγματος:
public class ExampleActivity extends Activity{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getLoaderManager().initLoader(1, null, new MyLoaderCallbacks()); } private class MyLoaderCallbacks implements LoaderManager.LoaderCallbacks { @Override public Loader onCreateLoader(int id, Bundle args) { return new MyLoader(ExampleActivity.this); } @Override public void onLoadFinished(Loader loader, Object data) { } @Override public void onLoaderReset(Loader loader) { } } private class MyLoader extends AsyncTaskLoader { public MyLoader(Context context) { super(context); } @Override public Object loadInBackground() { return someWorkToDo(); } } }
Service
είναι ένα στοιχείο που είναι χρήσιμο για την εκτέλεση μεγάλων (ή πιθανώς μεγάλων) λειτουργιών χωρίς κανένα περιβάλλον εργασίας χρήστη.
Service
τρέχει στο κύριο νήμα της διαδικασίας φιλοξενίας του? η υπηρεσία δεν δημιουργεί το δικό της νήμα και δεν εκτελείται σε ξεχωριστή διαδικασία, εκτός εάν ορίσετε διαφορετικά.
Χρήση δείγματος:
public class ExampleService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { doSomeLongProccesingWork(); stopSelf(); return START_NOT_STICKY; } @Nullable @Override public IBinder onBind(Intent intent) { return null; } }
Με Service
, είναι τα δικα σου ευθύνη να το σταματήσει όταν ολοκληρωθεί η εργασία του καλώντας είτε το stopSelf()
ή το stopService()
μέθοδος.
Όπως Service
, IntentService
τρέχει σε ξεχωριστό νήμα και σταματά αυτόματα μετά την ολοκλήρωση της εργασίας του.
IntentService
χρησιμοποιείται συνήθως για σύντομες εργασίες που δεν χρειάζεται να επισυνάπτονται σε καμία διεπαφή χρήστη.
Χρήση δείγματος:
public class ExampleService extends IntentService { public ExampleService() { super('ExampleService'); } @Override protected void onHandleIntent(Intent intent) { doSomeShortWork(); } }
Μερικές φορές μπορεί να θέλετε να στείλετε ένα αίτημα API σε έναν διακομιστή χωρίς να χρειάζεται να ανησυχείτε για την απάντησή του. Για παράδειγμα, ενδέχεται να στέλνετε ένα διακριτικό εγγραφής push στο παρασκήνιο της εφαρμογής σας.
Επειδή αυτό περιλαμβάνει την υποβολή αιτήματος μέσω του δικτύου, θα πρέπει να το κάνετε από ένα νήμα διαφορετικό από το κύριο νήμα.
Μπορείτε να χρησιμοποιήσετε το AsyncTask
ή φορτωτές για την πραγματοποίηση της κλήσης και θα λειτουργήσει.
Ωστόσο, AsyncTask
Και οι φορτωτές εξαρτώνται και οι δύο από τον κύκλο ζωής της δραστηριότητας. Αυτό σημαίνει ότι θα πρέπει είτε να περιμένετε να εκτελεστεί η κλήση και να προσπαθήσετε να αποτρέψετε το χρήστη από την έξοδο από τη δραστηριότητα, ή ελπίζετε ότι θα εκτελεστεί πριν από την καταστροφή της δραστηριότητας.
Service
μπορεί να ταιριάζει καλύτερα σε αυτήν την περίπτωση χρήσης, καθώς δεν συνδέεται με καμία δραστηριότητα. Επομένως, θα είναι σε θέση να συνεχίσει την κλήση δικτύου ακόμη και μετά την καταστροφή της δραστηριότητας. Επιπλέον, δεδομένου ότι δεν απαιτείται η απόκριση από το διακομιστή, ούτε μια υπηρεσία θα ήταν περιοριστική εδώ.
Ωστόσο, δεδομένου ότι μια υπηρεσία θα αρχίσει να λειτουργεί στο νήμα UI, θα πρέπει να διαχειριστείτε το νήμα μόνοι σας. Θα πρέπει επίσης να βεβαιωθείτε ότι η υπηρεσία έχει διακοπεί μόλις ολοκληρωθεί η κλήση δικτύου.
Αυτό θα απαιτούσε περισσότερη προσπάθεια από ό, τι θα ήταν απαραίτητο για μια τόσο απλή ενέργεια.
χρειάζεται ο κόμβος js διακομιστή ιστού
Αυτό, κατά τη γνώμη μου, θα ήταν η καλύτερη επιλογή.
Από IntentService
δεν συνδέεται με καμία δραστηριότητα και εκτελείται σε ένα νήμα εκτός UI, εξυπηρετεί τέλεια τις ανάγκες μας εδώ. Επιπλέον, IntentService
σταματά αυτόματα, οπότε δεν χρειάζεται να το διαχειριστείτε χειροκίνητα.
Αυτή η περίπτωση χρήσης είναι πιθανώς λίγο πιο κοινή. Για παράδειγμα, μπορεί να θέλετε να καλέσετε ένα API στο παρασκήνιο και να χρησιμοποιήσετε την απόκρισή του για τη συμπλήρωση πεδίων στην οθόνη.
Αν και a Service
ή ένα IntentService
τα πήγαινε καλά για την προηγούμενη περίπτωση χρήσης, η χρήση τους εδώ δεν θα ήταν καλή ιδέα. Προσπάθεια λήψης δεδομένων από ένα Service
ή ένα IntentService
στο κύριο νήμα UI θα έκανε τα πράγματα πολύπλοκα.
Στην πρώτη κοκκινίλα, AsyncTask
ή οι φορτωτές φαίνεται να είναι η προφανής λύση εδώ. Είναι εύχρηστα - απλά και απλά.
Ωστόσο, κατά τη χρήση AsyncTask
ή φορτωτές, θα παρατηρήσετε ότι υπάρχει ανάγκη να γράψετε κάποιο κωδικό boilerplate. Επιπλέον, ο χειρισμός σφαλμάτων γίνεται μια σημαντική δουλειά με αυτά τα στοιχεία. Ακόμα και με μια απλή κλήση δικτύωσης, πρέπει να γνωρίζετε τις πιθανές εξαιρέσεις, να τις πιάσετε και να ενεργήσετε ανάλογα. Αυτό μας αναγκάζει να ολοκληρώσουμε την απάντησή μας σε μια προσαρμοσμένη κλάση που περιέχει τα δεδομένα, με πιθανές πληροφορίες σφάλματος και μια επισήμανση δείχνει εάν η ενέργεια ήταν επιτυχής ή όχι.
Αυτή είναι πολύ δουλειά για κάθε κλήση. Ευτυχώς, υπάρχει τώρα μια πολύ καλύτερη και απλούστερη λύση: RxJava.
Μπορεί να έχετε ακούσει RxJava , η βιβλιοθήκη που αναπτύχθηκε από το Netflix. Είναι σχεδόν μαγικό στην Ιάβα.
RxAndroid σάς επιτρέπει να χρησιμοποιείτε το RxJava στο Android και καθιστά την αντιμετώπιση των ασύγχρονων εργασιών ένα αεράκι. Μπορείτε να μάθετε περισσότερα για το RxJava στο Android εδώ .
Το RxJava παρέχει δύο στοιχεία: Observer
και Subscriber
.
Ενα παρατηρητής είναι ένα στοιχείο που περιέχει κάποια ενέργεια. Εκτελεί αυτήν την ενέργεια και επιστρέφει το αποτέλεσμα εάν επιτύχει ή σφάλμα εάν αποτύχει.
ΠΡΟΣ ΤΟ συνδρομητής , από την άλλη πλευρά, είναι ένα στοιχείο που μπορεί να λάβει το αποτέλεσμα (ή σφάλμα) από ένα παρατηρήσιμο, εγγραφόμενος σε αυτό.
Με το RxJava, δημιουργείτε πρώτα ένα παρατηρήσιμο:
Observable.create((ObservableOnSubscribe) e -> { Data data = mRestApi.getData(); e.onNext(data); })
Μόλις δημιουργηθεί το παρατηρήσιμο, μπορείτε να εγγραφείτε σε αυτό.
Με τη βιβλιοθήκη RxAndroid, μπορείτε να ελέγξετε το νήμα στο οποίο θέλετε να εκτελέσετε την ενέργεια στο παρατηρήσιμο και το νήμα στο οποίο θέλετε να λάβετε την απόκριση (δηλαδή, το αποτέλεσμα ή το σφάλμα).
Βάζετε αλυσίδες στα παρατηρήσιμα με αυτές τις δύο λειτουργίες:
.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()
Οι προγραμματιστές είναι στοιχεία που εκτελούν την ενέργεια σε ένα συγκεκριμένο νήμα. AndroidSchedulers.mainThread()
είναι ο προγραμματιστής που σχετίζεται με το κύριο νήμα.
Δεδομένου ότι η κλήση API είναι mRestApi.getData()
και επιστρέφει ένα Data
αντικείμενο, η βασική κλήση μπορεί να έχει την εξής μορφή:
Observable.create((ObservableOnSubscribe) e -> { try { Data data = mRestApi.getData(); e.onNext(data); } catch (Exception ex) { e.onError(ex); } }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(match -> Log.i(“rest api, 'success'), throwable -> Log.e(“rest api, 'error: %s' + throwable.getMessage()));
Χωρίς καν να προχωρήσουμε σε άλλα οφέλη από τη χρήση του RxJava, μπορείτε ήδη να δείτε πώς το RxJava μας επιτρέπει να γράφουμε πιο ώριμο κώδικα αφαιρώντας την πολυπλοκότητα του νήματος.
Για κλήσεις δικτύου που πρέπει να εκτελούνται διαδοχικά (δηλαδή, όπου κάθε λειτουργία εξαρτάται από την απόκριση / αποτέλεσμα της προηγούμενης λειτουργίας), πρέπει να είστε ιδιαίτερα προσεκτικοί σχετικά με τη δημιουργία κώδικα σπαγγέτι.
Για παράδειγμα, ίσως χρειαστεί να πραγματοποιήσετε μια κλήση API με ένα διακριτικό που πρέπει να λάβετε πρώτα μέσω άλλης κλήσης API.
Χρήση AsyncTask
ή οι φορτωτές θα οδηγήσουν σχεδόν σίγουρα σε κώδικα σπαγγέτι. Η συνολική λειτουργικότητα θα είναι δύσκολο να γίνει σωστή και θα απαιτήσει τεράστια ποσότητα περιττού κώδικα boilerplate καθ 'όλη τη διάρκεια του έργου σας.
Στο RxJava, το flatMap
Ο χειριστής παίρνει μια εκπεμπόμενη τιμή από την παρατηρήσιμη πηγή και επιστρέφει ένα άλλο παρατηρήσιμο. Μπορείτε να δημιουργήσετε ένα παρατηρήσιμο και, στη συνέχεια, να δημιουργήσετε ένα άλλο παρατηρήσιμο χρησιμοποιώντας την εκπεμπόμενη τιμή από την πρώτη, η οποία βασικά θα τις αλυσώσει.
Βήμα 1. Δημιουργήστε το παρατηρήσιμο που παίρνει το διακριτικό:
σε τι χρησιμοποιείται το c
public Observable getTokenObservable() { return Observable.create(subscriber -> { try { String token = mRestApi.getToken(); subscriber.onNext(token); } catch (IOException e) { subscriber.onError(e); } }); }
Βήμα 2. Δημιουργήστε το παρατηρήσιμο που λαμβάνει τα δεδομένα χρησιμοποιώντας το διακριτικό:
public Observable getDataObservable(String token) { return Observable.create(subscriber -> { try { Data data = mRestApi.getData(token); subscriber.onNext(data); } catch (IOException e) { subscriber.onError(e); } }); }
Βήμα 3. Αλυσοδέστε τα δύο παρατηρήσιμα μαζί και εγγραφείτε:
getTokenObservable() .flatMap(new Function() { @Override public Observable apply(String token) throws Exception { return getDataObservable(token); } }) .subscribe(data -> { doSomethingWithData(data) }, error -> handleError(e));
Σημειώστε ότι η χρήση αυτής της προσέγγισης δεν περιορίζεται σε κλήσεις δικτύου. Μπορεί να λειτουργήσει με οποιοδήποτε σύνολο ενεργειών που πρέπει να εκτελούνται σε μια ακολουθία αλλά σε ξεχωριστά νήματα.
Όλες οι παραπάνω περιπτώσεις χρήσης είναι αρκετά απλές. Η εναλλαγή μεταξύ νημάτων πραγματοποιήθηκε μόνο μετά από κάθε ολοκλήρωση της εργασίας του. Πιο προηγμένα σενάρια - για παράδειγμα, όπου δύο ή περισσότερα νήματα πρέπει να επικοινωνούν ενεργά μεταξύ τους - μπορούν να υποστηριχθούν και από αυτήν την προσέγγιση.
Εξετάστε ένα σενάριο στο οποίο θα θέλατε να ανεβάσετε ένα αρχείο και να ενημερώσετε τη διεπαφή χρήστη μόλις ολοκληρωθεί.
Δεδομένου ότι η μεταφόρτωση ενός αρχείου μπορεί να διαρκέσει πολύ, δεν χρειάζεται να παραμείνει ο χρήστης σε αναμονή. Θα μπορούσατε να χρησιμοποιήσετε μια υπηρεσία, και πιθανώς IntentService
, για την εφαρμογή της λειτουργικότητας εδώ.
Σε αυτήν την περίπτωση, ωστόσο, η μεγαλύτερη πρόκληση είναι η επίκληση μιας μεθόδου στο νήμα διεπαφής χρήστη μετά την ολοκλήρωση της μεταφόρτωσης του αρχείου (η οποία εκτελέστηκε σε ξεχωριστό νήμα).
Το RxJava, είτε μόνο του είτε μέσα σε ένα IntentService
, μπορεί να μην είναι ιδανικό. Θα πρέπει να χρησιμοποιήσετε έναν μηχανισμό επαναφοράς κατά την εγγραφή σε Observable
και IntentService
είναι κατασκευασμένο για να πραγματοποιεί απλές σύγχρονες κλήσεις και όχι επιστροφές κλήσεων.
Από την άλλη πλευρά, με ένα Service
, θα πρέπει να διακόψετε χειροκίνητα την υπηρεσία, η οποία απαιτεί περισσότερη δουλειά.
Το Android παρέχει αυτό το στοιχείο, το οποίο μπορεί να ακούει παγκόσμια συμβάντα (π.χ. συμβάντα μπαταρίας, συμβάντα δικτύου κ.λπ.) καθώς και προσαρμοσμένα συμβάντα. Μπορείτε να χρησιμοποιήσετε αυτό το στοιχείο για να δημιουργήσετε ένα προσαρμοσμένο συμβάν που ενεργοποιείται όταν ολοκληρωθεί η μεταφόρτωση.
Για να το κάνετε αυτό, πρέπει να δημιουργήσετε μια προσαρμοσμένη κλάση που επεκτείνεται BroadcastReceiver
, καταχωρίστε την στο μανιφέστο και χρησιμοποιήστε Intent
και IntentFilter
για να δημιουργήσετε το προσαρμοσμένο συμβάν. Για να ενεργοποιήσετε το συμβάν, θα χρειαστείτε το sendBroadcast
μέθοδος.
Δηλωτικό:
public class UploadReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (intent.getBoolean(“success”, false) { Activity activity = (Activity)context; activity.updateUI(); } }
Δέκτης:
Intent intent = new Intent(); intent.setAction('com.example.upload'); sendBroadcast(intent);
Αποστολέας:
Handler
Αυτή η προσέγγιση είναι μια βιώσιμη επιλογή. Αλλά όπως έχετε παρατηρήσει, περιλαμβάνει κάποια εργασία και πάρα πολλές εκπομπές μπορούν να επιβραδύνουν τα πράγματα.
Α Runnable
είναι ένα στοιχείο που μπορεί να συνδεθεί σε ένα νήμα και στη συνέχεια να γίνει για να εκτελέσει κάποια ενέργεια σε αυτό το νήμα μέσω απλών μηνυμάτων ή Looper
καθήκοντα. Λειτουργεί σε συνδυασμό με ένα άλλο στοιχείο, Handler
, το οποίο είναι υπεύθυνο για την επεξεργασία μηνυμάτων σε ένα συγκεκριμένο νήμα.
Όταν ένα Looper
έχει δημιουργηθεί, μπορεί να πάρει ένα Looper.getMainLooper()
αντικείμενο στον κατασκευαστή, το οποίο δείχνει σε ποιο νήμα είναι συνδεδεμένος ο χειριστής. Εάν θέλετε να χρησιμοποιήσετε ένα χειριστή που είναι συνδεδεμένο στο κύριο νήμα, πρέπει να χρησιμοποιήσετε το βρόχο που σχετίζεται με το κύριο νήμα καλώντας Runnable
.
Σε αυτήν την περίπτωση, για να ενημερώσετε το περιβάλλον εργασίας χρήστη από ένα νήμα φόντου, μπορείτε να δημιουργήσετε ένα πρόγραμμα χειρισμού που είναι συνδεδεμένο στο νήμα διεπαφής χρήστη και, στη συνέχεια, να δημοσιεύσετε μια ενέργεια ως Handler handler = new Handler(Looper.getMainLooper()); handler.post(new Runnable() { @Override public void run() { // update the ui from here } });
:
EventBus
Αυτή η προσέγγιση είναι πολύ καλύτερη από την πρώτη, αλλά υπάρχει ακόμη ένας απλούστερος τρόπος για να το κάνετε αυτό…
UIEvent
, μια δημοφιλής βιβλιοθήκη της GreenRobot, επιτρέπει σε στοιχεία να επικοινωνούν με ασφάλεια μεταξύ τους. Δεδομένου ότι η περίπτωση χρήσης μας είναι εκείνη που θέλουμε μόνο να ενημερώσουμε το περιβάλλον εργασίας χρήστη, αυτή μπορεί να είναι η απλούστερη και ασφαλέστερη επιλογή.
Βήμα 1. Δημιουργήστε μια τάξη εκδηλώσεων. π.χ. @Subscribe(threadMode = ThreadMode.MAIN) public void onUIEvent(UIEvent event) {/* Do something */}; register and unregister eventbus : @Override public void onStart() { super.onStart(); EventBus.getDefault().register(this); } @Override public void onStop() { super.onStop(); EventBus.getDefault().unregister(this); }
.
Βήμα 2. Εγγραφείτε στην εκδήλωση.
EventBus.getDefault().post(new UIEvent());
Βήμα 3. Δημοσίευση της εκδήλωσης: ThreadMode
Με το UIEvent
παράμετρο στον σχολιασμό, καθορίζετε το νήμα στο οποίο θέλετε να εγγραφείτε για αυτό το συμβάν. Στο παράδειγμά μας εδώ, επιλέγουμε το κύριο νήμα, καθώς θα θέλουμε ο παραλήπτης του συμβάντος να μπορεί να ενημερώνει τη διεπαφή χρήστη.
Μπορείτε να δομήσετε το class UploadFileService extends IntentService { // … Boolean success = uploadFile(File file); EventBus.getDefault().post(new UIEvent(success)); // ... }
τάξη για να περιέχει πρόσθετες πληροφορίες, όπως απαιτείται.
Στην υπηρεσία:
@Subscribe(threadMode = ThreadMode.MAIN) public void onUIEvent(UIEvent event) {//show message according to the action success};
Στη δραστηριότητα / θραύσμα:
EventBus library
Χρησιμοποιώντας το EventBus
, η επικοινωνία μεταξύ νημάτων γίνεται πολύ πιο απλή.
Ας υποθέσουμε ότι δημιουργείτε ένα πρόγραμμα αναπαραγωγής πολυμέσων και θέλετε να μπορεί να συνεχίσει να παίζει μουσική ακόμα και όταν η οθόνη της εφαρμογής είναι κλειστή. Σε αυτό το σενάριο, θα θέλετε το περιβάλλον χρήστη να μπορεί να επικοινωνεί με το νήμα πολυμέσων (π.χ. αναπαραγωγή, παύση και άλλες ενέργειες) και θέλετε επίσης το νήμα πολυμέσων να ενημερώνει το περιβάλλον εργασίας χρήστη με βάση συγκεκριμένα συμβάντα (π.χ. σφάλμα, κατάσταση προσωρινής αποθήκευσης , και τα λοιπά).
Ένα πλήρες παράδειγμα προγράμματος αναπαραγωγής πολυμέσων είναι πέρα από το πεδίο αυτού του άρθρου. Μπορείτε, ωστόσο, να βρείτε καλά μαθήματα εδώ και εδώ .
Θα μπορούσατε να χρησιμοποιήσετε BoundService
εδώ. Ωστόσο, είναι γενικά μη ασφαλές να δημοσιεύετε ένα συμβάν από το νήμα διεπαφής χρήστη και να το λαμβάνετε σε μια υπηρεσία. Αυτό συμβαίνει επειδή δεν έχετε κανέναν τρόπο να γνωρίζετε εάν η υπηρεσία εκτελείται όταν έχετε στείλει το μήνυμα.
Α Service
είναι ένα Binder
που συνδέεται με μια δραστηριότητα / θραύσμα. Αυτό σημαίνει ότι η δραστηριότητα / θραύσμα γνωρίζει πάντα εάν η υπηρεσία εκτελείται ή όχι και, επιπλέον, αποκτά πρόσβαση στις δημόσιες μεθόδους της υπηρεσίας.
αρχείο κεφαλίδας έναντι αρχείου πηγής c++
Για να το εφαρμόσετε, πρέπει να δημιουργήσετε ένα προσαρμοσμένο public class MediaService extends Service { private final IBinder mBinder = new MediaBinder(); public class MediaBinder extends Binder { MediaService getService() { // Return this instance of LocalService so clients can call public methods return MediaService.this; } } @Override public IBinder onBind(Intent intent) { return mBinder; } }
μέσα στην υπηρεσία και δημιουργήστε μια μέθοδο που επιστρέφει την υπηρεσία.
ServiceConnection
Για να συνδέσετε τη δραστηριότητα με την υπηρεσία, πρέπει να εφαρμόσετε bindService
, η οποία είναι η τάξη που παρακολουθεί την κατάσταση της υπηρεσίας και να χρησιμοποιήσετε τη μέθοδο // in the activity MediaService mService; // flag indicates the bound status boolean mBound; @Override protected void onStart() { super.onStart(); // Bind to LocalService Intent intent = new Intent(this, MediaService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { MediaBinder binder = (MediaBinder) service; mService = binder.getService(); mBound = true; } @Override public void onServiceDisconnected(ComponentName arg0) { mBound = false; } };
για να κάνετε τη δέσμευση:
BroadcastReceiver
Μπορείτε να βρείτε ένα πλήρες παράδειγμα εφαρμογής εδώ .
Για να επικοινωνήσετε με την υπηρεσία όταν ο χρήστης πατήσει το κουμπί Αναπαραγωγή ή Παύση, μπορείτε να συνδεθείτε με την υπηρεσία και στη συνέχεια να καλέσετε τη σχετική δημόσια μέθοδο στην υπηρεσία.
Όταν υπάρχει ένα συμβάν πολυμέσων και θέλετε να το κοινοποιήσετε πίσω στη δραστηριότητα / τμήμα, μπορείτε να χρησιμοποιήσετε μία από τις προηγούμενες τεχνικές (π.χ. Handler
, EventBus
ή merge()
).
Ας υποθέσουμε ότι δημιουργείτε μια τουριστική εφαρμογή και θέλετε να εμφανίσετε αξιοθέατα σε έναν χάρτη που έχει ληφθεί από πολλές πηγές (διαφορετικοί πάροχοι δεδομένων). Επειδή δεν είναι όλες οι πηγές αξιόπιστες, ίσως θελήσετε να αγνοήσετε αυτές που έχουν αποτύχει και συνεχίζουν να αποδίδουν τον χάρτη ούτως ή άλλως.
πώς να λάβετε στοιχεία πιστωτικής κάρτας στο διαδίκτυο
Για να παραλληλιστεί η διαδικασία, κάθε κλήση API πρέπει να πραγματοποιείται σε διαφορετικό νήμα.
Στο RxJava, μπορείτε να συνδυάσετε πολλά παρατηρήσιμα σε ένα χρησιμοποιώντας το concat()
ή ExecutorService
χειριστές. Στη συνέχεια, μπορείτε να εγγραφείτε στο 'συγχωνευμένο' παρατηρήσιμο και να περιμένετε όλα τα αποτελέσματα.
Αυτή η προσέγγιση, ωστόσο, δεν θα λειτουργήσει όπως αναμενόταν. Εάν μια κλήση API αποτύχει, η συγχωνευθείσα παρατηρούμενη θα αναφέρει μια συνολική αποτυχία.
Το Future
στην Java δημιουργεί έναν σταθερό (διαμορφώσιμο) αριθμό νημάτων και εκτελεί εργασίες σε αυτά ταυτόχρονα. Η υπηρεσία επιστρέφει ένα invokeAll()
αντικείμενο που τελικά επιστρέφει όλα τα αποτελέσματα μέσω του ExecutorService
μέθοδος.
Κάθε εργασία που στέλνετε στο Callable
θα πρέπει να περιέχεται στο invokeAll()
διεπαφή, που είναι μια διεπαφή για τη δημιουργία μιας εργασίας που μπορεί να ρίξει μια εξαίρεση.
Μόλις λάβετε τα αποτελέσματα από ExecutorService pool = Executors.newFixedThreadPool(3); List
, μπορείτε να ελέγξετε κάθε αποτέλεσμα και να προχωρήσετε ανάλογα.
Ας πούμε, για παράδειγμα, ότι έχετε τρεις τύπους έλξης που προέρχονται από τρία διαφορετικά τελικά σημεία και θέλετε να πραγματοποιήσετε τρεις παράλληλες κλήσεις:
Cursor
Με αυτόν τον τρόπο, εκτελείτε όλες τις ενέργειες παράλληλα. Μπορείτε, επομένως, να ελέγξετε για σφάλματα σε κάθε ενέργεια ξεχωριστά και να αγνοήσετε τις μεμονωμένες αστοχίες, ανάλογα με την περίπτωση.
Αυτή η προσέγγιση είναι ευκολότερη από τη χρήση του RxJava. Είναι απλούστερο, μικρότερο και δεν αποτυγχάνει σε όλες τις ενέργειες λόγω μίας εξαίρεσης.
Όταν ασχολείστε με μια τοπική βάση δεδομένων SQLite, συνιστάται η χρήση της βάσης δεδομένων από ένα νήμα φόντου, καθώς οι κλήσεις βάσης δεδομένων (ειδικά με μεγάλες βάσεις δεδομένων ή πολύπλοκα ερωτήματα) μπορεί να είναι χρονοβόρες, με αποτέλεσμα το πάγωμα του περιβάλλοντος εργασίας χρήστη.
Κατά την αναζήτηση δεδομένων για δεδομένα SQLite, λαμβάνετε ένα Cursor cursor = getData(); String name = cursor.getString();
αντικείμενο που μπορεί στη συνέχεια να χρησιμοποιηθεί για τη λήψη των πραγματικών δεδομένων.
public Observable getLocalDataObservable() { return Observable.create(subscriber -> { Cursor cursor = mDbHandler.getData(); subscriber.onNext(cursor); }); }
Μπορείτε να χρησιμοποιήσετε το RxJava και να λάβετε τα δεδομένα από τη βάση δεδομένων, όπως λαμβάνουμε δεδομένα από το back-end:
getLocalDataObservable()
Μπορείτε να χρησιμοποιήσετε το παρατηρήσιμο που επιστρέφεται από getLocalDataObservable() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(cursor -> String name = cursor.getString(0), throwable -> Log.e(“db, 'error: %s' + throwable.getMessage()));
ως εξής:
CursorLoader
Ενώ αυτή είναι σίγουρα μια καλή προσέγγιση, υπάρχει μια ακόμη καλύτερη, καθώς υπάρχει ένα στοιχείο που έχει δημιουργηθεί μόνο για αυτό το σενάριο.
Το Android παρέχει Loader
, ένα εγγενές στοιχείο για τη φόρτωση δεδομένων SQLite και τη διαχείριση του αντίστοιχου νήματος. Είναι ένα Cursor
που επιστρέφει ένα getString()
, το οποίο μπορούμε να χρησιμοποιήσουμε για να λάβουμε τα δεδομένα καλώντας απλές μεθόδους όπως getLong()
, public class SimpleCursorLoader extends FragmentActivity implements LoaderManager.LoaderCallbacks { public static final String TAG = SimpleCursorLoader.class.getSimpleName(); private static final int LOADER_ID = 0x01; private TextView textView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple_cursor_loader); textView = (TextView) findViewById(R.id.text_view); getSupportLoaderManager().initLoader(LOADER_ID, null, this); } public Loader onCreateLoader(int i, Bundle bundle) { return new CursorLoader(this, Uri.parse('content://com.github.browep.cursorloader.data') , new String[]{'col1'}, null, null, null); } public void onLoadFinished(Loader cursorLoader, Cursor cursor) { if (cursor != null && cursor.moveToFirst()) { String text = textView.getText().toString(); while (cursor.moveToNext()) { text += '
κ.λπ.
' + cursor.getString(1); cursor.moveToNext(); } textView.setText(Html.fromHtml(text) ); } } public void onLoaderReset(Loader cursorLoader) { } }
CursorLoader
ContentProvider
λειτουργεί με το
|_+_|συστατικό. Αυτό το στοιχείο παρέχει μια πληθώρα δυνατοτήτων βάσης δεδομένων σε πραγματικό χρόνο (π.χ., ειδοποιήσεις αλλαγής, ενεργοποιήσεις κ.λπ.) που επιτρέπει στους προγραμματιστές να εφαρμόζουν μια καλύτερη εμπειρία χρήστη πολύ πιο εύκολα.
Το Android παρέχει πολλούς τρόπους χειρισμού και διαχείρισης νημάτων, αλλά κανένας από αυτούς δεν είναι ασημένιες σφαίρες.
Η επιλογή της σωστής προσέγγισης με σπείρωμα, ανάλογα με την περίπτωση χρήσης σας, μπορεί να κάνει όλη τη διαφορά στο να καταστήσει τη συνολική λύση εύκολη στην εφαρμογή και την κατανόηση. Τα εγγενή στοιχεία ταιριάζουν καλά σε ορισμένες περιπτώσεις, αλλά όχι για όλες. Το ίδιο ισχύει για φανταχτερά λύσεις τρίτων.
Ελπίζω να βρείτε αυτό το άρθρο χρήσιμο όταν εργάζεστε στο επόμενο έργο σας Android. Μοιραστείτε μαζί μας την εμπειρία σας στο νήμα στο Android ή σε οποιαδήποτε περίπτωση χρήσης όπου οι παραπάνω λύσεις λειτουργούν καλά - ή όχι, εν προκειμένω - στα παρακάτω σχόλια.