アクティビティの相互作用の問題、皆さんご存知ですか?

Androidデベロッパーにとって、アクティビティは初恋のようなもので、起動要件が低く、応答が速く、適切な準備を前提として実行できる多くのトリックがありますが、アクティビティの相互作用には多くの問題が発生する可能性があります。この記事では、一般的なバインダー、アクティビティジャンプおよびその他の側面に対する複数のアプリケーションの影響について、段階的に説明します。

バインダーパスデータ制限

データ転送の制限

インテントを介してアクティビティ間をジャンプするときにデータを渡すことは、すでに最も一般的な基本操作ですが、この操作は、次のシナリオなどの特定の状況でクラッシュを引き起こす可能性があります。

Intent intent = new Intent(MainActivity.this, NextPageActivity.class); 
Bitmap icon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
Bitmap bitmap = Bitmap.createScaledBitmap(icon, 1024, 1024, false); intent.putExtra("data",bitmap); 
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 
startActivity(intent);
复制代码

MainActivityのNextPageActivityページにジャンプし、ビットマップデータをインテントに渡します。上記のコードの実行によって発生したエラーは次のとおりです。image.pngエラーの理由:インテントによって送信されたデータが大きすぎることがわかります。 Androidシステムでは、バインダーを使用したデータ送信のサイズが制限されています(関連する制限は、frameworks / native / libs / byder / processState.cppクラスで定義されています。バージョンが異なるため、コードが若干異なる場合があります)、通常はシステム制限は1M、Android6およびAndroid7では、1回の転送のデータサイズ制限は200KBですが、バージョンやメーカーによって、この値は異なります。

実際、Binderの元の設計を考慮すると、この例外がスローされることは理解できます。Binder自体は、大きなデータをコピーするためではなく、プロセス間の頻繁で柔軟な通信用に設計されているため、パフォーマンスが優先され、データサイズは当然制限されます。 。

解決

1.オブジェクトをJson文字列に変換します

JVM 加载类时常会伴随额外的空间来保存类相关信息,将类中数据转化为 JSON 字符串可以减少数据大小。比如使用 Gson.toJson 方法。此方法几乎万能,当然,有时候将类转化成Json字符串后还是会超出Binder限制,说明这时候要传输的数据量较大,建议使用缓存等本地持久化方式、或全局观察者方式(EventBus、RxBus之类)来实现数据共享。

2、使用 transient 关键字修饰非必须字段,减少通过 Intent 传递的数据

transient只能修饰变量,而不能修饰方法、类、局部变量,而静态变量无论是否被transient修饰,都不能被序列化。可见,这种方式不适合所有场景,包括上述代码场景,例如下述传递一个bean类数据:

Intent intent = new Intent(MainActivity.this, NextPageActivity.class); intent.putExtra("data",new TestBean()); 
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 
startActivity(intent); 

static class TestBean implements Serializable { 
    private transient byte[] bean = new byte[1024*1024]; 
}
复制代码

在该bean类中对byte[]进行 transient 关键词修饰,运行代码后会发现再无报错。

指定process造成多个Application

特定情况(需求)下,我们要对Activity指定别的进程名,如下:

image.png

由于Activity能在不同的进程中启动,且每一个进程都会默认创建一个Application,因此可能会使得Application的onCreate()多次调用。我们可在Application代码中打印对应进程日志:

public class MainApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(getClass().getName(),"process name : "+getProcessName(this, Process.myPid()));
    }

    public String getProcessName(Context cxt, int pid) {
        ActivityManager am = (ActivityManager) cxt.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> runningApps = am.getRunningAppProcesses();
        if (runningApps == null) {
            return null;
        }
        for (ActivityManager.RunningAppProcessInfo procInfo : runningApps) {
            if (procInfo.pid == pid) {
                return procInfo.processName;
            }
        }
        return null;
    }

}
复制代码

MainActivity代码逻辑较简单,仅是一个点击事件,点击后跳转至指定process为“andev.process”的NextPageActivity,这里就不贴出来了,运行代码后,可看到对应打印的进程名日志为:

image.png

不难看出,MainApplication的onCreate()被调用了两次,如果按照我们往常的习惯在onCreate中进行一些环境的初始化的话,则对应各种初始化方法会执行两次,会造成一些意想不到的报错和崩溃。

解决方法

1、在Application的onCreate()中进行初始化前进行进程判断,如果是当前主进程则进行对应操作,一般都是用此方法;

2、网上说的抽象出一个与 Application 生命周期同步的类,并根据不同的进程创建相应的 Application 实例。不太建议,一般情况下通过当前进程名判断即可。

后台启动Activity问题

Android10(API29)开始,Android系统对从后台启动Activity做了一定限制,要用户同意“后台弹出界面”权限后,APP才能从后台服务或操作中弹出Activity,自从2019年5月份开始,小米开启了这项权限判断,从此各大厂商陆续添加此权限要求。其实此权限的设计初衷不难理解,为了避免当前前台用户的交互被打断,保证当前屏幕上展示的内容不受影响。想想看,风和日丽的一天,你吃着火锅唱着歌,玩着手里的农药,突然某个不知名APP在后台给你弹了个界面,你点了关闭重新回到农药,却发现只能看黑白电视了,还招来队友的问候,是不是顿时间就觉得火锅不香了。

其实这属于Android系统的优化,应尽可能的去适配厂商,给安卓生态圈带来更加用户体验,如果实在需要,可通过产品设计引导用户去开启对应权限,当然也有绕过此权限的方法,如下:

public void startWithNotify(Intent intent) {
    String channelId = "xxxxxxx";
    String nTag = "xxxxxxx";
    PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 2022712, intent, PendingIntent
            .FLAG_UPDATE_CURRENT);
    try {
        pendingIntent.send();

        notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
        if (notificationManager == null) {
            return;
        }
        if (Build.VERSION.SDK_INT >= 26 && notificationManager.getNotificationChannel(channelId) == null) {
            final NotificationChannel notificationChannel = new NotificationChannel(channelId, mContext.getString(R.string.app_name), NotificationManager.IMPORTANCE_HIGH);
            notificationChannel.setDescription(mContext.getString(R.string.app_name));
            notificationChannel.setLockscreenVisibility(-1);
            notificationChannel.enableLights(false);
            notificationChannel.enableVibration(false);
            notificationChannel.setShowBadge(false);
            notificationChannel.setSound((Uri) null, (AudioAttributes) null);
            notificationChannel.setBypassDnd(true);
            notificationManager.createNotificationChannel(notificationChannel);
        }
        //不弹出来的空通知栏
        NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext, channelId)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setFullScreenIntent(pendingIntent, true)
                .setCustomHeadsUpContentView(new RemoteViews(mContext.getPackageName(), R.layout.layout_empty_notify));
        if (Build.VERSION.SDK_INT >= 21) {
            builder.setVisibility(Notification.VISIBILITY_PRIVATE);
        }
        notificationManager.notify(nTag, notificationId, builder.build());
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {

                if (notificationManager != null) {//需延迟取消。不然activity出不来
                    notificationManager.cancel(nTag, notificationId);
                }
            }
        }, 1000);
    } catch (Exception e) {
        e.printStackTrace();
    }
}
复制代码

当然,此方法涉及到版本适配,不一定适配当前所有Room,此时可以采用另一套方案:

1、判断当前界面是否在前台,热后获取对应的ActivityManager;

2、利用系统的当前的task堆栈,遍历找到需要的task,将其调至强行切换到前台即可。

此方法相对耗时,有自己的弊端。但两种方式结合,能应对90%以上Room。而最好的方式,还是引导用户打开对应权限。

おすすめ

転載: juejin.im/post/7119465581232783397