先启动应用再打开目标Activity

前言

场合简述

在开辟Android
app的进度中,遭受那样一个必要:app中运行二个Service,该瑟维斯在独立进程中运作,与服务器保持长连接,将服务器推送过来的音信在布告栏中呈现,并设置点击动作,点击后跳转到app中对应的Activity。目前境遇的难题是Service以单身进度运维,在吸打消息并弹出布告后,app自身的经过有三种状态:

  1. app正在运作
  2. app已退出

对此第后生可畏种情景,管理就极其轻便了,直接将参数字传送入Intent并开发对应的Activity就可以。

但第三种情景相比较复杂,因为app已经脱离,而要伸开的Activity中的有些操作是索要信赖app的开端化的,这个最初化操作是在app运营进程中张开的。举例,三个购物应用推送了有个别新商品的音信,客商点击文告后步向商品详细情形的Activity,而该Activity中有个订购Button,点击该Button后就能从本土中收获客商的Id等新闻并发一条音信给服务器,告诉服务器某顾客订购了该商品。那个客商音讯是在app运营时与服务器进行风流倜傥多元相互作用后获取的。假若app退出后直接步入详细情形Activity并点击购买,就能因为获取不到顾客新闻而失误。

由此近来要解决的标题时,在Notification中装置点击动作,要是app本人正在运营,直接跳转到指标Activity;假如app已经脱离,先运行app完结先导化,再跳转到目的Activity。

新近境遇叁个很奇葩的主题材料,终于消除了,所以想着记录一下,方便大家仍然自身事后有亟待的时候能够参照他事他说加以考查学习。

方案和思路

作者们假若前段时间有七个Activity:

  1. SplashActivity
    用于展现app大图,同不时候张开顾客登入等操作,服务器重返数据后跳转到MainActivity。
  2. MainActivity app的主Activity。
  3. DetailActivity
    MainActivity中式茶食击Button步向的Activity,用于显示某件商品实际情况。

而弹出公告的Service在其余二个经过中。

大家要达到的指标是:

  1. 点击布告栏布告,若是app正在周转,则直接跳转到DetailActivity展现具体内容,在DetailActivity中按Back键再次来到MainActivity
  2. 点击通告栏文告,借使app已经脱离,先从SplashActivity步向,呈现app运转分界面,开始化操作完毕后步向MainActivity再跳转到DetailActivity展现具体内容,在DetailActivity中按Back键重返MainActivity。

带头的思绪是先判别app进度是或不是存在,如若存在的话,就利用startActivities运营MainActivity和DetailActivity。为啥还要运营MainActivity而不直接只运维DetailActivity?因为有如下处境,进度中的全部Activity都早已淡出了,但经过尚未被系统回笼,这个时候判断进度是不是留存再次来到true,然后只运营DetailActivity的话,按Back键职分栈就一向到底,再次来到桌面了。而作者辈要的效应是按Back键重返上一流Activity,也正是MainActivity。

即便app进度大器晚成度淡出,不设有了,那个时候就用叁个Intent运维应用,该Intent中隐含一个Bundle,
Bundle中存有运营DetailActivity所需的参数,那么些Intent传入SplashActivity后,再由SplashActivity传给MainActivity,在MainActivity中投入决断,要是有该参数,则象征应用是从文告栏运行的,要开展跳转到DetailActivity的操作,不然正是例行运维。

难点场景

代码达成

有了大约的实现思路后,大家来个demo实际操作一下。
先是,大家的demo有简要的机件:

  1. PushService,在新进度中运行的Service,负担监听服务器,收到服务器的音信后将消息广播出去,在本demo中,为了简化,只是轻巧的播放二个信息
  2. ShowNotificationReceiver,在新历程中登记的布罗兹castReceiver,收到PushService发的音信后,会在公告栏弹出布告
  3. NotificationReceiver,
    在新进度中注册的BroadcastReceiver,用来安装点击通知栏公告的动作,张开app中的有个别Activity
  4. SplashActivity, app运转页面,先是运营图片,3s后步向MainActivity
  5. MainActivity,app的主Activity
  6. DetailActivity,app中显示详细情形的Activity

用荣耀手提式有线电电话机使用OPPO推送一条新闻,然后点击布告栏中的音信运维应用,然后步向会话的Activity。应用运转后,若是当前界面不是会话界面,那么新新闻会在文告栏展现消息提示,然后点击会话音讯后却进不了会话的Activity,即点击了通告栏布告后,系统都并未有运维钦定Activity的意趣,未有阅览系统运维Activity的Log,到是会看出系统管理这些Activity的阴影。

PushService.java

率先是PushService,要在新进程中运营,要在AndroidManifest.xml中步向以投注册Service的代码

<service android:name=".PushService"
                 android:process=":push"/>

PushService的做事很简短,运行后发三个广播在公告栏彰显布告,然后常驻在后台

public class PushService extends Service{
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i("PushService", "PushService onCreate");
        //用AlarmManager定时发送广播
        AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE);

        Intent intent = new Intent(this, ShowNotificationReceiver.class);

        PendingIntent pendingIntent =
                PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

        am.set(AlarmManager.ELAPSED_REALTIME, SystemClock.currentThreadTimeMillis(), pendingIntent);

    }

}

其一钦赐的Activity不是会话的Activity,而是在AndroidManifest.xml文件中钦命android.intent.category.LAUNCHER的Activity
A。也正是说有会话音信都以先从那几个A伊始,然后把数据往背后的Activity传。

ShowNotificationReceiver.java

以此广播类用来在通报栏弹出文告

public class ShowNotificationReceiver extends BroadcastReceiver{
    private static final String TAG = "RepeatReceiver";
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "ShowNotificationReceiver onReceive");
        //设置点击通知栏的动作为启动另外一个广播
        Intent broadcastIntent = new Intent(context, NotificationReceiver.class);
        PendingIntent pendingIntent = PendingIntent.
                getBroadcast(context, 0, broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT);

        NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
        builder.setContentTitle("这就是通知的头")
                .setTicker("这是通知的ticker")
                .setContentIntent(pendingIntent)
                .setSmallIcon(android.R.drawable.ic_lock_idle_charging);

        Log.i("repeat", "showNotification");
        NotificationManager manager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
        manager.notify(2, builder.build());
    }
}

此地显示文告有二种艺术,大器晚成种是由手机系统在公告栏弹出,比如荣耀手机上利用金立推送,红米手提式无线电话机上采纳Nokia推送,此外后生可畏种是由运用的中远间隔进度弹出。

NotificationReceiver.java

点击公告栏后,会发送叁个播放,NotificationReceiver收到该广播后,就可以判断,app进度是还是不是依然存活,根据app进度的不等情况,定义不一样的app运维情势

public class NotificationReceiver extends BroadcastReceiver{

    @Override
    public void onReceive(Context context, Intent intent) {
        //判断app进程是否存活
        if(SystemUtils.isAppAlive(context, "com.liangzili.notificationlaunch")){
            //如果存活的话,就直接启动DetailActivity,但要考虑一种情况,就是app的进程虽然仍然在
            //但Task栈已经空了,比如用户点击Back键退出应用,但进程还没有被系统回收,如果直接启动
            //DetailActivity,再按Back键就不会返回MainActivity了。所以在启动
            //DetailActivity前,要先启动MainActivity。
            Log.i("NotificationReceiver", "the app process is alive");
            Intent mainIntent = new Intent(context, MainActivity.class);
            //将MainAtivity的launchMode设置成SingleTask, 或者在下面flag中加上Intent.FLAG_CLEAR_TOP,
            //如果Task栈中有MainActivity的实例,就会把它移到栈顶,把在它之上的Activity都清理出栈,
            //如果Task栈不存在MainActivity实例,则在栈顶创建
            mainIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

            Intent detailIntent = new Intent(context, DetailActivity.class);
            detailIntent.putExtra("name", "电饭锅");
            detailIntent.putExtra("price", "58元");
            detailIntent.putExtra("detail", "这是一个好锅, 这是app进程存在,直接启动Activity的");

            Intent[] intents = {mainIntent, detailIntent};

            context.startActivities(intents);
        }else {
            //如果app进程已经被杀死,先重新启动app,将DetailActivity的启动参数传入Intent中,参数经过
            //SplashActivity传入MainActivity,此时app的初始化已经完成,在MainActivity中就可以根据传入             //参数跳转到DetailActivity中去了
            Log.i("NotificationReceiver", "the app process is dead");
            Intent launchIntent = context.getPackageManager().
                    getLaunchIntentForPackage("com.liangzili.notificationlaunch");
            launchIntent.setFlags(
                    Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
            Bundle args = new Bundle();
            args.putString("name", "电饭锅");
            args.putString("price", "58元");
            args.putString("detail", "这是一个好锅, 这是app进程不存在,先启动应用再启动Activity的");
            launchIntent.putExtra(Constants.EXTRA_BUNDLE, args);
            context.startActivity(launchIntent);
        }
    }
}

初阶应用的率先个Activity
A也会有两种艺术,风度翩翩种是平素通过new来构造三个Intent,然后传入Activity
A的class;别的生机勃勃种是通过context.getPackageManager().getLaunchIntentForPackage(context.getPackageName())来拿到运转的Activity
A的Intent。然后调用PendingIntent.getActivity()方法,将获得的intent传入。

SplashActivity.java

SplashActivity.java先是app运转的图样,3s后进入MainActivity,
假使运维SplashActivity的Intent中蕴含参数,就将参数抽取,放入运营MainActivity的Intent中

public class SplashActivity extends AppCompatActivity{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_splash);
        //隐藏ActionBar
        getSupportActionBar().hide();
        //使用handler倒数3秒后进入MainActivity
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                Intent intent = new Intent(SplashActivity.this, MainActivity.class);
                //如果启动app的Intent中带有额外的参数,表明app是从点击通知栏的动作中启动的
                //将参数取出,传递到MainActivity中
                if(getIntent().getBundleExtra(Constants.EXTRA_BUNDLE) != null){
                    intent.putExtra(Constants.EXTRA_BUNDLE,
                            getIntent().getBundleExtra(Constants.EXTRA_BUNDLE));
                }
                startActivity(intent);
                finish();
            }
        }, 3000);
    }
}

那正是说难题来了,假诺是点击系统弹出的通告栏也许远程进度弹出的布告栏,假使只是利用此中一种运营方式运转应用,那么在使用运行后,点击文告栏中由后台远程进度弹出的新音讯公告,这时就不可能进来会话的Activity。从系统的日记来看,未有运维Activity,只是对Activity做了拍卖。

MainActivity.java

MainActivity中,假使有参数传入,就在最早化截至后,依据参数运转DetailActivity,若无参数字传送入,就此甘休自个儿的职分

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent(this, PushService.class);
        startService(intent);
        setTitle("MainActivity");
        Bundle bundle = getIntent().getBundleExtra(Constants.EXTRA_BUNDLE);
        if(bundle != null){
            //如果bundle存在,取出其中的参数,启动DetailActivity
            String name = bundle.getString("name");
            String price = bundle.getString("price");
            String detail = bundle.getString("detail");
            SystemUtils.startDetailActivity(this, name, price, detail);
            Log.i(TAG, "launchParam exists, redirect to DetailActivity");
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

也可以有人会想到是或不是要加叁个Intent.FLAG_ACTIVITY_NEW_TASK标识,因为在getLaunchIntentForPackage()方法中加了那一个标志。

DetailActivity.java

比较轻便,彰显传入的参数就能够:-D

public class DetailActivity extends AppCompatActivity{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_detail);
        getSupportActionBar().setTitle("DetailActivity");
        String name = getIntent().getStringExtra("name");
        String price = getIntent().getStringExtra("price");
        String detail = getIntent().getStringExtra("detail");

        ((TextView)findViewById(R.id.name)).setText(name);
        ((TextView)findViewById(R.id.price)).setText(price);
        ((TextView)findViewById(R.id.detail)).setText(detail);
    }
}

提及底测量检验开掘,只要利用还未有被运行,不管是点击系统弹出的通告栏照旧长途进程弹出的通告栏,借使再选拔新新闻文告,再点击布告栏,就能够进来会话Activity了。那纵然决断应用中是还是不是有Activity被运转就OK了,貌似难题能够化解了。

功能呈现

http://v.youku.com/v\_show/id\_XMTMwMjgyNTUwMA==.html?from=y1.7-1.2

难点消除

demo下载

https://github.com/slimhippo/androidcode

于是乎用了上边包车型地铁逻辑来判别是不是有前台Activity在运行。

/**
 * 判断UI进程是否正在运行
 * @return 返回true表示正在运行,否则没有运行
 */
public static boolean isForegroundRunning() {
 ActivityManager am = (ActivityManager) EimCloud.getContext().getSystemService(Context.ACTIVITY_SERVICE);
 List<ActivityManager.RunningAppProcessInfo> list = am.getRunningAppProcesses();
 if (list != null) {
  for (ActivityManager.RunningAppProcessInfo info : list) {
   if (info.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
  && EimCloud.getContext().getPackageName().equals(info.processName)) {
  return true;
   }
  }
 }

 return false;
}

拓展

只是下边包车型大巴大旨在红米手提式有线电电话机上凑效了,但在红米手提式有线电话机上依旧有标题,即便肖似的场景。索尼爱立信又坑爹了!

于是乎从头从上边的ActivityManager.RunningAppProcessInfo类中的importance变量的情事动手,然后测量试验各个气象大概现身的变量值,结果开掘效果救经引足,有个别场景难题依旧。

最终,又换种思路:不从Activity A初始起步应用,换个Activity
B,也正是在调用PendingIntent.getActivity()主意传入Intent对象使用B的class。运营B会发觉选取尚未被伊始化,则跳转到A实践初步化,然后再走正常流程。

再针对各样境况以致各类机型测量检验,开掘难点解决。从上边能够见到,固然不懂背后原理,但化解难题的笔触一定要广,特别是在急着发版本的时候,不要在风姿罗曼蒂克棵树上吊死。

总结

如上正是那篇小说的全部内容了,希望本文的开始和结果对我们的读书或然专业能推动一定的声援,要是万分大家可以留言交换。

你大概感兴趣的稿子:

  • Android
    8.0系列中文告栏的适配微手艺
  • Android通告栏微技艺一些亟待小心的小细节
  • Android编程完结通告栏进程条效果的不二等秘书籍现身说法
  • Android开荒达成剖断布告栏是不是张开及前往设置页面包车型地铁办法
  • Android之付出音信布告栏
  • 详细明白Android公告栏沉浸式/透明化完整技术方案
  • Android编制程序达成上方公告栏里闪动作效果果的情势
  • Android
    使用AlarmManager和NotificationManager来达成石英钟和布告栏
  • Android编制程序之通告栏的用法小结
  • android使用NotificationListenerService监听布告栏新闻
  • Android种采用Notification完结通告管理以至自定义布告栏实例(示例四)
  • Android
    8.0系统中文告栏的适配安详严整

Post Author: admin

发表评论

电子邮件地址不会被公开。 必填项已用*标注