Jak wywołać metodę po opóźnieniu w Androidzie


769

Chcę móc wywołać następującą metodę po określonym opóźnieniu. W celu c było coś takiego:

[self performSelector:@selector(DoSomething) withObject:nil afterDelay:5];

Czy istnieje odpowiednik tej metody w Androidzie z Javą? Na przykład muszę być w stanie wywołać metodę po 5 sekundach.

public void DoSomething()
{
     //do something here
}

Odpowiedzi:


1858

Kotlin

Handler().postDelayed({
  //Do something after 100ms
}, 100)


Jawa

final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
  @Override
  public void run() {
    //Do something after 100ms
  }
}, 100);



108
To rozwiązanie jest przydatne tylko w wątku interfejsu użytkownika. W przeciwnym razie w normalnym wątku musisz zaimplementować looper, który moim zdaniem nie jest najlepszą wersją
olivier_sdg

2
@olivier_sdg dlaczego musisz wdrożyć looper?
djechlin

37
@djechlin Handler musi zawsze być powiązany z Looper, który faktycznie przetworzy Runnable post (). Wątek interfejsu użytkownika jest już wyposażony w Looper, dzięki czemu można po prostu utworzyć nowy moduł obsługi () w wątku interfejsu użytkownika i bezpośrednio do niego napisać () Runnables. Te Runnable wykonują się w wątku interfejsu użytkownika. Aby Runnable działały w innym wątku, musisz utworzyć nowy wątek, a następnie Looper.prepare (), utworzyć nowy moduł obsługi (), a następnie Looper.loop (). Wszelkie Runnables wysłane do tego nowego modułu obsługi zostaną wykonane w tym nowym wątku. Jeśli nie zrobisz tego wszystkiego, post () zgłosi wyjątek.
Dororo,

12
W przypadku, gdy zachodzi taka potrzeba, można również anulować wykonanie dopóki Runnable jest nadal w kolejce komunikatów dzwoniąc removeCallbacks(Runnable r)na Handler.
Dennis

9
powinienimport android.os.handler
KaKa

322

Nie mogłem użyć żadnej innej odpowiedzi w moim przypadku. Zamiast tego użyłem natywnego timera Java.

new Timer().schedule(new TimerTask() {          
    @Override
    public void run() {
        // this code will be executed after 2 seconds       
    }
}, 2000);

43
jest to lepsze niż te, które używają modułu obsługi, ponieważ nie ma problemów z Looper, gdy moduł obsługi nie jest uruchamiany w wątku interfejsu użytkownika.
Ben H

32
Powinieneś zachować odniesienie do swojego timera, aby go anulować, gdy nie jest już potrzebny, ponieważ zgodnie z dokumentem Androida: „Gdy timer nie jest już potrzebny, użytkownicy powinni wywołać cancel (), co zwalnia wątek timera i inne zasoby. Czasomierze, które nie zostały wyraźnie anulowane, mogą przechowywać zasoby w nieskończoność. ”
Pooks,

14
Uwaga! To nie działa w wątku interfejsu użytkownika. Uruchomienie tego w wątku interfejsu użytkownika spowodowało błąd krytyczny: android.view.ViewRootImpl $ CalledFromWrongThreadException: Tylko oryginalny wątek, który utworzył hierarchię widoków, może dotknąć jego widoków.
vovahost

13
@vovahost to tylko dlatego, że aktualizujesz komponenty interfejsu użytkownika w bloku timera
Tim

10
Zauważ, że java.util.Timer (i TimerTask) będą przestarzałe w JDK 9. TimerTask tworzy nowe wątki dla zadań, które nie są zbyt dobre.
Varvara Kalinina

183

Uwaga: Ta odpowiedź została udzielona, ​​gdy w pytaniu nie określono Androida jako kontekstu. Aby uzyskać odpowiedź dotyczącą wątku interfejsu użytkownika Androida, zobacz tutaj.


Wygląda na to, że interfejs Mac OS API pozwala na kontynuację bieżącego wątku i planuje asynchroniczne uruchomienie zadania. W Javie równoważną funkcję zapewnia java.util.concurrentpakiet. Nie jestem pewien, jakie ograniczenia może nałożyć Android.

private static final ScheduledExecutorService worker = 
  Executors.newSingleThreadScheduledExecutor();

void someMethod() {
  
  Runnable task = new Runnable() {
    public void run() {
      /* Do something… */
    }
  };
  worker.schedule(task, 5, TimeUnit.SECONDS);
  
}

3
To nigdy nie nazywa mnie Runnable
Supuhstar

14
Na marginesie: Pozwala to również anulować zadanie później, co może być pomocne w niektórych sytuacjach. Wystarczy zapisać odniesienie do ScheduledFuture<?>zwróconego przez worker.schedule()i wywołać jego cancel(boolean)metodę.
Dennis

Myślę, że ta odpowiedź jest nieaktualna. .schedule nie wydaje się już być metodą Runnable ...? : /
beetree

5
@beetree to metoda na ScheduledExecutorService.
erickson

3
To nie działa, jeśli zaangażowane są obiekty wątku interfejsu użytkownika, musisz wywołać runOnUIThread (new runnable () {run () ....}); lub opublikuj obiekt uruchamialny przy użyciu obiektu modułu obsługi z poziomu run () {}
Jayant Arora

107

Aby wykonać coś w wątku interfejsu użytkownika po 5 sekundach:

new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
    @Override
    public void run() {
        //Do something here
    }
}, 5000);

8
Potwierdź, że jest to najlepsze rozwiązanie, aby zapobiec wywołaniu looper.prepare i umieścić całość w wątku interfejsu użytkownika.
Tobliug

Dzięki za to pomogłem mi z problemami z Looper :)
Tia

1
Byłbym ostrożny przy tworzeniu
modułu

40

możesz użyć Handler wewnątrz UIThread:

runOnUiThread(new Runnable() {

    @Override
    public void run() {
         final Handler handler = new Handler();
         handler.postDelayed(new Runnable() {
           @Override
           public void run() {
               //add your code here
           }
         }, 1000);

    }
});

36

Dzięki za wszystkie wspaniałe odpowiedzi znalazłem rozwiązanie, które najlepiej odpowiada moim potrzebom.

Handler myHandler = new DoSomething();
Message m = new Message();
m.obj = c;//passing a parameter here
myHandler.sendMessageDelayed(m, 1000);

class DoSomething extends Handler {
    @Override
    public void handleMessage(Message msg) {
      MyObject o = (MyObject) msg.obj;
      //do something here
    }
}

Czy jest w porządku, jeśli zastosuję to podejście, aby uzyskać informacje dotykowe po kliknięciu elementu .. view.setColor (some_color), a następnie usuń ten kolor w module obsługi po x sekundach ...?
eRaisedToX

24

KotlinI Javawiele sposobów

1. Korzystanie Handler

Handler().postDelayed({
    TODO("Do something")
    }, 2000)

2. Korzystanie z TimerTask

Timer().schedule(object : TimerTask() {
    override fun run() {
        TODO("Do something")
    }
}, 2000)

Lub nawet krócej

Timer().schedule(timerTask {
    TODO("Do something")
}, 2000)

Lub najkrótszy byłby

Timer().schedule(2000) {
    TODO("Do something")
}

3. Korzystanie Executors

Executors.newSingleThreadScheduledExecutor().schedule({
    TODO("Do something")
}, 2, TimeUnit.SECONDS)

W Javie

1. Korzystanie Handler

new Handler().postDelayed(new Runnable() {
    @Override
    public void run() {
        //Do something
    }
}, 2000);

2. Korzystanie Timer

new Timer().schedule(new TimerTask() {          
    @Override
    public void run() {
        // Do something
    }
}, 2000);

3. Korzystanie ScheduledExecutorService

private static final ScheduledExecutorService worker = Executors.newSingleThreadScheduledExecutor();

Runnable runnable = new Runnable() {
  public void run() {
      // Do something
  }
  };
worker.schedule(runnable, 2, TimeUnit.SECONDS);

1
@JanRabe Dziękujemy za sugestię. Doceniam to. Pytanie jednak brzmi How to call a method after a delay in Android. Skupiłem się na tym. Do momentu. W przeciwnym razie wycieki Java to duży temat, który należy osobno zrozumieć dla programistów.
Khemraj

20

Zobacz to demo:

import java.util.Timer;
import java.util.TimerTask;

class Test {
     public static void main( String [] args ) {
          int delay = 5000;// in ms 

          Timer timer = new Timer();

          timer.schedule( new TimerTask(){
             public void run() { 
                 System.out.println("Wait, what..:");
              }
           }, delay);

           System.out.println("Would it run?");
     }
}

20

Jeśli musisz użyć modułu obsługi, ale przechodzisz do innego wątku, możesz użyć go runonuithreaddo uruchomienia modułu obsługi w wątku interfejsu użytkownika. To uchroni Cię przed wyjątkami zgłoszonymi z prośbą o połączenieLooper.Prepare()

runOnUiThread(new Runnable() {
    @Override
    public void run() {
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                //Do something after 1 second
            }
        }, 1000);
    }
});

Wygląda dość niechlujnie, ale jest to jeden ze sposobów.


4
To działa, nie mogę edytować twojego postu z powodu głupich reguł SO z minimum 6 znakami do edycji, ale brakuje „()” po „new Handler”, powinien to być „new Handler ()”
Jonathan Muller

2
Zamiast umieszczać wszystko w wątku interfejsu, możesz zrobić: nowy moduł obsługi (Looper.getMainLooper ())
Tobliug

17

Wolę używać View.postDelayed()metody, prostego kodu poniżej:

mView.postDelayed(new Runnable() {
    @Override
    public void run() {
        // Do something after 1000 ms
    }
}, 1000);

1
Czy nie zawiesza samego elementu interfejsu użytkownika, ponieważ zostanie on zaplanowany w module obsługi widoków?
JacksOnF1re

1
Nie, wysłane zadanie zostanie wykonane za 1 sekundę, ale podczas tego drugiego wątku interfejsu użytkownika wykonuje inną przydatną pracę
demaksee

14

Oto moje najkrótsze rozwiązanie:

new Handler().postDelayed(new Runnable() {
    @Override
    public void run() {
        //Do something after 100ms
    }
}, 100);

10
final Handler handler = new Handler(); 
Timer t = new Timer(); 
t.schedule(new TimerTask() { 
    public void run() { 
        handler.post(new Runnable() { 
            public void run() { 
                //DO SOME ACTIONS HERE , THIS ACTIONS WILL WILL EXECUTE AFTER 5 SECONDS...
            }
        }); 
    } 
}, 5000); 

10

Jeśli używasz Androida Studio 3.0 i nowszych wersji, możesz używać wyrażeń lambda. Metoda callMyMethod()jest wywoływana po 2 sekundach:

new Handler().postDelayed(() -> callMyMethod(), 2000);

Jeśli chcesz anulować opóźnione uruchomienie, użyj tego:

Handler handler = new Handler();
handler.postDelayed(() -> callMyMethod(), 2000);

// When you need to cancel all your posted runnables just use:
handler.removeCallbacksAndMessages(null);

Jak możemy to anulować?
Damia Fuentes,

Dziwię się, ilu tutaj chętnie przeniesie się na Kotlin, ale całkowicie zignoruje wyrażenia Lambda, które są standardową Javą.
TomDK

6

Sugeruję Timer , pozwala zaplanować metodę, która będzie wywoływana w bardzo określonym przedziale czasowym. Nie zablokuje to interfejsu użytkownika i utrzyma działanie aplikacji podczas wykonywania metody.

Inną opcją jest wait (); spowoduje to zablokowanie bieżącego wątku na określony czas. Spowoduje to, że interfejs użytkownika przestanie odpowiadać, jeśli zrobisz to w wątku interfejsu użytkownika.


2
Thread.sleep () jest lepszy niż Object.wait (). Oczekiwanie oznacza, że ​​oczekujesz powiadomienia i synchronizujesz niektóre działania. Sen oznacza, że ​​po prostu nie chcesz nic robić przez określony czas. Timer jest właściwym rozwiązaniem, jeśli chcesz, aby akcja miała miejsce asynchronicznie w późniejszym czasie.
Tim Bender

1
To prawda. Dlatego wymieniłem to jako kolejną opcję ;-)
Nate

6

Aby opóźnić obsługę postu za pomocą prostej linii, możesz wykonać następujące czynności:

new Handler().postDelayed(new Runnable() {
    @Override
    public void run() {
        // Do someting
    }
}, 3000);

mam nadzieję, że to pomoże


5

Możesz użyć tego dla najprostszego rozwiązania:

new Handler().postDelayed(new Runnable() {
    @Override
    public void run() {
        //Write your code here
    }
}, 5000); //Timer is in ms here.

W przeciwnym razie poniżej może znajdować się kolejne czyste użyteczne rozwiązanie:

new Handler().postDelayed(() -> 
{/*Do something here*/}, 
5000); //time in ms

5

Możesz uczynić go znacznie czystszym, używając nowo wprowadzonych wyrażeń lambda:

new Handler().postDelayed(() -> {/*your code here*/}, time);

5

Jest zatem kilka rzeczy do rozważenia, ponieważ istnieje tak wiele sposobów na skórowanie tego kota. Chociaż wszystkie odpowiedzi zostały już wybrane i wybrane. Myślę, że ważne jest, aby ponownie zapoznać się z odpowiednimi wytycznymi dotyczącymi kodowania, aby uniknąć sytuacji, w której ktoś pójdzie w niewłaściwym kierunku tylko z powodu „większości wybranych prostych odpowiedzi”.

Więc najpierw omówmy prostą odpowiedź Post Delayed, która jest ogólnie wybraną odpowiedzią w tym wątku.

Kilka rzeczy do rozważenia. Po opóźnieniu postu możesz napotkać wycieki pamięci, martwe przedmioty, cykle życia, które zniknęły i wiele więcej. Ważne jest więc również odpowiednie obchodzenie się z nim. Możesz to zrobić na kilka sposobów.

Ze względu na nowoczesny rozwój dostarczę w KOTLIN

Oto prosty przykład użycia wątku interfejsu użytkownika na wywołaniu zwrotnym i potwierdzeniu, że Twoja aktywność nadal działa i ma się dobrze po uderzeniu w wywołanie zwrotne.

  Handler(Looper.getMainLooper()).postDelayed({
            if(activity != null && activity?.isFinishing == false){
                txtNewInfo.visibility = View.GONE
            }
        }, NEW_INFO_SHOW_TIMEOUT_MS)

Jednak nadal nie jest to idealne, ponieważ nie ma powodu, aby oddzwonić, jeśli aktywność zniknęła. więc lepszym sposobem byłoby zachować odniesienie do niego i usunąć jego wywołania zwrotne w ten sposób.

    private fun showFacebookStylePlus1NewsFeedOnPushReceived(){
        A35Log.v(TAG, "showFacebookStylePlus1NewsFeedOnPushReceived")
        if(activity != null && activity?.isFinishing == false){
            txtNewInfo.visibility = View.VISIBLE
            mHandler.postDelayed({
                if(activity != null && activity?.isFinishing == false){
                    txtNewInfo.visibility = View.GONE
                }
            }, NEW_INFO_SHOW_TIMEOUT_MS)
        }
    }

i oczywiście zajmuj się czyszczeniem w onPause, aby nie oddzwoniło.

    override fun onPause() {
        super.onPause()
        mHandler.removeCallbacks(null)
    }

Teraz, kiedy omawialiśmy to, co oczywiste, porozmawiajmy o czystszej opcji z nowoczesnymi coroutines i kotlin :). Jeśli jeszcze ich nie używasz, naprawdę brakuje.

   fun doActionAfterDelay() 
        launch(UI) {
            delay(MS_TO_DELAY)           
            actionToTake()
        }
    }

lub jeśli chcesz zawsze uruchamiać interfejs użytkownika dla tej metody, możesz po prostu zrobić:

  fun doActionAfterDelay() = launch(UI){ 
      delay(MS_TO_DELAY)           
      actionToTake()
  }

Oczywiście, podobnie jak PostDelayed, musisz upewnić się, że poradzisz sobie z anulowaniem, więc możesz albo sprawdzić aktywność po opóźnionym połączeniu, albo możesz anulować go w onPause, tak jak na innej trasie.

var mDelayedJob: Job? = null
fun doActionAfterDelay() 
   mDelayedJob = launch(UI) {
            try {
               delay(MS_TO_DELAY)           
               actionToTake()
            }catch(ex: JobCancellationException){
                showFancyToast("Delayed Job canceled", true, FancyToast.ERROR, "Delayed Job canceled: ${ex.message}")
            }
        }
   }
}

// obsługa czyszczenia

override fun onPause() {
   super.onPause()
   if(mDelayedJob != null && mDelayedJob!!.isActive) {
      A35Log.v(mClassTag, "canceling delayed job")
      mDelayedJob?.cancel() //this should throw CancelationException in coroutine, you can catch and handle appropriately
   }
}

Jeśli umieścisz uruchomienie (UI) w podpisie metody, zadanie może zostać przypisane w linii wywołującej kodu.

więc morał tej historii jest bezpieczny przy opóźnionych działaniach, upewnij się, że usunąłeś oddzwanianie lub anulowałeś swoje zadania i oczywiście potwierdziłeś, że masz odpowiedni cykl życia, aby dotknąć elementów opóźnionego oddzwaniania. Coroutines oferuje także akcje, które można anulować.

Warto również zauważyć, że zwykle powinieneś sobie poradzić z różnymi wyjątkami, które mogą pochodzić z coroutines. Na przykład anulowanie, wyjątek, limit czasu, cokolwiek zdecydujesz się użyć. Oto bardziej zaawansowany przykład, jeśli zdecydujesz się naprawdę zacząć używać coroutines.

   mLoadJob = launch(UI){
            try {
                //Applies timeout
                withTimeout(4000) {
                    //Moves to background thread
                    withContext(DefaultDispatcher) {
                        mDeviceModelList.addArrayList(SSDBHelper.getAllDevices())
                    }
                }

                //Continues after async with context above
                showFancyToast("Loading complete", true, FancyToast.SUCCESS)
            }catch(ex: JobCancellationException){
                showFancyToast("Save canceled", true, FancyToast.ERROR, "Save canceled: ${ex.message}")
            }catch (ex: TimeoutCancellationException) {
                showFancyToast("Timed out saving, please try again or press back", true, FancyToast.ERROR, "Timed out saving to database: ${ex.message}")
            }catch(ex: Exception){
                showFancyToast("Error saving to database, please try again or press back", true, FancyToast.ERROR, "Error saving to database: ${ex.message}")
            }
        }

1
Nie ma problemu, Rajiv, pójdę o krok dalej i wspomnę, że korzystając z Live Data, korporacje mogą być świadome cyklu życia i samoczynnie się anulować, aby uniknąć wezwań do czyszczenia, ale nie chcą rzucać zbyt wielu krzywych uczenia się w jedną odpowiedź;)
Sam

3

Stworzyłem prostszą metodę nazywania tego.

public static void CallWithDelay(long miliseconds, final Activity activity, final String methodName)
    {
        new Handler().postDelayed(new Runnable() {

            @Override
            public void run() {
                try {
                    Method method =  activity.getClass().getMethod(methodName);
                    method.invoke(activity);
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }, miliseconds);
    }

Aby z niego skorzystać, wystarczy zadzwonić: .CallWithDelay(5000, this, "DoSomething");


3
Refleksja na temat tak podstawowego zadania?
Max Ch

Ponieważ pytanie wywołać metodę podobną do iOS performSelector. to najlepszy sposób na zrobienie tego.
HelmiB

3

Poniżej jeden działa, gdy otrzymasz,

java.lang.RuntimeException: Nie można utworzyć modułu obsługi w wątku, który nie wywołał Looper.prepare ()

final Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(new Runnable() {
  @Override
  public void run() {
    //Do something after 100ms
  }
}, 100);

3

Korzystając z Kotlin, możemy osiągnąć, wykonując następujące czynności

Handler().postDelayed({
    // do something after 1000ms 
}, 1000)

2

Jest bardzo łatwy w użyciu CountDownTimer. Aby uzyskać więcej informacji https://developer.android.com/reference/android/os/CountDownTimer.html

import android.os.CountDownTimer;

// calls onTick every second, finishes after 3 seconds
new CountDownTimer(3000, 1000) { 

   public void onTick(long millisUntilFinished) {
      Log.d("log", millisUntilFinished / 1000);
   }

   public void onFinish() {
      // called after count down is finished
   } 
}.start();

2

Jeśli używasz RxAndroid, obsługa wątków i błędów staje się znacznie łatwiejsza. Poniższy kod wykonuje się z opóźnieniem

   Observable.timer(delay, TimeUnit.SECONDS)
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(aLong -> {
           // Execute code here
        }, Throwable::printStackTrace);

1

wydaje się, że wszyscy zapominają wyczyścić moduł obsługi przed opublikowaniem nowego pliku uruchamialnego lub wiadomości na nim. W przeciwnym razie mogłyby się kumulować i powodować złe zachowanie.

handler.removeMessages(int what);
// Remove any pending posts of messages with code 'what' that are in the message queue.

handler.removeCallbacks(Runnable r)
// Remove any pending posts of Runnable r that are in the message queue.

1

Oto kolejny podchwytliwy sposób: nie wyrzuci wyjątku, gdy wykonalne zmiany elementów interfejsu użytkownika.

public class SimpleDelayAnimation extends Animation implements Animation.AnimationListener {

    Runnable callBack;

    public SimpleDelayAnimation(Runnable runnable, int delayTimeMilli) {
        setDuration(delayTimeMilli);
        callBack = runnable;
        setAnimationListener(this);
    }

    @Override
    public void onAnimationStart(Animation animation) {

    }

    @Override
    public void onAnimationEnd(Animation animation) {
        callBack.run();
    }

    @Override
    public void onAnimationRepeat(Animation animation) {

    }
}

Możesz wywołać animację w następujący sposób:

view.startAnimation(new SimpleDelayAnimation(delayRunnable, 500));

Animacja może zostać dołączona do dowolnego widoku.



1

Lubię rzeczy czystsze: oto moja implementacja, wbudowany kod do użycia w twojej metodzie

new Handler().postDelayed(new Runnable() {
  @Override
  public void run() {
    //Do something after 100ms
  }
}, 100);

0

Odpowiednie rozwiązanie w Androidzie:

private static long SLEEP_TIME = 2 // for 2 second
.
.
MyLauncher launcher = new MyLauncher();
            launcher.start();
.
.
private class MyLauncher extends Thread {
        @Override
        /**
         * Sleep for 2 seconds as you can also change SLEEP_TIME 2 to any. 
         */
        public void run() {
            try {
                // Sleeping
                Thread.sleep(SLEEP_TIME * 1000);
            } catch (Exception e) {
                Log.e(TAG, e.getMessage());
            }
            //do something you want to do
           //And your code will be executed after 2 second
        }
    }

0

Podobne rozwiązanie, ale o wiele czystsze w użyciu

Napisz tę funkcję poza klasą

fun delay(duration: Long, `do`: () -> Unit) {

    Handler().postDelayed(`do`, duration)

}

Stosowanie:

delay(5000) {
    //Do your work here
}

Co robi „zrobić”?
Skizo-ozᴉʞS

tylko imię, trzymaj wszystko tam. dojest wbudowaną metodą, dlatego musimy użyć `, aby użyć go jako nazwy zmiennej
Manohar Reddy

Dzięki, ale dlaczego używana jest ta nazwa zmiennej? Mam na myśli, jaka jest jego funkcja.
Skizo-ozᴉʞS

1
Tak myślałem dopo 3 sekundach
Manohar Reddy

0

W Androidzie możemy napisać poniżej kodu kotlin, aby opóźnić wykonanie dowolnej funkcji

class MainActivity : AppCompatActivity() {

private lateinit var handler: Handler

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    handler= Handler()
    handler.postDelayed({
        doSomething()
    },2000)
}

private fun doSomething() {
    Toast.makeText(this,"Hi! I am Toast Message",Toast.LENGTH_SHORT).show()
}
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.