进程死亡后如何使用接口在活动之间进行通信?

How to use interface to communicate between activities after process death?

我正在构建一个 SDK 并且需要在活动之间实现回调,但实际上并没有完成 activity。我以前使用 onActivityResult 将结果返回给调用者 activity。但是,这会关闭 activity 并且我需要传递回调, 而不是从 SDK 完成 activity。我当前的实现:

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

    override fun response2() {
        
    }
};

例如,客户端在点击按钮后从他的activity调用initializeSDK()。然后客户端将接口作为参数传递,在SDK单例中设置为属性。然后我使用那个接口来 return 结果。

问题发生在进程死亡之后。接口变为空,因为它没有序列化,我不能return回调到客户端了。我应该如何编辑我的代码来解决这个问题?有可能吗?

我知道客户端可以在应用程序class中初始化SDK,然后在进程死亡后重新设置。但是,这种方法将导致客户端难以将 结果从应用程序 class.

传回 activity

您不能直接使用接口在活动之间进行通信。

一旦你开始一个新的 activity 并且新的 activity 变得可见 android OS 可以随时杀死第一个 activity(你可以试试这个在开发人员选项中有一个标志“不保留活动”)。因此,您的 SDK 用户会抱怨某些随机的“空指针异常”。

所以,现在如果您想在当前屏幕和上一个屏幕之间共享数据,您可能需要重新考虑使用片段的解决方案。 使用片段公开您的 UI 并将您的结果传回给 activity,后者将更新需要数据的适当片段。

我在一个现有的应用程序中遇到了类似的问题,我被要求修复。我将整个应用程序切换为片段和单个activity,首先发布一个热修复

更新:

右键单击项目树并添加名为 IMyAidlInterface.aidl:

的新 AIDL 文件
package com.test.aidlsample;

import com.test.aidlsample.MyData;

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

如果您需要 return 对象给您的客户,您需要将它们声明并定义为可打包的并将它们也导入 aidl 文件,这里是 MyData.aidl 应该在另一个 aidl 旁边文件:

package com.test.aidlsample;

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

这是 java 文件夹中的 MyData.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];
        }
    };
}

现在构建项目,以便构建 Stub class。成功构建后继续服务:

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 文件添加到客户端。如果没有,您必须添加它们并构建客户端应用程序。现在,只剩下实现客户端activity:

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

每次您的客户端 activity 获得可见性时,它都会从 sdk 服务获取数据。只需在此模板上构建您的逻辑。在 sdk activity 中将数据保存到数据库中,并在服务中从数据库中查询它们。我在这个示例中使用了简单的参数。

我假设你的 sdk 是客户端应用程序中的一个库。如果没有,您可能需要做一些小的修改。正如我之前提到的,您可以在此处找到更多详细信息:Android Interface Definition Language (AIDL)。 SO 中有很多示例,甚至更多 Q/A。祝你好运。

原文: 你需要从一个目前不可见的activity获取回调,因为你的SDKactivity在前面,对吧?为此,您可以为您的 SDK 创建一个数据库,将数据保存到您的数据库中,并通过 activity:

中的 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();
}

请注意获取活页夹是一项异步作业,因此如果您调用 bindService 并在调用 sdkService.getCallbackData 之后立即收到 NullPointerException。所以你可能想在 onServiceConnected 中移动 getCallbacks 和 updateUI 并在 onResume 中调用 bindService 所以每次 activity 变得可见你会检查是否有 CallbackData 这样你就可以更新你的 UI 或随便。

The problem occurs after process death. The interface becomes null, because it is not serialised and I can't return callback to client anymore. How should I edit my code to tackle this issue? Is it even possible?

这是不可能的。如果客户端进程终止,其所有执行代码(包括您的 SDK)都会被清除。

I know that client can initialise SDK in the application class, then it will be re-set after process death. However, such approach will result in difficulty for client to communicate results back to activity from application class.

那又怎样?如果客户端 Activity 重新启动,它应该再次调用 SDK 以设置一个新的回调实例,您可以从那时起使用它。

您可以使用绑定到两个活动的共享视图模型;有一个 mutablelivedata 变量,您可以从这两个活动中观察到它。

理想情况下,第一个 activity 您可以将值放在 mutablelivedata 变量中。然后在第二个 activity 得到 activity.

按照以下link给你一个指导。

ViewModel Overview