Cách ngăn chặn nhiều trường hợp của một Hoạt động khi nó được khởi chạy với các Ý định khác nhau


121

Tôi đã gặp lỗi trong ứng dụng của mình khi ứng dụng được khởi chạy bằng nút "Mở" trên ứng dụng Cửa hàng Google Play (trước đây được gọi là Android Market). Có vẻ như việc khởi chạy nó từ Cửa hàng Play sẽ khác Intentvới việc khởi chạy nó từ menu biểu tượng ứng dụng của điện thoại. Điều này dẫn đến nhiều bản sao của cùng một Hoạt động được khởi chạy, các bản sao này xung đột với nhau.

Ví dụ: nếu ứng dụng của tôi bao gồm các Hoạt động ABC, thì vấn đề này có thể dẫn đến một đống ABCA.

Tôi đã thử sử dụng android:launchMode="singleTask"trên tất cả các Hoạt động để khắc phục sự cố này, nhưng nó có tác dụng phụ không mong muốn là xóa ngăn xếp Hoạt động để root, bất cứ khi nào tôi nhấn nút HOME.

Hành vi mong đợi là: ABC -> HOME -> Và khi ứng dụng được khôi phục, tôi cần: ABC -> HOME -> ABC

Có cách nào tốt để ngăn việc khởi chạy nhiều Hoạt động cùng loại mà không cần đặt lại hoạt động gốc khi sử dụng nút HOME không?


Câu trả lời:


187

Thêm cái này vào onCreate và bạn nên thực hiện:

// 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
Tôi đã cố gắng giải quyết lỗi này trong nhiều năm và đây là giải pháp hiệu quả, vì vậy, cảm ơn bạn rất nhiều! Tôi cũng cần lưu ý rằng đây không chỉ là sự cố trong Android Market mà còn gây ra sự cố này bằng cách tải ứng dụng đó lên máy chủ hoặc gửi ứng dụng qua email tới điện thoại của bạn. Tất cả những thứ này đều cài đặt ứng dụng bằng Trình cài đặt gói, nơi tôi tin rằng lỗi cư trú. Hơn nữa, trong trường hợp không rõ ràng, bạn chỉ cần thêm mã này vào phương thức onCreate để biết hoạt động gốc của bạn là gì.
ubzack 14/12/11

2
Tôi thấy rất kỳ lạ khi điều này xảy ra trong một ứng dụng đã ký được triển khai cho thiết bị, nhưng không phải là một phiên bản gỡ lỗi được triển khai từ Eclipse. Làm cho nó khá khó khăn để gỡ lỗi!
Matt Connolly vào

6
Điều này xảy ra với phiên bản gỡ lỗi được triển khai từ Eclipse miễn là bạn BẮT ĐẦU nó cũng thông qua Eclipse (hoặc IntelliJ hoặc IDE khác). Nó không liên quan gì đến cách ứng dụng được cài đặt trên thiết bị. Vấn đề là do cách ứng dụng được bắt đầu .
David Wasser

2
Có ai biết liệu mã này có đảm bảo rằng phiên bản hiện có của ứng dụng sẽ được đưa lên nền trước không? Hay nó chỉ gọi finish (); và để lại cho người dùng không có dấu hiệu trực quan rằng điều gì đã xảy ra?
Carlos P

5
@CarlosP nếu hoạt động đó đã được tạo ra là không hoạt động thư mục gốc của công việc, có phải (theo định nghĩa) có ít nhất một hoạt động khác bên dưới nó. Nếu hoạt động này gọi finish()thì người dùng sẽ thấy hoạt động bên dưới. Do đó, bạn có thể an toàn giả định rằng phiên bản hiện có của ứng dụng sẽ được đưa lên nền trước. Nếu không phải như vậy, bạn sẽ có nhiều phiên bản của ứng dụng trong các tác vụ riêng biệt và hoạt động đang được tạo sẽ là gốc của nhiệm vụ của nó.
David Wasser

27

Tôi chỉ giải thích tại sao nó không thành công và cách tái tạo lỗi này theo chương trình để bạn có thể kết hợp nó vào bộ thử nghiệm của mình:

  1. Khi bạn khởi chạy một ứng dụng thông qua Eclipse hoặc Market App, ứng dụng đó sẽ khởi chạy với cờ ý định: FLAG_ACTIVITY_NEW_TASK.

  2. Khi khởi chạy qua trình khởi chạy (trang chủ), nó sử dụng cờ: FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_BROUGHT_TO_FRONT | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED và sử dụng hành động " MAIN " và danh mục " LAUNCHER ".

Nếu bạn muốn tái tạo điều này trong trường hợp thử nghiệm, hãy sử dụng các bước sau:

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

Sau đó, làm bất cứ điều gì cần thiết để bắt đầu hoạt động khác. Đối với mục đích của tôi, tôi chỉ đặt một nút bắt đầu một hoạt động khác. Sau đó, quay lại trình khởi chạy (trang chủ) với:

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

Và mô phỏng khởi chạy nó thông qua trình khởi chạy với điều này:

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

Nếu bạn chưa kết hợp giải pháp isTaskRoot (), điều này sẽ tạo ra sự cố. Chúng tôi sử dụng điều này trong thử nghiệm tự động của mình để đảm bảo rằng lỗi này không bao giờ xảy ra nữa.

Hi vọng điêu nay co ich!


8

Bạn đã thử chế độ khởi chạy singleTop chưa?

Dưới đây là một số mô tả từ http://developer.android.com/guide/topics/manifest/activity-element.html :

... một phiên bản mới của hoạt động "singleTop" cũng có thể được tạo để xử lý một ý định mới. Tuy nhiên, nếu tác vụ đích đã có một phiên bản hoạt động hiện có ở đầu ngăn xếp của nó, thì phiên bản đó sẽ nhận được ý định mới (trong một lệnh gọi onNewIntent ()); một phiên bản mới không được tạo. Trong các trường hợp khác - ví dụ: nếu một phiên bản hiện có của hoạt động "singleTop" nằm trong tác vụ mục tiêu, nhưng không ở trên cùng của ngăn xếp hoặc nếu nó ở trên cùng của ngăn xếp, nhưng không có trong tác vụ đích - phiên bản mới sẽ được tạo và đẩy lên ngăn xếp.


2
Tôi đã nghĩ đến điều đó, nhưng nếu hoạt động không ở đầu ngăn xếp thì sao? Ví dụ: có vẻ như singleTop sẽ ngăn AA, nhưng không ngăn ABA.
bsberkeley

Bạn có thể đạt được những gì bạn muốn bằng cách sử dụng singleTop và các phương thức kết thúc trong Activity không?
Eric Levine

Tôi không biết liệu nó có thực hiện được những gì tôi muốn hay không. Ví dụ: Nếu tôi đang tham gia hoạt động C sau khi chọn A và B, thì một hoạt động mới A sẽ được khởi chạy và tôi sẽ có một cái gì đó giống như CA phải không?
bsberkeley

Thật khó để trả lời điều này nếu không hiểu thêm về những hoạt động này. Bạn có thể cung cấp thêm thông tin chi tiết về ứng dụng của mình và các hoạt động không? Tôi tự hỏi liệu có sự không khớp giữa chức năng của nút Home và cách bạn muốn nó hoạt động hay không. Nút trang chủ không thoát khỏi Hoạt động, nó "làm nền" cho nó để người dùng có thể chuyển sang thứ khác. Nút quay lại là những gì thoát / kết thúc và hoạt động. Việc phá vỡ mô hình đó có thể khiến người dùng bối rối / thất vọng.
Eric Levine

Tôi đã thêm một câu trả lời khác vào chuỗi này để bạn có thể xem bản sao của tệp kê khai.
bsberkeley

4

Có lẽ nó là vấn đề này ? Hoặc một số hình thức khác của cùng một lỗi?


Xem thêm code.google.com/p/android/issues/detail?id=26658 , phần này chứng tỏ rằng lỗi do những thứ khác ngoài Eclipse gây ra.
Kristopher Johnson

1
Vì vậy, tôi có nên sao chép-dán một mô tả vấn đề có thể cũ không? Gồm những bộ phận nào? Các phần thiết yếu có nên được giữ lại nếu liên kết thay đổi không và tôi có trách nhiệm giữ câu trả lời được cập nhật không? Người ta nên nghĩ rằng liên kết chỉ trở nên không hợp lệ nếu vấn đề được giải quyết. Rốt cuộc đây không phải là một liên kết đến một blog.
DuneCat

2

Tôi nghĩ câu trả lời được chấp nhận ( Duane Homick ) đã giải quyết các trường hợp:

Bạn có các tính năng bổ sung khác nhau (và kết quả là các ứng dụng trùng lặp):

  • khi bạn khởi chạy ứng dụng từ Market hoặc bằng biểu tượng màn hình chính (được Market tự động đặt)
  • khi bạn khởi chạy ứng dụng bằng trình khởi chạy hoặc biểu tượng màn hình chính được tạo thủ công

Đây là một giải pháp (SDK_INT> = 11 cho các thông báo) mà tôi tin tưởng sẽ xử lý các trường hợp này và cả thông báo trên thanh trạng thái.

Tệp kê khai :

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

Hoạt động của trình khởi chạy :

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

Dịch vụ :

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

Thông báo :

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

Tôi nhận ra rằng câu hỏi không liên quan gì đến Xamarin Android nhưng tôi muốn đăng thứ gì đó vì tôi không thấy nó ở bất kỳ nơi nào khác.

Để khắc phục điều này trong Xamarin Android, tôi đã sử dụng mã từ @DuaneHomick và thêm vào MainActivity.OnCreate(). Sự khác biệt với Xamarin là phải đi sau Xamarin.Forms.Forms.Init(this, bundle);LoadApplication(new App());. Vì vậy, của tôi OnCreate()sẽ trông giống như:

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

* Chỉnh sửa: Kể từ Android 6.0, giải pháp trên là không đủ cho một số trường hợp nhất định. Bây giờ tôi cũng đã đặt LaunchModethành SingleTask, điều này dường như đã làm cho mọi thứ hoạt động bình thường trở lại. Tuy nhiên, không chắc điều này có thể có những ảnh hưởng gì đến những thứ khác.


0

Tôi đã gặp vấn đề tương tự và tôi đã khắc phục nó bằng giải pháp sau.

Trong hoạt động chính của bạn, hãy thêm mã này vào đầu onCreatephương pháp:

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

đừng quên thêm quyền này vào tệp kê khai của bạn.

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

hy vọng nó sẽ giúp bạn.


0

Tôi cũng có vấn đề này

  1. Đừng gọi finish (); trong hoạt động gia đình, nó sẽ chạy liên tục - hoạt động gia đình đang được ActivityManager gọi khi nó kết thúc.
  2. Thông thường khi cấu hình đang thay đổi (tức là xoay màn hình, thay đổi ngôn ngữ, dịch vụ điện thoại thay đổi tức là mcc mnc, v.v.) hoạt động sẽ tạo lại - và nếu hoạt động gia đình đang chạy thì nó sẽ gọi lại đến A. vì điều đó cần thêm vào tệp kê khai android:configChanges="mcc|mnc"- nếu bạn có kết nối với mạng di động, hãy xem http://developer.android.com/guide/topics/manifest/activity-element.html#config cho cấu hình nào khi khởi động hệ thống hoặc mở hoặc bất cứ điều gì.

0

Hãy thử giải pháp này:
Tạo Applicationlớp và xác định ở đó:

public static boolean IS_APP_RUNNING = false;

Sau đó, trong Hoạt động (Trình khởi chạy) đầu tiên của bạn onCreatetrước khi setContentView(...)thêm cái này:

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

PS ControllerApplicationlớp của tôi .


Bạn nên sử dụng boolean nguyên thủy, điều này làm cho việc kiểm tra null là không cần thiết.
WonderCsabo

Điều này không phải lúc nào cũng hiệu quả. Bạn sẽ không bao giờ có thể khởi chạy ứng dụng của mình, thoát khỏi ứng dụng và sau đó nhanh chóng khởi chạy lại ứng dụng của bạn. Android không nhất thiết phải giết quá trình lưu trữ hệ điều hành ngay khi không có hoạt động nào. Trong trường hợp này, khi bạn khởi động lại ứng dụng, biến IS_APP_RUNNINGsẽ là truevà ứng dụng của bạn sẽ thoát ngay lập tức. Không phải là thứ mà người dùng có thể thấy thú vị.
David Wasser

-2

hãy thử sử dụng chế độ khởi chạy SingleInstance với mối quan hệ được đặt để allowtaskreparenting Điều này sẽ luôn tạo hoạt động trong tác vụ mới nhưng cũng cho phép hiển thị lại nó. Kiểm tra dis: Thuộc tính sở thích


2
Có lẽ sẽ không hoạt động vì theo tài liệu "việc nuôi dạy con cái lại bị giới hạn ở chế độ" tiêu chuẩn "và" singleTop "." bởi vì "hoạt động với 'singleTask' hoặc 'singleInstance' chế độ khởi động chỉ có thể là ở thư mục gốc của một nhiệm vụ"
bsberkeley

-2

Tôi đã tìm ra cách để ngăn việc bắt đầu các hoạt động tương tự, điều này rất hiệu quả đối với tôi

if ( !this.getClass().getSimpleName().equals("YourActivityClassName")) {
    start your activity
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.