다른 인 텐트로 시작될 때 활동의 여러 인스턴스를 방지하는 방법


121

Google Play 스토어 앱 (이전에는 Android 마켓이라고 함) 의 "열기" 버튼을 사용하여 애플리케이션을 시작할 때 내 애플리케이션에서 버그를 발견했습니다 . Play 스토어 Intent에서 실행하는 것은 전화의 응용 프로그램 메뉴 아이콘에서 실행 하는 것과 다른 것을 사용하는 것 같습니다 . 이로 인해 서로 충돌하는 동일한 활동의 ​​여러 복사본이 실행됩니다.

예를 들어 내 앱이 활동 ABC로 구성된 경우이 문제로 인해 ABCA 스택이 발생할 수 있습니다.

android:launchMode="singleTask"이 문제를 해결하기 위해 모든 활동을 사용하려고 시도했지만 홈 버튼을 누를 때마다 활동 스택을 루트로 지우는 원치 않는 부작용이 있습니다.

예상되는 동작은 다음과 같습니다. ABC-> HOME-> 앱이 복원되면 ABC-> HOME-> ABC가 필요합니다.

HOME 버튼을 사용할 때 루트 활동으로 재설정하지 않고 동일한 유형의 여러 활동이 시작되는 것을 방지하는 좋은 방법이 있습니까?


답변:


187

이것을 onCreate에 추가하면 좋습니다.

// Possible work around for market launches. See https://issuetracker.google.com/issues/36907463
// for more details. Essentially, the market launches the main activity on top of other activities.
// we never want this to happen. Instead, we check if we are the root and if not, we finish.
if (!isTaskRoot()) {
    final Intent intent = getIntent();
    if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && Intent.ACTION_MAIN.equals(intent.getAction())) {
        Log.w(LOG_TAG, "Main Activity is not the root.  Finishing Main Activity instead of launching.");
        finish();
        return;       
    }
}

25
저는이 버그를 수년 동안 해결하려고 노력해 왔으며 이것이 효과가있는 솔루션이었습니다. 정말 감사합니다! 또한 이것은 Android 마켓의 문제 일뿐만 아니라 앱을 서버에 업로드하거나 이메일로 앱을 사이드로드하면이 문제가 발생합니다. 이 모든 것들은 내가 버그가 있다고 믿는 Package Installer를 사용하여 앱을 설치합니다. 또한 명확하지 않은 경우 루트 활동이 무엇인지 onCreate 메서드에이 코드를 추가하기 만하면됩니다.
ubzack 2011

2
이것은 장치에 배포 된 서명 된 앱에서 발생하지만 Eclipse에서 배포 된 디버그 버전에서는 발생하지 않는 것이 매우 이상합니다. 디버그하기가 매우 어렵습니다!
매트 코놀리

6
이것은 않습니다 당신이 이클립스 (또는 인 IntelliJ 또는 다른 IDE)을 통해도를 시작으로 이클립스에서 배포 디버그 버전으로 발생합니다. 앱이 기기에 설치 되는 방식과는 관련이 없습니다 . 문제는 앱이 시작 되는 방식 때문 입니다.
데이비드 WASSER

2
이 코드가 앱의 기존 인스턴스를 포 그라운드로 가져올 지 여부를 아는 사람이 있습니까? 아니면 그냥 finish (); 어떤 일이 발생했음을 시각적으로 알리지 않고 사용자를 남겨 둡니까?
Carlos P

5
@CarlosP 생성중인 활동이 작업의 루트 활동 이 아닌 경우 (정의에 따라) 그 아래에 하나 이상의 다른 활동 이 있어야 합니다. 이 활동이 호출 finish()되면 사용자는 아래에 있던 활동을 볼 수 있습니다. 따라서 앱의 기존 인스턴스가 포 그라운드로 이동 될 것이라고 안전하게 가정 할 수 있습니다. 그렇지 않은 경우 별도의 작업에 앱의 여러 인스턴스가 있고 생성되는 활동이 작업의 루트가됩니다.
David Wasser

27

실패한 이유와이 버그를 프로그래밍 방식으로 재현하여 테스트 스위트에 통합 할 수있는 방법을 설명하겠습니다.

  1. Eclipse 또는 Market App을 통해 앱을 시작하면 FLAG_ACTIVITY_NEW_TASK 인 텐트 플래그와 함께 시작됩니다.

  2. 런처 (홈)를 통해 시작할 때 다음 플래그를 사용합니다. FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_BROUGHT_TO_FRONT | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED, 작업 " MAIN "및 범주 " LAUNCHER "를 사용합니다.

테스트 케이스에서이를 재현하려면 다음 단계를 사용하십시오.

adb shell am start -f 0x10000000 -n com.testfairy.tests.regression.taskroot/.MainActivity 

그런 다음 다른 활동을 수행하는 데 필요한 모든 것을하십시오. 제 목적을 위해 다른 활동을 시작하는 버튼을 배치했습니다. 그런 다음 다음을 사용하여 런처 (홈)로 돌아갑니다.

adb shell am start -W -c android.intent.category.HOME -a android.intent.action.MAIN

다음과 같이 실행기를 통해 실행을 시뮬레이션합니다.

adb shell am start -a "android.intent.action.MAIN" -c "android.intent.category.LAUNCHER" -f 0x10600000 -n com.testfairy.tests.regression.taskroot/.MainActivity

isTaskRoot () 해결 방법을 통합하지 않은 경우 문제가 재현됩니다. 이 버그가 다시 발생하지 않도록 자동 테스트에서이를 사용합니다.

도움이 되었기를 바랍니다!


8

singleTop 실행 모드 를 사용해 보셨습니까 ?

다음은 http://developer.android.com/guide/topics/manifest/activity-element.html의 일부 설명입니다 .

... 새로운 인 텐트를 처리하기 위해 "singleTop"활동의 새 인스턴스를 만들 수도 있습니다. 그러나 대상 작업의 스택 맨 위에 이미 활동의 기존 인스턴스가있는 경우 해당 인스턴스는 onNewIntent () 호출에서 새 인 텐트를 수신합니다. 새 인스턴스가 생성되지 않습니다. 다른 상황에서 (예 : "singleTop"활동의 기존 인스턴스가 대상 작업에 있지만 스택의 맨 위에 있지 않은 경우 또는 스택의 맨 위에 있지만 대상 작업에는없는 경우) 새 인스턴스가 생성되고 스택에 푸시됩니다.


2
나는 그것을 생각했지만 활동이 스택의 맨 위에 있지 않으면 어떻게 될까요? 예를 들어, singleTop은 AA를 방지하지만 ABA는 방지하지 않는 것처럼 보입니다.
bsberkeley 2010

Activity 내에서 singleTop과 finish 방법을 사용하여 원하는 것을 얻을 수 있습니까?
Eric Levine

내가 원하는 것을 달성 할 수 있을지 모르겠습니다. 예 : A와 B를 터뜨린 후 활동 C에있는 경우 새 활동 A가 시작되고 CA와 같은 것을 갖게됩니다.
bsberkeley 2010

이러한 활동이하는 일에 대해 더 많이 이해하지 않고 대답하기가 어렵습니다. 신청 및 활동에 대한 자세한 내용을 제공 할 수 있습니까? 홈 버튼의 기능과 원하는 기능 사이에 불일치가 있는지 궁금합니다. 홈 버튼은 활동을 종료하는 것이 아니라 사용자가 다른 것으로 전환 할 수 있도록 "배경 화"합니다. 뒤로 버튼은 종료 / 종료 및 활동입니다. 패러다임을 깨면 사용자를 혼란스럽게하거나 좌절시킬 수 있습니다.
Eric Levine

이 스레드에 또 다른 답변을 추가하여 매니페스트 사본을 볼 수 있습니다.
bsberkeley 2010

4

아마도이 문제 일까요? 아니면 같은 버그의 다른 형태?


Eclipse가 아닌 다른 원인으로 인해 발생했음을 보여주는 code.google.com/p/android/issues/detail?id=26658 도 참조하세요 .
Kristopher Johnson

1
따라서 부실해질 수있는 문제 설명을 복사하여 붙여 넣어야합니까? 어떤 부분? 링크가 변경되면 필수 부분을 유지해야하며 답변을 최신 상태로 유지하는 것이 내 책임입니까? 문제가 해결 된 경우에만 링크가 유효하지 않다고 생각해야합니다. 결국 이것은 블로그에 대한 링크가 아닙니다.
DuneCat 2014 년

2

받아 들인 답변 ( Duane Homick )에는 처리되지 않은 사례가 있다고 생각합니다 .

다른 추가 기능이 있으며 결과적으로 앱이 중복됩니다.

  • 마켓 또는 홈 화면 아이콘 (마켓에서 자동으로 배치됨)에서 애플리케이션을 실행할 때
  • 런처 또는 수동으로 만든 홈 화면 아이콘으로 응용 프로그램을 시작할 때

다음은 이러한 사례와 상태 표시 줄 알림도 처리 할 수있는 솔루션 (SDK_INT> = 11 알림)입니다.

매니페스트 :

    <activity
        android:name="com.acme.activity.LauncherActivity"
        android:noHistory="true">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
            <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>
    </activity>
    <service android:name="com.acme.service.LauncherIntentService" />

런처 활동 :

public static Integer lastLaunchTag = null;
@Override
public void onCreate(final Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mInflater = LayoutInflater.from(this);
    View mainView = null;
    mainView = mInflater.inflate(R.layout.act_launcher, null); // empty layout
    setContentView(mainView);

    if (getIntent() == null || getIntent().getExtras() == null || !getIntent().getExtras().containsKey(Consts.EXTRA_ACTIVITY_LAUNCH_FIX)) {
        Intent serviceIntent = new Intent(this, LauncherIntentService.class);
        if (getIntent() != null && getIntent().getExtras() != null) {
            serviceIntent.putExtras(getIntent().getExtras());
        }
        lastLaunchTag = (int) (Math.random()*100000);
        serviceIntent.putExtra(Consts.EXTRA_ACTIVITY_LAUNCH_TAG, Integer.valueOf(lastLaunchTag));
        startService(serviceIntent);

        finish();
        return;
    }

    Intent intent = new Intent(this, SigninActivity.class);
    if (getIntent() != null && getIntent().getExtras() != null) {
        intent.putExtras(getIntent().getExtras());
    }
    startActivity(intent);
}

서비스 :

@Override
protected void onHandleIntent(final Intent intent) {
    Bundle extras = intent.getExtras();
    Integer lastLaunchTag = extras.getInt(Consts.EXTRA_ACTIVITY_LAUNCH_TAG);

    try {
        Long timeStart = new Date().getTime(); 
        while (new Date().getTime() - timeStart < 100) {
            Thread.currentThread().sleep(25);
            if (!lastLaunchTag.equals(LauncherActivity.lastLaunchTag)) {
                break;
            }
        }
        Thread.currentThread().sleep(25);
        launch(intent);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

private void launch(Intent intent) {
    Intent launchIintent = new Intent(LauncherIntentService.this, LauncherActivity.class);
    launchIintent.addCategory(Intent.CATEGORY_LAUNCHER);
    launchIintent.setAction(Intent.ACTION_MAIN); 
    launchIintent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    launchIintent.addFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 
    if (intent != null && intent.getExtras() != null) {
        launchIintent.putExtras(intent.getExtras());
    }
    launchIintent.putExtra(Consts.EXTRA_ACTIVITY_LAUNCH_FIX, true);
    startActivity(launchIintent);
}

알림 :

ComponentName actCN = new ComponentName(context.getPackageName(), LauncherActivity.class.getName()); 
Intent contentIntent = new Intent(context, LauncherActivity.class);
contentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);    
if (Build.VERSION.SDK_INT >= 11) { 
    contentIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); // if you need to recreate activity stack
}
contentIntent.addCategory(Intent.CATEGORY_LAUNCHER);
contentIntent.setAction(Intent.ACTION_MAIN);
contentIntent.putExtra(Consts.EXTRA_CUSTOM_DATA, true);

2

나는 질문이 Xamarin Android와 관련이 없다는 것을 알고 있지만 다른 곳에서는 보지 못했기 때문에 무언가를 게시하고 싶었습니다.

Xamarin Android에서이 문제를 해결하기 위해 @DuaneHomick의 코드를 사용하고 MainActivity.OnCreate(). Xamarin과의 차이점은 Xamarin.Forms.Forms.Init(this, bundle);LoadApplication(new App());. 그래서 내 OnCreate()모습은 다음과 같습니다.

protected override void OnCreate(Bundle bundle) {
    base.OnCreate(bundle);

    Xamarin.Forms.Forms.Init(this, bundle);
    LoadApplication(new App());

    if(!IsTaskRoot) {
        Intent intent = Intent;
        string action = intent.Action;
        if(intent.HasCategory(Intent.CategoryLauncher) && action != null && action.Equals(Intent.ActionMain, System.StringComparison.OrdinalIgnoreCase)) {
            System.Console.WriteLine("\nIn APP.Droid.MainActivity.OnCreate() - Finishing Activity and returning since a second MainActivity has been created.\n");
            Finish();
            return; //Not necessary if there is no code below
        }
    }
}

* 편집 : Android 6.0 이후로 위의 솔루션은 특정 상황에 충분하지 않습니다. 나는 이제 설정 LaunchModeSingleTask일을 다시 한 번 제대로 작동 만든 것으로 보인다. 불행히도 이것이 다른 것들에 어떤 영향을 미칠지 확실하지 않습니다.


0

나는 같은 문제가 있었고 다음 솔루션을 사용하여 수정했습니다.

주요 활동에서 onCreate메서드 상단에 다음 코드를 추가합니다 .

ActivityManager manager = (ActivityManager) this.getSystemService( ACTIVITY_SERVICE );
List<RunningTaskInfo> tasks =  manager.getRunningTasks(Integer.MAX_VALUE);

for (RunningTaskInfo taskInfo : tasks) {
    if(taskInfo.baseActivity.getClassName().equals(<your package name>.<your class name>) && (taskInfo.numActivities > 1)){
        finish();
    }
}

이 권한을 매니페스트에 추가하는 것을 잊지 마십시오.

< uses-permission android:name="android.permission.GET_TASKS" />

도움이되기를 바랍니다.


0

나는 또한이 문제가 있었다

  1. finish ()를 호출하지 마십시오. 홈 활동에서는 끝없이 실행됩니다. 홈 활동은 완료되면 ActivityManager에 의해 호출됩니다.
  2. 일반적으로 구성이 변경 될 때 (예 : 화면 회전, 언어 변경, 전화 통신 서비스 변경 (예 : mcc mnc 등)) 활동이 다시 생성되고, 홈 활동이 실행중인 android:configChanges="mcc|mnc"경우 매니페스트에 추가해야하는 경우 다시 A를 호출합니다. 셀룰러에 연결되어 있습니다. http://developer.android.com/guide/topics/manifest/activity-element.html#config 에서 시스템을 부팅하거나 푸시 열기 등의 구성을 참조 하십시오 .

0

이 솔루션을 시도
하십시오. Application클래스를 만들고 정의 하십시오 .

public static boolean IS_APP_RUNNING = false;

그런 다음 첫 번째 (런처) 활동에서 다음 onCreatesetContentView(...)추가하십시오.

if (Controller.IS_APP_RUNNING == false)
{
  Controller.IS_APP_RUNNING = true;
  setContentView(...)
  //Your onCreate code...
}
else
  finish();

추신 Controller은 내 Application수업입니다.


null 검사를 불필요하게 만드는 기본 부울을 사용해야합니다.
WonderCsabo

이것은 항상 작동하지 않습니다. 앱을 실행하고 앱을 종료 한 다음 앱을 빠르게 다시 실행할 수 없습니다. Android는 활성 활동이없는 즉시 호스팅 OS 프로세스를 반드시 종료하지 않습니다. 이 경우 앱을 다시 시작하면 변수 IS_APP_RUNNING가되며 true앱이 즉시 종료됩니다. 사용자가 재미있을 것 같지는 않습니다.
David Wasser

-2

allowtaskreparenting에 선호도가 설정된 SingleInstance 시작 모드를 사용해보십시오. 그러면 항상 새 작업에서 활동이 생성 되지만 부모 재지 정도 허용됩니다. dis : Affinity 속성 확인


2
문서에 따르면 "재 육아는"표준 "및"singleTop "모드로 제한됩니다." 때문에 singleTask "또는"singleInstance "로 활동" "발사 모드는 작업의 루트가 될 수 있습니다"
bsberkeley

-2

동일한 활동을 시작하는 것을 방지 할 수있는 방법을 찾았습니다.

if ( !this.getClass().getSimpleName().equals("YourActivityClassName")) {
    start your activity
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.