วิธีป้องกันไม่ให้โหลดกิจกรรมสองครั้งเมื่อกดปุ่ม


96

ฉันพยายามป้องกันไม่ให้โหลดกิจกรรมซ้ำสองครั้งหากฉันกดปุ่มสองครั้งทันทีหลังจากคลิกครั้งแรก

ฉันมีกิจกรรมที่โหลดเมื่อคลิกปุ่มพูด

 myButton.setOnClickListener(new View.OnClickListener() {
      public void onClick(View view) {
       //Load another activity
    }
});

ตอนนี้เนื่องจากกิจกรรมที่จะโหลดมีการเรียกเครือข่ายจึงต้องใช้เวลาเล็กน้อยในการโหลด (MVC) ฉันแสดงมุมมองการโหลดสำหรับสิ่งนี้ แต่ถ้าฉันกดปุ่มสองครั้งก่อนหน้านั้นฉันจะเห็นกิจกรรมที่กำลังโหลดสองครั้ง

มีใครรู้วิธีป้องกันปัญหานี้หรือไม่?


คุณสามารถปิดใช้งานปุ่มนี้ได้หลังจากเปิดกิจกรรม ... และเมื่อกิจกรรมเสร็จสิ้นให้เปิดใช้งานอีกครั้ง ... คุณสามารถตรวจจับการสิ้นสุดของกิจกรรมที่สองได้โดยเรียกใช้ฟังก์ชัน onActivityResult
Maneesh

ปิดใช้งานปุ่มเมื่อมีการคลิกครั้งแรกและเปิดใช้งานใหม่ในภายหลังเมื่อคุณต้องการให้คลิกปุ่มอีกครั้งเท่านั้น
JimmyB

การปิดใช้งานไม่ได้ผลในลักษณะง่ายๆหากคำสั่งถัดไปเป็นสำหรับการเริ่มกระบวนการหรือกิจกรรมที่ยาวนาน ... ในการปิดใช้งานปุ่มคุณต้องสร้างเธรดแยกต่างหาก ...
Awais Tariq

หากกดปุ่ม API เดียวกันสองครั้งโปรดดูที่นี่: techstricks.com/avoid-multiple-requests-when-using-volley
Shailendra Madda

คำตอบ:


69

ในตัวฟังเหตุการณ์ของปุ่มให้ปิดใช้งานปุ่มและแสดงกิจกรรมอื่น

    Button b = (Button) view;
    b.setEnabled(false);

    Intent i = new Intent(this, AnotherActitivty.class);
    startActivity(i);

แทนที่onResume()เพื่อเปิดใช้งานปุ่มอีกครั้ง

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

        Button button1 = (Button) findViewById(R.id.button1);
        button1.setEnabled(true);
    }

1
นี่คือแนวทางที่ถูกต้อง มันจะจัดการ Button Selected States ให้คุณด้วย (หากคุณระบุ) และ "สารพัด" ดีไซน์ Material ทั้งหมดที่คุณคาดหวังจากวิดเจ็ตมาตรฐานธรรมดา ๆ ไม่น่าเชื่อว่ามีคนใช้ตัวจับเวลาสำหรับสิ่งนี้ จากนั้นคุณก็เริ่มเห็นห้องสมุดแปลก ๆ เพื่อจัดการสิ่งเหล่านี้ ...
Martin Marconcini

158

เพิ่มสิ่งนี้ในActivityคำจำกัดความของคุณในAndroidManifest.xml...

android:launchMode = "singleTop"

ตัวอย่างเช่น:

<activity
            android:name=".MainActivity"
            android:theme="@style/AppTheme.NoActionBar"
            android:launchMode = "singleTop"/>

โอเคฉันเดาว่าคุณกำลังประมวลผลที่ยาวนานหลังจากเริ่มกิจกรรมใหม่ .. นั่นคือสาเหตุที่ทำให้หน้าจอกลายเป็นสีดำ ตอนนี้ถ้าคุณต้องการหลีกเลี่ยงหน้าจอสีดำคุณควรแสดงกล่องโต้ตอบความคืบหน้าเมื่อเริ่มกิจกรรมและดำเนินการประมวลผลแบบยาวในเธรดแยกต่างหาก (เช่นเธรด UI หรือใช้คลาส async) เมื่อการประมวลผลของคุณเสร็จสิ้นให้ซ่อนกล่องโต้ตอบนั้น เป็นทางออกที่ดีที่สุดในความรู้ของฉันและฉันได้ใช้มันหลายครั้ง ... :)
Awais Tariq

ฉันมีกล่องโต้ตอบที่จะแสดง แต่ใช่ฉันมีวิธีการชี้เว็บวิธีหนึ่งใน onCreate แต่นี่เป็นทางออกเดียวหรือไม่? เพราะ ณ จุดนี้ฉันต้องการจัดการโดยไม่ต้องเปลี่ยนด้ายและทั้งหมด คุณรู้วิธีอื่น ๆ ที่เป็นไปได้ไหม และฉันมีปุ่มในรายการอะแดปเตอร์ของฉันและฉันได้ประกาศวิธีการสำหรับมันใน xml ไม่ใช่แบบเป็นโปรแกรม
tejas

2
มีอะไรอีกบ้าง ??? ไม่ทางใดก็ทางหนึ่งคุณต้องใช้เธรดเพื่อให้ได้แอปที่ดูเรียบเนียน ... ลองดูนะ .. ;) เพียงแค่ใส่รหัสปัจจุบันทั้งหมดในวิธีการและเรียกวิธีการนั้นจากเธรดแยกต่างหากในที่เดียวกับที่คุณเขียน ก่อนหน้านี้ ... มันแทบจะไม่เพิ่มโค้ดห้าถึงหกบรรทัด ..
Awais Tariq

19
วิธีนี้ป้องกันไม่ให้มีกิจกรรมสองอินสแตนซ์ที่มีอยู่ แต่ไม่ได้ป้องกันไม่ให้โค้ดทำงานสองครั้งไม่ถูกต้อง คำตอบที่ได้รับการยอมรับนั้นดีกว่าแม้ว่าจะมีคะแนนเสียงน้อยกว่าก็ตาม
lilbyrdie

18
สิ่งนี้ไม่ถูกต้องทำให้ไม่มีกิจกรรมซ้ำสองแม้จะอยู่คนละงาน วิธีที่ถูกต้องandroid:launchMode = "singleTop"คือซึ่งจะบรรลุผลโดยไม่ทำลายการทำงานหลายอย่างพร้อมกันของ Android เอกสารระบุว่าแอปส่วนใหญ่ไม่ควรใช้singleInstanceตัวเลือกนี้
Nohus

37

คุณสามารถใช้แฟล็กเจตนาเช่นนี้

Intent intent = new Intent(Class.class);    
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
activity.startActivity(intent);

จะทำให้มีเพียงกิจกรรมเดียวเท่านั้นที่เปิดอยู่ที่ด้านบนสุดของกองประวัติ


4
คำตอบนี้รวมกับคำตอบที่ได้รับการโหวตมากที่สุดดูเหมือนจะได้ผลดีที่สุด ใช้แฟล็กนี้ในรายการกิจกรรม: android:launchMode = "singleTop"วิธีนี้จะแก้ไขได้โดยไม่ต้องเพิ่มแฟล็กในทุกเจตนา
Nohus

1
สิ่งนี้ไม่มีประโยชน์เมื่อคุณต้องการกิจกรรมที่ซ้อนกันเนื่องจากคุณไม่สามารถมีกิจกรรมประเภทเดียวกันสองกิจกรรมได้
Behnam Heydari

5
สิ่งนี้ใช้ไม่ได้ในกรณีของ startActivityForResult
raj

27

เนื่องจาก SO ไม่อนุญาตให้ฉันแสดงความคิดเห็นเกี่ยวกับคำตอบอื่น ๆ ฉันจึงต้องสร้างมลพิษให้กับเธรดนี้ด้วยคำตอบใหม่

คำตอบทั่วไปสำหรับปัญหา "กิจกรรมเปิดสองครั้ง" และประสบการณ์ของฉันกับโซลูชันเหล่านี้ (Android 7.1.1):

  1. ปุ่มปิดใช้งานที่เริ่มกิจกรรม: ใช้งานได้ แต่รู้สึกเงอะงะเล็กน้อย หากคุณมีหลายวิธีในการเริ่มกิจกรรมในแอปของคุณ (เช่นปุ่มในแถบการทำงานและโดยการคลิกที่รายการในมุมมองรายการ) คุณต้องติดตามสถานะเปิด / ปิดใช้งานขององค์ประกอบ GUI หลายรายการ นอกจากนี้ยังไม่สะดวกในการปิดใช้งานรายการที่คลิกในมุมมองรายการเช่น ดังนั้นจึงไม่ใช่แนวทางสากล
  2. LaunchMode = "singleInstance": ไม่ทำงานกับ startActivityForResult () หยุดการนำทางย้อนกลับด้วย startActivity () ไม่แนะนำสำหรับแอปพลิเคชันทั่วไปตามเอกสารแสดงรายการของ Android
  3. LaunchMode = "singleTask": ไม่ทำงานกับ startActivityForResult () ไม่แนะนำสำหรับแอปพลิเคชันทั่วไปโดยเอกสารรายการ Android
  4. FLAG_ACTIVITY_REORDER_TO_FRONT: แบ่งปุ่มย้อนกลับ
  5. FLAG_ACTIVITY_SINGLE_TOP: ไม่ทำงานกิจกรรมยังคงเปิดอยู่สองครั้ง
  6. FLAG_ACTIVITY_CLEAR_TOP: นี่เป็นเครื่องเดียวที่ใช้ได้สำหรับฉัน

แก้ไข: ใช้สำหรับการเริ่มต้นกิจกรรมด้วย startActivity () เมื่อใช้ startActivityForResult () ฉันต้องตั้งค่าทั้ง FLAG_ACTIVITY_SINGLE_TOP และ FLAG_ACTIVITY_CLEAR_TOP


FLAG_ACTIVITY_CLEAR_TOP: นี่เป็นเครื่องเดียวที่ใช้ได้กับฉันบน Android 7.1.1
Mingjiang Shi

1
ฉันใช้ "FLAG_ACTIVITY_REORDER_TO_FRONT" และใช้งานได้ดีและปุ่มย้อนกลับก็ทำงานได้ตามปกติ คำว่า "Breaks back button" หมายความว่าอย่างไร คุณช่วยชี้แจงเรื่องนี้ได้ไหม
Mirmukhsin Sodikov

ฉันพบว่าธง "REORDER" มีข้อบกพร่อง ... และไม่ได้เรียงลำดับใหม่ใน KitKat อย่างไรก็ตามฉันตรวจสอบใน Lollipop และ Pie แล้วว่าใช้งานได้ดี
Mirmukhsin Sodikov

9

มันใช้ได้เฉพาะกับฉันเมื่อ startActivity(intent)

intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);

1
@raj คุณลองเพิ่มสิ่งนี้android:launchMode = "singleInstance"ในไฟล์ Manifest ของแท็กกิจกรรมของคุณหรือยัง
Shailendra Madda

5

ใช้ singleInstance เพื่อหลีกเลี่ยงกิจกรรมที่จะเรียกใช้สองครั้ง

<activity
            android:name=".MainActivity"
            android:label="@string/activity"
            android:launchMode = "singleInstance" />

4

สมมติว่า @wannik พูดถูก แต่ถ้าเรามีมากกว่า 1 ปุ่มเรียกผู้ฟังการกระทำเดียวกันและฉันคลิกสองปุ่มเกือบครั้งเดียวกันก่อนเริ่มกิจกรรมถัดไป ...

ดังนั้นจึงเป็นการดีหากคุณมีสนามprivate boolean mIsClicked = false;และในผู้ฟัง:

if(!mIsClicked)
{
    mIsClicked = true;
    Intent i = new Intent(this, AnotherActitivty.class);
    startActivity(i);
}

และonResume()เราจำเป็นต้องคืนสถานะ:

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

    mIsClicked = false;
}

อะไรคือความแตกต่างระหว่างคำตอบของฉันกับ @ วันนิก?

หากคุณตั้งค่าเปิดใช้งานเป็นเท็จในฟังก์ชั่นการเรียกดูปุ่มอื่นที่ใช้ตัวฟังเดียวกันจะยังคงเปิดใช้งานอยู่ ดังนั้นเพื่อให้แน่ใจว่าการกระทำของผู้ฟังไม่ได้ถูกเรียกซ้ำสองครั้งคุณจำเป็นต้องมีบางสิ่งที่ปิดการใช้งานการเรียกของผู้ฟังทั้งหมด (ไม่เป็นไรหากเป็นอินสแตนซ์ใหม่หรือไม่)

อะไรคือความแตกต่างระหว่างคำตอบของฉันกับคำตอบอื่น ๆ ?

พวกเขากำลังคิดในทางที่ถูกต้อง แต่พวกเขาไม่ได้คิดที่จะกลับไปใช้กิจกรรมการโทรแบบเดิมในอนาคต :)


เซอร์โวขอขอบคุณสำหรับการวิจัยของคุณ คำถามนี้ได้รับการแก้ไขแล้วคำตอบของคุณดูดีอย่างไรสำหรับสถานการณ์ที่คุณบอก ให้ฉันลองและมาพร้อมกับผลลัพธ์ :)
tejas

1
ฉันมีปัญหานี้ในหนึ่งในเกมของฉัน ฉันมี baloons "เลือกระดับ" ที่มีผู้ฟังคนเดียวกันและมุมมองต่างกันเพียงแท็ก ดังนั้นถ้าฉันเลือกสองลูกโป่งอย่างรวดเร็วมันจะเริ่มสองกิจกรรม ฉันรู้ว่าเป็นเพราะกิจกรรมใหม่เริ่มดังขึ้น .. และในกรณีนี้เสียงจะเล่นสองครั้ง ... แต่คุณสามารถตรวจสอบได้โดยคลิกย้อนกลับซึ่งจะนำคุณไปสู่กิจกรรมก่อนหน้านี้
Sir NIkolay Cesar The First

1
สิ่งนี้ไม่เพียงพอ คุณต้องใช้synchronized(mIsClicked) {...}เช่นกันจึงจะปลอดภัย 100%
Monstieur

@Monstieur คุณไม่ต้องการบล็อกที่ซิงโครไนซ์เพราะนี่คือ Main Thread ทั้งหมด…
Martin Marconcini

@MartinMarconcini เพียงเพราะมันปลอดภัยในกิจกรรม Android ไม่ได้ทำให้รหัสดี หากเป็นคลาสแบบสแตนด์อโลนจะต้องได้รับการบันทึกว่าไม่ปลอดภัยต่อเธรด
Monstieur

4

สำหรับสถานการณ์นี้ฉันจะไปหาหนึ่งในสองแนวทางsingleTaskใน manifest.xml หรือแฟล็กในกิจกรรมonResume()& onDestroy()วิธีการตามลำดับ

สำหรับวิธีแก้ปัญหาแรก : ฉันชอบใช้singleTaskสำหรับกิจกรรมในรายการมากกว่าsingleInstanceตามการใช้singleInstanceฉันพบว่าในบางครั้งกิจกรรมจะสร้างอินสแตนซ์แยกใหม่สำหรับตัวเองซึ่งส่งผลให้มีหน้าต่างแอปพลิเคชันสองหน้าต่างแยกกันในแอปที่กำลังทำงานอยู่ ใน bcakground และนอกเหนือจากการจัดสรรหน่วยความจำเพิ่มเติมซึ่งจะส่งผลให้ประสบการณ์การใช้งานของผู้ใช้แย่มากเมื่อผู้ใช้เปิดมุมมองแอพเพื่อเลือกแอพบางตัวเพื่อดำเนินการต่อ ดังนั้นวิธีที่ดีกว่าคือการกำหนดกิจกรรมที่ manifest.xml ดังต่อไปนี้:

<activity
    android:name=".MainActivity"
    android:launchMode="singleTask"</activity>

คุณสามารถตรวจสอบโหมดการเปิดตัวกิจกรรมที่นี่


สำหรับแนวทางที่สองคุณต้องกำหนดตัวแปรคงที่หรือตัวแปรการกำหนดค่าตามความชอบตัวอย่างเช่น:

public class MainActivity extends Activity{
    public static boolean isRunning = false;

    @Override
    public void onResume() {
        super.onResume();
        // now the activity is running
        isRunning = true;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // now the activity will be available again
        isRunning = false;
    }

}

และจากอีกด้านหนึ่งเมื่อคุณต้องการเปิดกิจกรรมนี้เพียงเลือก:

private void launchMainActivity(){
    if(MainActivity.isRunning)
        return;
    Intent intent = new Intent(ThisActivity.this, MainActivity.class);
    startActivity(intent);
}

3

ฉันคิดว่าคุณกำลังจะแก้ปัญหาในทางที่ผิด โดยทั่วไปมันเป็นความคิดที่ไม่ดีสำหรับกิจกรรมที่จะทำให้ยาวทำงานในหน้าเว็บใด ๆ ของวิธีการเริ่มต้นวงจร ( onCreate(), onResume()ฯลฯ ) จริงๆแล้วควรใช้วิธีการเหล่านี้เพื่อสร้างอินสแตนซ์และเริ่มต้นออบเจ็กต์ที่กิจกรรมของคุณจะใช้จึงควรจะค่อนข้างรวดเร็ว

หากคุณจำเป็นต้องดำเนินการร้องขอทางเว็บให้ทำในเธรดพื้นหลังจากกิจกรรมที่เพิ่งเปิดตัว (และแสดงกล่องโต้ตอบการโหลดในกิจกรรมใหม่) เมื่อเธรดคำขอพื้นหลังเสร็จสิ้นก็สามารถอัปเดตกิจกรรมและซ่อนกล่องโต้ตอบได้

ซึ่งหมายความว่ากิจกรรมใหม่ของคุณควรจะเริ่มทันทีและป้องกันไม่ให้ดับเบิลคลิกได้


3

หวังว่านี่จะช่วยได้:

 protected static final int DELAY_TIME = 100;

// to prevent double click issue, disable button after click and enable it after 100ms
protected Handler mClickHandler = new Handler() {

    public void handleMessage(Message msg) {

        findViewById(msg.what).setClickable(true);
        super.handleMessage(msg);
    }
};

@Override
public void onClick(View v) {
    int id = v.getId();
    v.setClickable(false);
    mClickHandler.sendEmptyMessageDelayed(id, DELAY_TIME);
    // startActivity()
}`

2

วิธีอื่น ๆ ที่ง่ายมากหากคุณไม่ต้องการใช้onActivityResult()คือปิดการใช้งานปุ่มเป็นเวลา 2 วินาที (หรือเวลาที่คุณต้องการ) ไม่เหมาะ แต่สามารถแก้ปัญหาได้บางส่วนเป็นบางกรณีและรหัสนั้นง่าย:

   final Button btn = ...
   btn.setOnClickListener(new OnClickListener() {
        public void onClick(View v) {
            //start activity here...
            btn.setEnabled(false);   //disable button

            //post a message to run in UI Thread after a delay in milliseconds
            btn.postDelayed(new Runnable() {
                public void run() {
                    btn.setEnabled(true);    //enable button again
                }
            },1000);    //1 second in this case...
        }
    });

2

// ตัวแปรเพื่อติดตามเวลาของเหตุการณ์

private long mLastClickTime = 0;

2. ใน onClick ตรวจสอบว่าหากเวลาปัจจุบันและเวลาในการคลิกสุดท้ายต่างกันน้อยกว่าฉันวินาทีก็ไม่ได้ทำอะไรเลย (กลับมา) ให้ไปที่เหตุการณ์คลิก

 @Override
public void onClick(View v) {
    // Preventing multiple clicks, using threshold of 1 second
    if (SystemClock.elapsedRealtime() - mLastClickTime < 1000) {
        return;
          }
    mLastClickTime = SystemClock.elapsedRealtime();
            // Handle button clicks
            if (v == R.id.imageView2) {
        // Do ur stuff.
         }
            else if (v == R.id.imageView2) {
        // Do ur stuff.
         }
      }
 }

1

เพียงแค่รักษาการตั้งค่าสถานะไว้ปุ่มเดียวในวิธีการคลิกเป็น:

บูลีนสาธารณะ oneTimeLoadActivity = false;

    myButton.setOnClickListener(new View.OnClickListener() {
          public void onClick(View view) {
               if(!oneTimeLoadActivity){
                    //start your new activity.
                   oneTimeLoadActivity = true;
                    }
        }
    });

1

เพิ่ม Launch Mode เป็นงานเดียวในรายการเพื่อหลีกเลี่ยงการเปิดกิจกรรมสองครั้งเมื่อคลิก

<activity
        android:name=".MainActivity"
        android:label="@string/activity"
        android:launchMode = "singleTask" />

0

หากคุณใช้ onActivityResult คุณสามารถใช้ตัวแปรเพื่อบันทึกสถานะ

private Boolean activityOpenInProgress = false;

myButton.setOnClickListener(new View.OnClickListener() {
  public void onClick(View view) {
    if( activityOpenInProgress )
      return;

    activityOpenInProgress = true;
   //Load another activity with startActivityForResult with required request code
  }
});

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  if( requestCode == thatYouSentToOpenActivity ){
    activityOpenInProgress = false;
  }
}

ใช้งานได้กับปุ่มย้อนกลับเช่นกันเนื่องจากมีการส่งคืนรหัสคำขอในเหตุการณ์



-1

ใช้flagชุดตัวแปรto trueตรวจสอบว่าเป็นจริงหรือไม่ในreturnการเรียกใช้กิจกรรม

คุณยังสามารถใช้ setClickable (false) เพื่อดำเนินการเรียกกิจกรรม

flg=false
 public void onClick(View view) { 
       if(flg==true)
         return;
       else
       { flg=true;
        // perform click}
    } 

perform click; wait; flg = false;เมื่อเรากลับมา
Xeno Lupus

-1

คุณสามารถแทนที่ startActivityForResult และใช้ตัวแปรอินสแตนซ์:

boolean couldStartActivity = false;

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

    couldStartActivity = true;
}

@Override
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
    if (couldStartActivity) {
        couldStartActivity = false;
        intent.putExtra(RequestCodeKey, requestCode);
        super.startActivityForResult(intent, requestCode, options);
    }
}

-4

คุณสามารถลองสิ่งนี้ได้เช่นกัน

Button game = (Button) findViewById(R.id.games);
        game.setOnClickListener(new View.OnClickListener() 
        {
            public void onClick(View view) 
            {
                Intent myIntent = new Intent(view.getContext(), Games.class);
                startActivityForResult(myIntent, 0);
            }

        });
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.