Как использовать интерфейс для связи между действиями после смерти процесса? - PullRequest
3 голосов
/ 09 июля 2020

Я создаю SDK, и мне нужно реализовать обратные вызовы между действиями, не завершая sh действие. Ранее я использовал onActivityResult для предоставления результатов активности вызывающего абонента. Однако это закрывает действие, и мне нужно выполнить обратный вызов без завершения действия из SDK . Моя текущая реализация:

fun initializeSDK(){
    SDK.getInstance().initialize(resultsCallbackImpl)
}
val resultsCallbackImpl:ResultsCallback = object : ResultsCallback {
    override fun response1() {
        
    }

    override fun response2() {
        
    }
};

Например, клиент вызывает initializeSDK() из своей активности после нажатия кнопки. Затем клиент передает интерфейс как параметр, который устанавливается как свойство в синглтоне SDK. Затем я использую этот интерфейс для получения результатов.

Проблема возникает после смерти процесса. Интерфейс становится нулевым, потому что он не сериализован, и я не может больше возвращать обратный вызов клиенту . Как мне отредактировать свой код, чтобы решить эту проблему? Возможно ли это вообще?

Я знаю, что клиент может инициализировать SDK в классе приложения , а затем он будет повторно установлен после смерти процесса. Однако такой подход приведет к тому, что клиенту будет сложно передать результаты обратно активности из класса приложения .

Ответы [ 4 ]

1 голос
/ 13 июля 2020

Обновление:

Щелкните правой кнопкой мыши дерево проекта и добавьте новый файл AIDL с именем IMyAidlInterface.aidl:

package com.test.aidlsample;

import com.test.aidlsample.MyData;

interface IMyAidlInterface {
    List<MyData> getData(long id);
}

Если вам нужно вернуться Объекты для вашего клиента, вам необходимо объявить и определить их как parcelable и импортировать их также в файл helpl, вот MyData.aidl, который должен быть рядом с другим файлом helpl:

package com.test.aidlsample;

// Declare MyData so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable MyData;

, а это MyData. java в папке java:

public class MyData implements Parcelable {
    private long productId;
    private String productName;
    private long productValue;

    public MyData(long productId, String productName, long productValue) {
        this.productId = productId;
        this.productName = productName;
        this.productValue = productValue;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeLong(this.productId);
        dest.writeString(this.productName);
        dest.writeLong(this.productValue);
    }

    protected MyData(Parcel in) {
        this.productId = in.readLong();
        this.productName = in.readString();
        this.productValue = in.readLong();
    }

    public static final Parcelable.Creator<MyData> CREATOR = new Parcelable.Creator<MyData>() {
        @Override
        public MyData createFromParcel(Parcel source) {
            return new MyData(source);
        }

        @Override
        public MyData[] newArray(int size) {
            return new MyData[size];
        }
    };
}

Теперь соберите проект так, чтобы был собран класс-заглушка. После успешной сборки продолжите работу со службой:

public class SdkService extends Service {

    private IMyAidlInterface.Stub binder = new IMyAidlInterface.Stub() {
        @Override
        public List<MyData> getData(long id) throws RemoteException {
            //TODO: get data from db by id;
            List<MyData> data = new ArrayList<>();
            MyData aData = new MyData(1L, "productName", 100L);
            data.add(aData);
            return data;
        }
    };

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }
}

и добавьте службу в манифест sdk. Если вы добавляете sdk в качестве зависимости для клиента, например: implementation project(':sdk'), вам не нужно добавлять файлы AIDL в клиент. Если нет, вам необходимо добавить их и построить клиентское приложение. Теперь осталось реализовать клиентскую активность:

public class MainActivity extends AppCompatActivity {

    IMyAidlInterface mService;

    /**
     * Class for interacting with the main interface of the service.
     */
    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className,
                                       IBinder service) {
            // This is called when the connection with the service has been
            // established, giving us the service object we can use to
            // interact with the service.  We are communicating with our
            // service through an IDL interface, so get a client-side
            // representation of that from the raw service object.
            mService = IMyAidlInterface.Stub.asInterface(service);

            try {
                List<MyData> data = mService.getData(1L);
                updateUi(data);
            } catch (RemoteException e) {
                // In this case the service has crashed before we could even
                // do anything with it; we can count on soon being
                // disconnected (and then reconnected if it can be restarted)
                // so there is no need to do anything here.
            }

        }

        public void onServiceDisconnected(ComponentName className) {
            // This is called when the connection with the service has been
            // unexpectedly disconnected -- that is, its process crashed.
            mService = null;
        }
    };

    private void updateUi(List<MyData> data) {
        //TODO: Update UI here
    }

    @Override
    protected void onResume() {
        if (mService == null) {
            Intent serviceIntent = new Intent();
            
            //CAREFUL: serviceIntent.setComponent(new ComponentName("your.client.package", "your.sdk.service.path"));
            serviceIntent.setComponent(new ComponentName("com.test.sampleclient", "com.test.aidlsample.SdkService"));
            bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE);
        } else {
            try {
                updateUi(mService.getData(1L));
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        super.onResume();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

каждый раз, когда ваша клиентская активность становится видимой, она получает данные из службы sdk. Просто создайте свой logi c поверх этого шаблона. В активности sdk сохранять данные в базу данных, а в сервисе запрашивать их из базы данных. В этом примере я использовал простые параметры.

Я предполагал, что ваш sdk - это библиотека в клиентском приложении. Если нет, возможно, вам нужно внести небольшие изменения. И, как я уже упоминал ранее, вы можете найти более подробную информацию здесь: Android Язык определения интерфейса (AIDL) . Здесь, в SO, есть много примеров и еще больше вопросов и ответов по этой теме. Удачи.

Оригинал: Вам нужно получать обратные вызовы от активности, которая в настоящее время невидима, так как ваша активность SDK находится впереди, верно? Для этого вы можете создать базу данных для своего SDK, сохранить данные в своей базе данных и получить данные через AIDL в стартовом действии:

SdkService sdkService;
CallbackData callbackData

private ServiceConnection mConnection = new ServiceConnection() {
    // Called when the connection with the service is established
    public void onServiceConnected(ComponentName className, IBinder service) {
        sdkService = SdkService.Stub.asInterface(service);
    }

    // Called when the connection with the service disconnects unexpectedly
    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "Service has unexpectedly disconnected");
        sdkService = null;
    }
};

в onCreate:

Intent i = new Intent()
i.setClassName("your.sdk.packageName", "your.sdk.service.path.and.name");
bindService(i, mConnection, Context.BIND_AUTO_CREATE);

и при необходимости:

if(sdkService != null){
    callbackData = sdkService.getCallbacks();
    updateUI();
}

Просто будьте осторожны, получение связующего - это асинхронное c задание, поэтому, если вы вызываете bindService и сразу после вызова sdkService.getCallbackData вы получаете исключение NullPointerException. Таким образом, вы можете переместить getCallbacks и updateUI внутри onServiceConnected и вызвать bindService в onResume, чтобы каждый раз, когда активность становится видимой, вы должны проверять, есть ли CallbackData, чтобы вы могли обновить свой пользовательский интерфейс или что-то еще.

0 голосов
/ 17 июля 2020

Вы можете использовать sharedviewmodel, привязанную к обоим действиям; иметь переменную mutablelivedata, которую вы можете наблюдать в двух действиях.

в идеале, при первом действии вы можете просто поместить значение в переменную mutablelivedata. Затем во втором действии получите действие.

Перейдите по следующей ссылке, чтобы получить рекомендации.

Обзор ViewModel

0 голосов
/ 13 июля 2020

Проблема возникает после смерти процесса. Интерфейс становится нулевым, потому что он не сериализуется, и я больше не могу возвращать обратный вызов клиенту. Как мне отредактировать свой код, чтобы решить эту проблему? Возможно ли это вообще?

Это невозможно. Если клиентский процесс умирает, весь его исполняемый код, включая ваш SDK, стирается.

Я знаю, что клиент может инициализировать SDK в классе приложения, после чего он будет повторно установлен смерть. Однако такой подход приведет к тому, что клиенту будет сложно передать результаты активности из класса приложения.

И что? Если клиентская активность перезапущена, он должен снова вызвать SDK, чтобы установить новый экземпляр обратного вызова, который вы можете использовать с этого момента.

0 голосов
/ 12 июля 2020

Вы не можете использовать интерфейсы напрямую для связи между действиями.

Как только вы начинаете новое действие и новое действие становится видимым android ОС может в любой момент убить первое действие (вы можете попробовать это с помощью флага внутри вариант разработчика «Не вести активную деятельность»). Таким образом, пользователь вашего SDK будет жаловаться на определенное случайное «исключение нулевого указателя».

Итак, теперь, если вы хотите обмениваться данными между текущим и предыдущим экраном, вам, возможно, придется переосмыслить свое решение с использованием фрагментов. Отображение вашего пользовательского интерфейса с помощью фрагмента и передача вашего результата активности, которая затем обновит соответствующий фрагмент, для которого нужны данные.

Я столкнулся с аналогичной проблемой в одном существующем приложении, которое меня попросили исправить. Я переключил все приложение на фрагменты и отдельные действия, чтобы сначала выпустить исправление.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...