Как правильно активировать Сервис вне Активности? - PullRequest
0 голосов
/ 06 сентября 2018

Я пытаюсь создать приложение, которое будет работать на веб-сервисе. Чтобы сделать это, я решил следовать архитектуре Model-View-ViewModel вместе с шаблоном Repository. Я пытаюсь сделать эту архитектуру вдохновленной рекомендациями, приведенными в Руководство по архитектуре приложений , с официального сайта Android Developer.

Я использую библиотеку OkHttp, чтобы использовать WebService, и Room для базы данных телефона.

Android's recommended architecture

Я провел некоторое тестирование, чтобы убедиться, что приложение успешно получило Данные через веб-сервис изнутри Основного Действия, и оно работало; Приложение успешно получило данные.

ServiceConnection connection = new ServiceConnection() {

    @Override
    public void onServiceDisconnected(ComponentName name) {
        connected = false;
        Log.i("MainActivity", "MyWebService DISconnected successfully.");
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        myweb_service = ((MyWebService.LocalBinder)service).getService();
        connected = true;
        Log.i("MainActivity", "MyWebService connected successfully.");
    }
};

void doBindMyWebService() {
    if (bindService(new Intent(this, MyWebService.class),
            connection, Context.BIND_AUTO_CREATE))
    {
        mShouldUnbind = true;
    }
    else {
        Log.e("MainActivity", "ERROR --> Service instance doesn't exist, or this Client doesn't have permission to access.");
    }
}

void doUnbindMyWebService() {
    if (mShouldUnbind) {
        unbindService(connection);
    }
}

doBindMyWebService();

/* I am not trying to extract this method at all; I just included it for
 * the sake of completeness
 */
@Override
protected void onDestroy() {
    super.onDestroy();
    doUnbindMyWebService();
}

Теперь, чтобы придерживаться выбранной архитектуры, я пытаюсь переместить код, который создает ServiceConnection в Main Activity, за пределы Activity, чтобы отделить его от логики View. Для этого я попытался обернуть код, который создает ServiceConnection, и связал его с новым классом для обработки Services. При извлечении кода и попытке связать его с классом Repository возник следующий вопрос: необходимо ли привязывать Службу для того, чтобы она работала, и извлекать из нее данные, или вместо этого я могу сделать это, не привязывая ее к все? (Для справки: это не сам вопрос; я просто указываю на то, что я думал, делая код)

Обратите внимание, что мне известно, что я мог бы просто передать некоторый контекст (например, контекст MainActivity или контекст приложения) конструкторам классов и сохранить его в каком-то свойстве, но это действительно то, чего я пытаюсь избежать делать для того, чтобы предотвратить утечки памяти. Короче говоря:

  • Приведенный выше код работает в MainActivity
  • Я пытаюсь активировать Сервисы вне класса Activity, поэтому я создал специальный класс для обработки Сервисов внутри него. Я назвал этот класс "RemoteDataSource"
  • Я пытаюсь создать класс Repository для извлечения данных из Службы после того, как Служба завершит HTTP-вызов и получит ответ.
  • Я хочу, чтобы MyWebService продолжал работать, даже если нет ни одного экземпляра Activity.
  • Попытка привязать Сервис к какому-либо контексту в этих условиях кажется бессмысленной, поскольку это может привести к утечкам памяти.
  • Кажется, что вызова onStartCommand недостаточно для запуска службы.

Вот код MyWebService:

package com.example.myapp;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.util.Log;

import com.example.myapp.model.MyDataModel;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class MyWebService extends Service {
    private String url= "https://someserver.here/api/data/read.php";
    private String response = "";
    private JSONArray response_jsonarray;
    private boolean running = false;

    final static int MESSAGE = 1;

    private final IBinder mBinder = new LocalBinder();

    public boolean isRunning() {
        return this.running;
    }

    public class LocalBinder extends Binder {
        MyWebService getService() {
            return MyWebService.this;
        }
    }

    public String getResponse() {
        if (!running) return null;
        return this.response;
    }

    public JSONArray getResponseAsJsonArray() {
        if (!running) return null;
        return this.response_jsonarray;
    }

    public List<MyDataModel> getResponseAsObject() {
        if (!running) return null;

        ArrayList<MyDataModel> child_nodes = new ArrayList<MyDataModel>();

        for (int i = 0; i < this.response_jsonarray.length(); i++) {
            try {
                JSONObject parent_obj = this.response_jsonarray.getJSONObject(i);
                long parent_id = parent_obj.getLong("id_parent");
                short type_id = (short)parent_obj.getInt("type");
                short status_id = (short)parent_obj.getInt("status_id");

                JSONObject childs_list = parent_obj.getJSONObject("childs");
                Iterator<String> iter_childs = childs_list.keys();
                while (iter_childs.hasNext()) {
                    JSONObject child_obj = childs_list.getJSONObject(iter_child.next());

                    long i_id = child_obj.getLong("id");
                    String i_name = child_obj.getString("name");
                    double i_lat = child_obj.getDouble("lat");
                    double i_long = child_obj.getDouble("long");
                    short i_order = (short)child_obj.getInt("order");

                    MyDataModel i_child = new MyDataModel(i_id, i_name, i_lat, i_long, 0, 0, type_id, status_id, 0, parent_id, i_order);
                    child_nodes.add(i_child);
                }
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }
        return child_nodes;
    }

    /*
     * SERVICE LIFECYCLE
     */
    @Override
    public void onCreate() {
        Log.i("MyWebService", "OnCreate TRIGGERED");

        OkHttpClient client = new OkHttpClient();

        Request request = new Request.Builder()
                .url(url)
                .build();

        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.e("MyWebService", "ERROR --> " + e.getMessage());
                e.printStackTrace();
                call.cancel();
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                Log.i("MyWebService", "GOOD -> Response received! :-)");

                final String myResponse = response.body().string();
                response = myResponse;
                try {
                    response_jsonarray = new JSONArray(response);
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        });
        this.running = true;

        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        super.onStartCommand(intent, flags, startId);
        Log.i("MyWebService", "OnStartCommand TRIGGERED");
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.i("MyWebService", "OnBind TRIGGERED");
        return mBinder;
    }
}

А вот код класса, который я назвал «RemoteDataSource»:

package com.example.myapp;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.util.Log;

public class RemoteDataSource {
    private boolean connected;
    private boolean mShouldUnbind;
    private MyWebService mWebService;

    private final int INTENT_WEBSERVICE = 1;

    public boolean isConnected() {
        return this.connected;
    }

    public boolean isBound() {
        return this.mShouldUnbind;
    }

    public ServiceConnection getConnection() {
        return this.connection;
    }

    private ServiceConnection connection;

    public MyWebService getMyWebService() {
        return this.mWebService;
    }

    public RemoteDataSource() {
        this.connection = new ServiceConnection() {

            @Override
            public void onServiceDisconnected(ComponentName name) {
                connected = false;
                Log.i("RemoteDataSource", "MyWebService DISconnected successfully");
            }

            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                mWebService = ((MyWebService.LocalBinder) service).getService();
                connected = true;
                Log.i("RemoteDataSource", "MyWebService connected successfully");
            }
        };
    }

    public boolean isRunning() {
        if (this.mWebService == null) return false;
        return this.mWebService.isRunning();
    }

    public void prepareWebService() {
        //TODO -> Consider this empty method as a symbol of what I am trying to achieve
    }

    public void startMyWebService() {
        this.mWebService.onStartCommand(new Intent(Intent.ACTION_SYNC), 0, INTENT_WEBSERVICE);
    }

    public void stopMyWebService() {
        this.mWebService.stopSelf();
    }

    public void doBindMyWebService (Context context) {
        if (context.bindService(new Intent(context, MyWebService.class),
                this.connection, Context.BIND_AUTO_CREATE)) {
            this.mShouldUnbind = true;
        } else {
            Log.e("RemoteDataSource", "ERROR --> Service instance doesn't exist, or this Client doesn't have permission to access.");
        }
    }

    public void doUnbindMyWebService (Context context) {
        if (this.mShouldUnbind) {
            context.unbindService(connection);
        }
    }
}

А вот часть моего конструктора класса Repository:

public Repository(Application application) {
    AppDatabase db = AppDatabase.getDatabase(application);
    mParentDao = db.parentDao();
    mMyDataModelDao = db.myDataModelDao();

    mRemoteDataSource = new RemoteDataSource();

    /* XXX It was a bad idea, it gets stuck in an infinite loop; I just had to try anyway
     *     The point is, if I don't wait until MyWebService has made it's work,
     *       the app crashes with NullPointerException (or it seems so, at least)
     */
    while (!mRemoteDataSource.isRunning()) { continue; }

    mRemoteDataSource.startMyWebService();

    // [...]
}

На данный момент моя цель - обработать мой MyWebService из класса RemoteDataSource, избегая его привязки к контексту. WebService может заслуживать отдельного потока, но я все еще новичок с потоками; так что я бы предпочел, чтобы все было просто, пока я не увижу, как это работает; позже я буду беспокоиться об улучшении производительности.

В любом случае, я думаю, что я слишком озадачен тем, как мне следует запускать Службу (обратите внимание, что я не слишком знаком со Службами и способами их обработки). Является ли способ, которым я пытался справиться с услугой, неправильно? Если это так, что я пропускаю? Что я должен иметь в виду, чтобы отсоединить MyWebService от логики View?

...