Android Oreo sería el mejor de todas las versiones de Android hasta ahora. La verdad la verdad, no lo comparto. Las notificaciones de aplicaciones en segundo plano son molestas e inútiles, los mensajes en apps de mensajes como WhatsApp llegan con retraso, y así por el estilo sin embargo, este post no es para hablar del sistema operativo como tal. Con los cambios hechos los Googlers te sugieren usar
JobScheduler para ejecutar tareas
en segundo plano, lo resalto por eso, para ejecutar así en background, si la app esta en foreground seguimos usando intent services o los mecanismos de siempre.
JobScheduler fue introducido en Lollipop (API 21) pero ahora es cuando me he dado la tarea de aprender sobre JS al ver los ANR en Android Oreo de mi app. La primera estrellada,
JobService trabaja en el Main Thread (UI Thread) no leí esa parte y pensaba que funcionaba como un
IntentService luego de meter código de networking en
onStartJob me apareció el
stack trace de
networking en el UI thread, por lo que hay que sacar código de ejecución larga y pesada a su propio thread.
La solución (según los ejemplos de Googlers) es usar un
AsyncTask , algo así como esto
@Overridepublic boolean onStartJob(final JobParameters params) {
mDownloadArtworkTask = new DownloadArtworkTask(this) {
@Override protected void onPostExecute(Boolean success) {
super.onPostExecute(success);
jobFinished(params, !success);
}
};
mDownloadArtworkTask.execute();
return true;
}
pero lamentablemente esto tiene una inspección de código bien fea que dice.
Static Field Leaks: A static field will leak contexts. Non-static inner classes have an implicit reference to their outer class. If that outer class is for example a Fragment or Activity, then this reference means that the long-running handler/loader/task will hold a reference to the activity which prevents it from getting garbage collected. Similarly, direct field references to activities and fragments from these longer running instances can cause leaks. ViewModel classes should never point to Views or non-application Contexts.
En español , podría ocurrir memory leaks al tener clases internas no estáticas, ya que guardan una referencia implícita del contexto de la clase externa. Si ocurren o no, no lo sé. Supongo que no, pero particularmente no me gusta tener Warnings en mi código así que decidí buscar otra forma de hacerlo.
Leyendo un poco mas, te hablan también de usar
Handlers y
Threads estos hace tiempo que no los usaba y había olvidado como se implementaban; Luego de terminar mi implementación pues funciono muy bien, y no habían warnings molestos por ningún lado. Así que aquí es a donde vamos.
JobService tiene tres métodos principales: onStartJob, onStopJob y el onCreate de siempre.
public class AlertJobService extends JobService {
private Handler mJobServiceHandler;
private final int TASK_COMPLETE = 0;
@Override
public void onCreate() {
super.onCreate();
}
@Override
public boolean onStartJob(JobParameters jobParameters) {
return true;
}
@Override public boolean onStopJob(JobParameters jobParameters) {
return false;
}
}
En
onCreate puedes iniciar lo que quieras entre éstas y una bien importante es el
handler, el cual se encargar de manejar los mensajes que reciba dese otros Threads.
Definimos una variable global de tipo Handler -yo la llame
mJobServiceHandler - y la iniciamos en
onCreate. Estoy asignando a este handle el main looper o en otras palabras el UI Thread.
@Overridepublic void onCreate() {
super.onCreate();
mJobServiceHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
default:
super.handleMessage(msg);
}
}
};
}
Creamos el Thread para networking quedando algo así como esto.
/** * Network thread with background priority */
class NetworkThread extends Thread {
JobParameters jobParameters;
String url;
String requestParameters;
/*** Network handler constructor
** @param jobParameters Job parameters of this Job service
* @param url Service url
* @param requestParams Request parameters
*/
NetworkThread(JobParameters jobParameters, String url, String requestParams) {
this.url = url;
this.jobParameters = jobParameters;
this.requestParameters = requestParams;
}
@Override
public void run() {
Response response = null;
android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
OkHttpClient client = new OkHttpClient();
RequestBody requestBody = RequestBody.create(JSON, requestParameters);
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.build();
try {
response = client.newCall(request).execute();
} catch (IOException e) {
e.printStackTrace();
}
Message message = new Message();
message.what = TASK_COMPLETE;
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.JOB_PARAMETERS, jobParameters);
if (response != null) {
bundle.putString(Constants.RESPONSE, response.body().string());
}
message.setData(bundle);
mJobServiceHandler.sendMessage(message);
}
}
Llamamos el thread en onStartJob. En mi caso, estoy llamando a un servicio que me retorna una lista de alertas. Si te das cuenta, estoy creando el url y los parámetros que necesito en
onStartJob para poder usar los métodos como getString(), getResources() etc. Estos tienen un contexto definido y puedo hacerlo, luego se los paso al thread desde el constructor.
@Override
public boolean onStartJob(JobParameters jobParameters) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "onStartJob: Alert Job Service");
}
String url = getString(R.string.base_server_url);
String requestParams = jobParameters.getExtras().getString(Constants.REQUEST_PARAMS, "");
if (requestParams.isEmpty()) {
throw new IllegalArgumentException(getResources().getString(R.string.throw_add_parameters_to_request));
} else {
NetworkThread networkThread = new NetworkThread(jobParameters, url, requestParams);
new Thread(networkThread).start();
}
return true;
}
onStartJob retorna true para que el framework lo mantenga vivo, es decir, retornamos true si llamamos a hacer algo en otro thread y tenemos que esperar la respuesta como en el caso de mi NetworkThread.
Una vez empezado el thread, este llama al servicio remoto hace lo que tiene que hacer y regresa, al final llama a
mJobServiceHandler.sendMessage(message); Ahí defino mi mensaje el cual va a ser recibido por handleMessage(). A continuación el código completo de handleMessage
@Override
public void onCreate() {
super.onCreate();
mJobServiceHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case TASK_COMPLETE:
if (msg.getData().containsKey(Constants.INSTANCE.getRESPONSE())) {
Intent broadcast = new Intent();
Type alertListType = new TypeToken<ArrayList<Alert>>() {
}.getType();
AlertsResponse response = gs.fromJson(msg.getData().getString(Constants.INSTANCE.getRESPONSE()), alertListType);
broadcast.putExtra(Constants.INSTANCE.getRESPONSE(), response);
LocalBroadcastManager.getInstance(AlertJobService.this).sendBroadcast(broadcast);
}
JobParameters parameters = msg.getData().getParcelable("jobParameters");
jobFinished(parameters, true);
break;
default:
super.handleMessage(msg);
}
}
};
}
Handle message tiene un switch case en msg.what esto lo definí dentro del Network thread para indicar que ya se terminó el trabajo pendiente, una vez recibido envío mi broadcast o local broadcast con el resultado, y listo.
Comentarios
Publicar un comentario
Prove yourself!