Cara mencegah beberapa contoh Aktivitas saat diluncurkan dengan Maksud berbeda

121

Saya menemukan bug dalam aplikasi saya saat diluncurkan menggunakan tombol "Buka" di aplikasi Google Play Store (sebelumnya disebut Android Market). Tampaknya meluncurkannya dari Play Store menggunakan cara yang berbeda Intentdari meluncurkannya dari menu ikon aplikasi ponsel. Hal ini menyebabkan beberapa salinan dari Aktivitas yang sama sedang diluncurkan, yang saling bertentangan.

Misalnya, jika aplikasi saya terdiri dari Aktivitas ABC, masalah ini dapat menyebabkan tumpukan ABCA.

Saya mencoba menggunakan android:launchMode="singleTask"semua Aktivitas untuk memperbaiki masalah ini, tetapi memiliki efek samping yang tidak diinginkan untuk membersihkan tumpukan Aktivitas ke root, setiap kali saya menekan tombol HOME.

Perilaku yang diharapkan adalah: ABC -> HOME -> Dan ketika aplikasi dipulihkan, saya membutuhkan: ABC -> HOME -> ABC

Adakah cara yang baik untuk mencegah peluncuran beberapa Aktivitas dengan tipe yang sama, tanpa menyetel ulang ke aktivitas root saat menggunakan tombol HOME?

bsberkeley.dll
sumber

Jawaban:

187

Tambahkan ini ke onCreate dan Anda harus melakukannya dengan baik:

// 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;       
    }
}
Duane Homick
sumber
25
Saya telah mencoba mengatasi bug ini selama bertahun-tahun, dan ini adalah solusi yang berhasil, jadi terima kasih banyak! Saya juga perlu mencatat bahwa ini bukan hanya masalah di dalam Android Market, tetapi juga mengesampingkan aplikasi dengan mengunggahnya ke server, atau mengirimkannya melalui email ke ponsel Anda, menyebabkan masalah ini. Semua hal ini menginstal aplikasi menggunakan Penginstal Paket, tempat saya yakin bug itu berada. Selain itu, jika tidak jelas, Anda hanya perlu menambahkan kode ini ke metode onCreate untuk mengetahui aktivitas root Anda.
ubzack
2
Saya merasa sangat aneh bahwa ini terjadi di aplikasi bertanda tangan yang diterapkan ke perangkat, tetapi bukan versi debug yang diterapkan dari Eclipse. Membuat proses debug menjadi cukup sulit!
Matt Connolly
6
Ini tidak terjadi dengan versi debug dikerahkan dari Eclipse selama Anda MULAI juga melalui Eclipse (atau IntelliJ atau IDE lainnya). Ini tidak ada hubungannya dengan bagaimana aplikasi terinstal di perangkat. Masalahnya adalah cara aplikasi dimulai .
David Wasser
2
Adakah yang tahu jika kode ini akan memastikan bahwa contoh aplikasi yang ada akan dibawa ke latar depan? Atau apakah itu hanya memanggil finish (); dan membiarkan pengguna tanpa indikasi visual bahwa telah terjadi sesuatu?
Carlos P
5
@CarlosP jika aktivitas yang sedang dibuat adalah tidak aktivitas akar tugas, ada harus (dengan definisi) setidaknya satu kegiatan lain di bawahnya. Jika aktivitas ini memanggil finish()maka pengguna akan melihat aktivitas yang ada di bawahnya. Karena itu, Anda dapat dengan aman berasumsi bahwa instance aplikasi yang sudah ada akan dibawa ke latar depan. Jika bukan itu masalahnya, Anda akan memiliki beberapa instance aplikasi dalam tugas terpisah dan aktivitas yang sedang dibuat akan menjadi root tugasnya.
David Wasser
27

Saya hanya akan menjelaskan mengapa gagal, dan bagaimana mereproduksi bug ini secara terprogram sehingga Anda dapat memasukkan ini ke dalam rangkaian pengujian Anda:

  1. Saat Anda meluncurkan aplikasi melalui Eclipse atau Market App, aplikasi diluncurkan dengan tanda maksud: FLAG_ACTIVITY_NEW_TASK.

  2. Saat diluncurkan melalui peluncur (beranda), ini menggunakan bendera: FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_BROUGHT_TO_FRONT | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED, dan menggunakan tindakan " MAIN " dan kategori " LAUNCHER ".

Jika Anda ingin mereproduksi ini dalam kasus uji, gunakan langkah-langkah berikut:

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

Kemudian lakukan apa pun yang diperlukan untuk memulai aktivitas lainnya. Untuk tujuan saya, saya hanya menempatkan tombol yang memulai aktivitas lain. Kemudian, kembali ke peluncur (rumah) dengan:

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

Dan simulasikan peluncurannya melalui peluncur dengan ini:

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

Jika Anda belum memasukkan solusi isTaskRoot (), ini akan mereproduksi masalah. Kami menggunakan ini dalam pengujian otomatis kami untuk memastikan bug ini tidak pernah terjadi lagi.

Semoga ini membantu!

gilm
sumber
8

Sudahkah Anda mencoba mode peluncuran singleTop ?

Berikut beberapa deskripsi dari http://developer.android.com/guide/topics/manifest/activity-element.html :

... instance baru dari aktivitas "singleTop" juga dapat dibuat untuk menangani maksud baru. Namun, jika tugas target sudah memiliki instance aktivitas yang ada di bagian atas tumpukannya, instance itu akan menerima maksud baru (dalam panggilan onNewIntent ()); sebuah contoh baru tidak dibuat. Dalam keadaan lain - misalnya, jika instance yang ada dari aktivitas "singleTop" ada dalam tugas target, tetapi tidak di atas tumpukan, atau jika di atas tumpukan, tetapi tidak di tugas target - a instance baru akan dibuat dan didorong ke tumpukan.

Eric Levine
sumber
2
Saya memikirkannya, tetapi bagaimana jika aktivitas tersebut tidak berada di urutan teratas? Misalnya, sepertinya singleTop akan mencegah AA, tetapi bukan ABA.
bsberkeley
Bisakah Anda mencapai apa yang Anda inginkan dengan menggunakan singleTop dan metode finish dalam Aktivitas?
Eric Levine
Saya tidak tahu apakah itu akan cukup mencapai apa yang saya inginkan. Contoh: Jika saya berada di aktivitas C setelah memunculkan A dan B, aktivitas A baru akan diluncurkan dan saya akan memiliki sesuatu seperti CA, bukan?
bsberkeley
Sulit untuk menjawab ini tanpa memahami lebih jauh tentang apa yang dilakukan oleh kegiatan tersebut. Dapatkah Anda memberikan detail lebih lanjut tentang aplikasi dan aktivitas Anda? Saya ingin tahu apakah ada ketidakcocokan antara fungsi tombol Beranda dan cara Anda menginginkannya. Tombol beranda tidak keluar dari Aktivitas, itu "melatarbelakangi" sehingga pengguna dapat beralih ke sesuatu yang lain. Tombol kembali adalah tombol keluar / selesai dan aktivitas. Melanggar paradigma itu mungkin membingungkan / membuat frustrasi pengguna.
Eric Levine
Saya telah menambahkan jawaban lain ke utas ini sehingga Anda dapat melihat salinan manifes.
bsberkeley
4

Mungkinkah masalah ini ? Atau bentuk lain dari bug yang sama?

DuneCat
sumber
Lihat juga code.google.com/p/android/issues/detail?id=26658 , yang menunjukkan bahwa hal itu disebabkan oleh hal-hal selain Eclipse.
Kristopher Johnson
1
Jadi saya harus menyalin dan menempel deskripsi masalah yang mungkin menjadi basi? Bagian mana? Haruskah bagian-bagian penting disimpan jika tautan berubah, dan apakah saya bertanggung jawab agar jawabannya selalu diperbarui? Orang harus berpikir bahwa tautan hanya menjadi tidak valid jika masalah diselesaikan. Ini bukan tautan ke blog.
DuneCat
2

Saya pikir jawaban yang diterima ( Duane Homick ) memiliki kasus yang tidak tertangani:

Anda memiliki tambahan yang berbeda (dan aplikasi duplikat sebagai hasilnya):

  • ketika Anda meluncurkan aplikasi dari Market atau dengan ikon layar beranda (yang ditempatkan oleh Market secara otomatis)
  • saat Anda meluncurkan aplikasi dengan peluncur atau ikon layar beranda yang dibuat secara manual

Berikut adalah solusi (SDK_INT> = 11 untuk notifikasi) yang saya percaya menangani kasus ini dan notifikasi statusbar juga.

Manifes :

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

Aktivitas peluncur :

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

Layanan :

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

Notifikasi :

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);
StanislavKo
sumber
2

Saya menyadari bahwa pertanyaan tersebut tidak ada hubungannya dengan Xamarin Android tetapi saya ingin memposting sesuatu karena saya tidak melihatnya di tempat lain.

Untuk memperbaiki ini di Xamarin Android saya menggunakan kode dari @DuaneHomick dan ditambahkan ke MainActivity.OnCreate(). Perbedaan dengan Xamarin adalah bahwa harus mengejar Xamarin.Forms.Forms.Init(this, bundle);dan LoadApplication(new App());. Jadi saya OnCreate()akan terlihat seperti:

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

* Edit: Sejak Android 6.0, solusi di atas tidak cukup untuk situasi tertentu. Saya sekarang juga telah mengatur LaunchModeke SingleTask, yang tampaknya telah membuat semuanya bekerja dengan benar sekali lagi. Tidak yakin apa efeknya pada hal-hal lain meskipun sayangnya.

hvaughan3
sumber
0

Saya memiliki masalah yang sama, dan saya memperbaikinya menggunakan solusi berikut.

Dalam aktivitas utama Anda, tambahkan kode ini di atas onCreatemetode:

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

jangan lupa untuk menambahkan izin ini di manifes Anda.

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

semoga membantu Anda.

gugarush
sumber
0

Saya juga mengalami masalah ini

  1. Jangan panggil finish (); di aktivitas rumah, ini akan berjalan tanpa henti - aktivitas rumah akan dipanggil oleh ActivityManager setelah selesai.
  2. Biasanya ketika konfigurasi berubah (yaitu memutar layar, mengubah bahasa, layanan telepon berubah misalnya mcc mnc, dll.) Aktivitas membuat ulang - dan jika aktivitas rumah sedang berjalan maka ia akan memanggil lagi ke A. untuk itu perlu menambahkan ke manifes android:configChanges="mcc|mnc"- jika Anda memiliki koneksi ke seluler, lihat http://developer.android.com/guide/topics/manifest/activity-element.html#config untuk konfigurasi apa yang ada saat mem-boot sistem atau mendorong buka atau apa pun.
pengguna1249350
sumber
0

Coba solusi ini:
Buat Applicationkelas dan tentukan di sana:

public static boolean IS_APP_RUNNING = false;

Kemudian di Aktivitas (Peluncur) pertama Anda onCreatesebelum setContentView(...)tambahkan ini:

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

PS Controlleradalah kelasku Application.

Volodymyr Kulyk
sumber
Anda harus menggunakan boolean primitif, yang membuat pemeriksaan null tidak diperlukan.
WonderCsabo
Ini tidak selalu berhasil. Anda tidak akan pernah bisa meluncurkan aplikasi Anda, keluar dari aplikasi Anda dan kemudian dengan cepat meluncurkan aplikasi Anda lagi. Android tidak serta merta mematikan proses OS hosting begitu tidak ada aktivitas aktif. Dalam kasus ini, saat Anda memulai aplikasi lagi, variabelnya IS_APP_RUNNINGakan menjadi truedan aplikasi Anda akan segera ditutup . Bukan sesuatu yang akan dianggap lucu oleh pengguna.
David Wasser
-2

coba gunakan mode peluncuran SingleInstance dengan afinitas yang disetel ke allowtaskreparenting. Ini akan selalu membuat aktivitas dalam tugas baru, tetapi juga mengizinkan reparentingnya. Centang dis: Atribut afinitas

Shaireen
sumber
2
Mungkin tidak akan berhasil karena, menurut dokumentasi, "mengasuh kembali terbatas pada mode" standar "dan" singleTop "." karena "aktivitas dengan mode peluncuran" singleTask "atau" singleInstance "hanya dapat berada di root tugas"
bsberkeley
-2

Saya menemukan cara untuk mencegah memulai aktivitas yang sama, ini bekerja dengan baik untuk saya

if ( !this.getClass().getSimpleName().equals("YourActivityClassName")) {
    start your activity
}
Odhik Susanto
sumber