Activity交互问题,你确定都知道?

对于AndroidDeveloper来说,activity就像初恋一样,启动要求低,响应快,准备得当的前提下能玩的花样也多,但关于activity交互方面的可能问题也有不少,本文将从常见Binder传递数据限制、多个Application对activity跳转的影响等方面进行逐步探讨。

Binder传递数据限制

数据传递限制

通过intent在Activity之间相互跳转时传递数据,已经是最常见的基本操作,但这种操作在特定情况下会引起崩溃,例如以下场景:

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页面,并通过intent传递一个bitmap数据,执行上述代码后引起的报错如下:
image.png
可见报错原因:Intent传输数据过大,而Android系统对使用Binder进行数据传输的大小做了限制(相关限制定义在frameworks/native/libs/binder/processState.cpp类中,可能因为版本不同而代码有些许不同),通常情况下系统限制为1M,在Android 6和Android 7上单次传输数据大小限制为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。而最好的方式,还是引导用户打开对应权限。

  • 18
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值