Farklı Amaçlarla başlatıldığında bir Aktivitenin birden çok örneği nasıl engellenir


121

Google Play Store uygulamasındaki (önceden Android Market olarak adlandırılıyordu) "Aç" düğmesi kullanılarak başlatıldığında uygulamamda bir hatayla karşılaştım . Görünüşe göre Play Store'dan Intentbaşlatmak, telefonun uygulama simgelerinin menüsünden başlatmaktan farklı bir şey kullanıyor . Bu, aynı Faaliyetin birbiriyle çelişen birden fazla kopyasının başlatılmasına yol açmaktadır.

Örneğin, uygulamam Faaliyetler ABC'sinden oluşuyorsa, bu sorun bir yığın ABCA'ya yol açabilir.

android:launchMode="singleTask"Bu sorunu gidermek için tüm Etkinlikler'i kullanmayı denedim , ancak HOME düğmesine her bastığımda Etkinlik yığınını kökten temizlemek gibi istenmeyen bir yan etkiye sahip.

Beklenen davranış: ABC -> HOME -> Ve uygulama geri yüklendiğinde, ihtiyacım olan: ABC -> HOME -> ABC

HOME düğmesini kullanırken kök aktiviteye sıfırlamadan aynı türden birden fazla Aktivite başlatmayı önlemenin iyi bir yolu var mı?


Yanıtlar:


187

Bunu onCreate'e ekleyin ve gitmekte fayda var:

// 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
Yıllardır bu hatayı çözmeye çalışıyorum ve işe yarayan çözüm buydu, bu yüzden çok teşekkür ederim! Bunun yalnızca Android Market'te bir sorun olmadığını, aynı zamanda bir uygulamayı bir sunucuya yükleyerek veya telefonunuza e-postayla göndererek bu soruna neden olduğunu da belirtmem gerekiyor. Bütün bunlar uygulamayı, hatanın bulunduğuna inandığım Paket Yükleyiciyi kullanarak yükler. Ayrıca, açık olmaması durumunda, yalnızca bu kodu kök etkinliğinizin onCreate yöntemine eklemeniz gerekir.
ubzack

2
Bunun cihaza dağıtılan imzalı bir uygulamada gerçekleşmesini çok garip buluyorum, ancak Eclipse'den dağıtılan bir hata ayıklama sürümünde değil. Hata ayıklamayı oldukça zorlaştırır!
Matt Connolly

6
Bu , Eclipse (veya IntelliJ veya başka bir IDE) aracılığıyla BAŞLATTIĞINIZ sürece Eclipse'den dağıtılan bir hata ayıklama sürümünde gerçekleşir. Uygulamanın cihaza nasıl yükleneceği ile ilgisi yoktur . Sorun, uygulamanın başlatılma biçiminden kaynaklanıyor .
David Wasser

2
Bu kodun uygulamanın mevcut örneğinin ön plana çıkarılmasını sağlayıp sağlamayacağını bilen var mı? Veya sadece finish () 'yi mi çağırır; ve kullanıcıyı herhangi bir şey olduğuna dair görsel bir belirti olmadan mı bırakıyor?
Carlos P

5
Oluşturulurken etkinlik ise @CarlosP değil görevin kök aktivitesi, orada gerekir (tanım gereği) bunun altında en azından bir başka etkinlik olacak. Bu etkinlik çağırırsa finish(), kullanıcı alttaki etkinliği görecektir. Bu nedenle, uygulamanın mevcut örneğinin ön plana çıkarılacağını güvenle varsayabilirsiniz. Durum böyle olmasaydı, uygulamanın birden çok örneğini ayrı görevlerde alırsınız ve oluşturulan etkinlik, görevinin kökü olur.
David Wasser

27

Sadece neden başarısız olduğunu ve bu hatayı programlı olarak nasıl yeniden üreteceğinizi açıklayacağım, böylece bunu test paketinize dahil edebilirsiniz:

  1. Eclipse veya Market Uygulaması üzerinden bir uygulama başlattığınızda, amaç işaretleri ile başlar: FLAG_ACTIVITY_NEW_TASK.

  2. Başlatıcı (ana) aracılığıyla başlatılırken, bayrakları kullanır: FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_BROUGHT_TO_FRONT | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED ve " MAIN " eylemini ve " LAUNCHER " kategorisini kullanır .

Bunu bir test senaryosunda yeniden oluşturmak isterseniz şu adımları kullanın:

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

Sonra diğer aktiviteye geçmek için ne gerekiyorsa yapın. Amaçlarım için, başka bir etkinliği başlatan bir düğme yerleştirdim. Ardından, başlatıcıya (ana sayfa) geri dönün:

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

Ve bununla başlatıcı aracılığıyla başlatmayı simüle edin:

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

İsTaskRoot () geçici çözümünü dahil etmediyseniz, bu sorunu yeniden oluşturacaktır. Bunu, bu hatanın bir daha asla oluşmadığından emin olmak için otomatik testimizde kullanırız.

Bu yardımcı olur umarım!


8

SingleTop başlatma modunu denediniz mi?

Http://developer.android.com/guide/topics/manifest/activity-element.html adresindeki açıklamalardan bazıları :

... yeni bir amacı işlemek için bir "singleTop" etkinliğinin yeni bir örneği de oluşturulabilir. Bununla birlikte, hedef görev yığınının üstünde zaten mevcut bir etkinlik örneğine sahipse, bu örnek yeni amacı alır (onNewIntent () çağrısında); yeni bir örnek oluşturulmaz. Diğer durumlarda - örneğin, "singleTop" etkinliğinin mevcut bir örneği hedef görevdeyse, ancak yığının en üstünde değilse veya bir yığının en üstündeyse, ancak hedef görevde değilse - a yeni örnek oluşturulacak ve yığın üzerinde itilecek.


2
Bunu düşündüm, ama ya aktivite yığının tepesinde değilse? Örneğin, singleTop AA'yı engelleyecek, ancak ABA'yı engelleyecek gibi görünüyor.
bsberkeley

Activity içindeki singleTop ve bitiş yöntemlerini kullanarak istediğinizi başarabilir misiniz?
Eric Levine

İstediğimi tam olarak başaracak mı bilmiyorum. Örnek: A ve B'yi patlattıktan sonra C aktivitesindeysem, o zaman yeni bir A aktivitesi başlatılır ve CA gibi bir şeye sahip olacağım, değil mi?
bsberkeley

Bu faaliyetlerin ne işe yaradığını daha fazla anlamadan buna cevap vermek zor. Başvurunuz ve faaliyetleriniz hakkında daha fazla ayrıntı verebilir misiniz? Ana Sayfa düğmesinin yaptığı şeyle nasıl davranmasını istediğiniz arasında bir uyumsuzluk olup olmadığını merak ediyorum. Ana sayfa düğmesi bir Etkinlikten çıkmaz, kullanıcının başka bir şeye geçebilmesi için onu "arka plana alır". Geri düğmesi, çıkılan / biten ve faaliyettir. Bu paradigmayı kırmak kullanıcıların kafasını karıştırabilir / hayal kırıklığına uğratabilir.
Eric Levine

Manifestin bir kopyasını görebilmeniz için bu ileti dizisine başka bir yanıt ekledim.
bsberkeley

4

Belki de bu sorun budur ? Veya aynı hatanın başka bir formu?


Eclipse dışındaki şeylerin neden olduğunu gösteren code.google.com/p/android/issues/detail?id=26658 adresine de bakın .
Kristopher Johnson

1
Öyleyse, bayatlayabilecek bir sorun açıklamasını kopyalayıp yapıştırmalı mıyım? Hangi kısımlar? Bağlantı değişirse temel parçalar saklanmalı mı ve cevabın güncel tutulması benim sorumluluğum mu? Bağlantının ancak sorun çözülürse geçersiz hale geleceğini düşünmek gerekir. Sonuçta bu bir bloga bağlantı değil.
DuneCat

2

Kabul edilen cevabın ( Duane Homick ) ele alınmamış vakalar olduğunu düşünüyorum :

Farklı ekstralarınız (ve sonuç olarak uygulamanın kopyaları) var:

  • Uygulamayı Market'ten veya ana ekran simgesiyle (Market tarafından otomatik olarak yerleştirilir) başlattığınızda
  • uygulamayı başlatıcıyla veya manuel olarak oluşturulan ana ekran simgesiyle başlattığınızda

İşte bu durumları ve durum çubuğu bildirimlerini de ele aldığına inandığım bir çözüm (SDK_INT> = 11 bildirimler için).

Manifesto :

    <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" />

Başlatıcı etkinliği :

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

Hizmet :

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

Bildirim :

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

Sorunun Xamarin Android ile ilgisi olmadığını anlıyorum ama başka hiçbir yerde görmediğim için bir şeyler göndermek istedim.

Bunu Xamarin Android'de düzeltmek için @ DuaneHomick'teki kodu kullandım ve MainActivity.OnCreate(). Xamarin ile farkı, Xamarin.Forms.Forms.Init(this, bundle);ve sonrasında gitmesi gerektiğidir LoadApplication(new App());. Yani benim OnCreate()şöyle görünür:

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

* Düzenleme: Android 6.0'dan beri, yukarıdaki çözüm belirli durumlar için yeterli değildir. Şimdi de belirledik LaunchModeiçin SingleTaskişler bir kez daha düzgün çalışması yapmış görünüyor. Ne yazık ki bunun başka şeyler üzerinde ne gibi etkileri olabileceğinden emin değilim.


0

Aynı sorunu yaşadım ve aşağıdaki çözümü kullanarak düzelttim.

Ana etkinliğinizde bu kodu onCreateyöntemin üstüne ekleyin :

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

bu izni manifestinize eklemeyi unutmayın.

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

umarım sana yardımcı olur.


0

Bu problemi de yaşadım

  1. Finish () 'yi çağırmayın; ev aktivitesinde sonsuz çalışırdı - ev aktivitesi bittiğinde ActivityManager tarafından çağrılır.
  2. Genellikle yapılandırma değiştiğinde (yani ekranı döndürme, dili değiştirme, telefon hizmeti değişiklikleri, yani mcc mnc vb.) Etkinlik yeniden oluşturulur - ve ev etkinliği çalışıyorsa, manifest'e ekleme ihtiyacı için tekrar A'yı çağırır android:configChanges="mcc|mnc"- eğer hücresel bağlantınız var, sistemi başlatırken veya push açarken veya her neyse, hangi yapılandırmanın mevcut olduğu için http://developer.android.com/guide/topics/manifest/activity-element.html#config adresine bakın .

0

Şu çözümü deneyin: Sınıf
oluşturun Applicationve orada tanımlayın:

public static boolean IS_APP_RUNNING = false;

Ardından, bunu eklemeden onCreateönce ilk (Başlatıcı) Etkinliğinizde setContentView(...):

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

PS Controllerbenim Applicationsınıfım.


İlkel boolean kullanmalısınız, bu da boşluğu kontrol etmeyi gereksiz kılar.
WonderCsabo

Bu her zaman işe yaramayacak. Uygulamanızı hiçbir zaman başlatamaz, uygulamanızdan çıkamaz ve ardından uygulamanızı hızla tekrar başlatamazsınız. Etkin etkinlik olmadığında Android, barındırma işletim sistemi sürecini mutlaka sonlandırmaz. Bu durumda, uygulamayı yeniden başlattığınızda, değişken IS_APP_RUNNINGolacak trueve uygulamanız hemen çıkacaktır. Kullanıcının eğlenceli bulacağı bir şey değil.
David Wasser

-2

Göreve yeniden denetlemeye izin verecek şekilde ayarlanmış benzeşimle SingleInstance başlatma modunu kullanmayı deneyin. Bu, her zaman yeni görevdeki etkinliği oluşturacak, ancak aynı zamanda yeniden ana öğelere de izin verecektir. Dis: Affinity özelliğini kontrol edin


2
Muhtemelen işe yaramayacak çünkü belgelere göre "yeniden ebeveynlik" standart "ve" singleTop "modlarıyla sınırlı." çünkü "" singleTask "veya" singleInstance "başlatma modlarına sahip etkinlikler yalnızca bir görevin kökünde olabilir"
bsberkeley

-2

Aynı aktivitelere başlamayı engellemenin bir yolunu buldum, bu benim için harika çalışıyor

if ( !this.getClass().getSimpleName().equals("YourActivityClassName")) {
    start your activity
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.