This section

1.JavaThread downloads data callback

2. Introduce coroutines

3. Launch and async

4. CoroutineScope and CoroutineContext

5.WithContext Switches threads

6. Repetitive OkHttp

7. Okhtttp obtains data

8. API description of aggregated data headlines

9. Use OkHttp3 to get data

10. Manually create the data model

11. Use plug-ins to automatically create models

12. Get data using Retrofit

JavaThread Download data callback
1.Thread blocks the main-UI Thread, and small tasks that take shorter time are put into the main Thread.
2. Sometimes the main thread has a long task that blocks other tasks on the main thread. To solve this problem, start a new thread, called a child thread.
3. The UI thread is provided for the user to interact with. Try not to let it block.
4. In the following code, for example, to implement the button click event, we print start and wait for a while before printing end
 button.setOnClickListener {
                Log.v("swl","start  ${Thread.currentThread()}")
                Thread.sleep(2000)
                Log.v("swl","end  ${Thread.currentThread()}")
            }

Copy the code
  • In this case, after clicking the button the first time, you can’t click the button the second time until the event has passed.

Block the main thread directly

5. To prevent it from blocking the main thread, we can create a new thread so that every time the button is clicked, it can start running without waiting.
button.setOnClickListener { Thread(object :Runnable{ override fun run() { Log.v("swl","start ${Thread.currentThread()}")  Thread.sleep(2000) Log.v("swl","end ${Thread.currentThread()}") } } ).start() }Copy the code
  • Since arguments inherit from an interface that has only one method in it, lambda expressions can be used.
button.setOnClickListener {
            Thread{
                Log.v("swl","start  ${Thread.currentThread()}")
                Thread.sleep(2000)
                Log.v("swl","end  ${Thread.currentThread()}")
            }.start()
        }

Copy the code

A new thread is started each time the button is clicked

  • Thread.sleep(2000) blocks the current Thread, and if it is written directly into the MainActivity, it blocks the main Thread and must wait for end to print before clicking the button again.
  • But if we create a new thread every time we click, we print start after the first click, because it blocks for 2s, so we don’t print end immediately. If I immediately click the button again, a new thread will be created and a start will be printed. Because I clicked the button four times in 2S, I printed four start, and then I printed end after 2S.
6. Disadvantages of Java threading:
  • Java has thread pools, and each thread pool can hold only a specified number of threads. If that number is exceeded, the one that exceeds that number is put into the wait sequence until there are no threads in the thread pool to execute.
  • Threads are very memory intensive, so you can’t open up lots of threads. When a thread reaches a certain point, a warning appears.
  • Data interaction between threads: ① pass data through Handler (callback) ② switch between threads (Rxjava).
7. Method of calling back data
  • Define a set of interfaces to implement data callbacks between two threads. Define an interface in the class that needs to pass data (the interface defines two methods) and a Listener of the interface type in this class. There is also a method in the class that needs to check whether there is a listener, and if there is a listener, do something about it. (1) In the class receiving data, first inherit the interface of the previous class, then implement the method in the class, and use that class as its listener. (2) The listener is separated from the two methods in the interface, and there is another method. Use the anonymous class directly and set the Listener equal to the anonymous class in which to implement the two methods in the interface. (The second option is recommended)
  • Classes that pass data:
class UtilNetWork { var listener: callBack? = null fun data(){Thread{log. v(" SWL "," start downloading. ${thread.currentThread ()}") thread.sleep (2000) log. v(" SWL "," download complete. ${Thread.currentThread()}") val result = "jack" } listener.let { } } interface callBack{ fun onSuccess(data:String) fun onFailure(error:String) } }Copy the code
  • The class that receives the data, in MainActivity
 button.setOnClickListener {
           val util = UtilNetWork()
            util.listener = object :UtilNetWork.callBack{
                override fun onSuccess(data: String) {

                }

                override fun onFailure(error: String) {

                }
            }
        }

Copy the code
8. Switch threads.
RunOnUIThread {// Do the required operation}Copy the code
Introduce Coroutine
1. The difference between threads and coroutines:
  • ① A task can create multiple threads, but the number of threads is limited. The more threads there are, the more memory they consume and the slower they are. ② For coroutines, there are only two threads, the main thread and the child thread. Countless coroutines can be created on the child thread, and the resource consumption is not large. Scheduling can be carried out on a thread, and thousands or hundreds of coroutines can be executed at the same time. Coroutines are blocked, threads are basically not blocked because there are many and coroutines on one thread.
  • Threads execute tasks sequentially. If there is a task running on the thread, subsequent tasks must wait for it to finish. But if a thread has a coroutine that takes a long time to execute, it suspends it, lets it execute on its own, and then executes the next coroutine on the thread. When the previous coroutine completes, it resumes where it was suspended.
2. Characteristics of coroutines:
  • Lightweight: You can run multiple coroutines on a single thread because coroutines support suspension and do not block the thread running them. Suspension saves memory than blocking and supports multiple parallel operations.
  • Fewer memory leaks: Multiple operations are performed within a scope using structured concurrency mechanisms.
  • Built-in cancel support: Cancel operations are automatically propagated throughout the running coroutine hierarchy.
  • Jetpack Integration: Many Jetpack libraries include extensions that provide full coroutine support. Some libraries also provide their own coroutine scope that you can use to structure concurrency.
3. Use coroutines
  • 1. Create a new project, add a library to it, and add the following dependencies to your application’s build.gradle file. If you are only using kotlin, import the following dependent libraries directly. But if it is used in android, so also need to import the implementation (” org. Jetbrains. Kotlinx: kotlinx coroutines – android: 1.3.9 “) the dependent libraries)
Implementation (" org. Jetbrains. Kotlinx: kotlinx coroutines -- core: 1.3.9 ")Copy the code
  • 2. Create another MyClass class in the library package, write a main () method outside the class, and create a thread inside it.
fun main(){
    println("main start ${Thread.currentThread()}")
    Thread{
        println("start ${Thread.currentThread()}")
        Thread.sleep(2000)
        println("end ${Thread.currentThread()}")
    }.start()

    println("main end ${Thread.currentThread()}")
}

Copy the code
The running results are as follows:

The results

The main thread will not block, so execute main End immediately after main start. If the new thread is started for a short time, the output order may also be main start-> start->main end->end
  • 3. Use coroutines. (Instead of using Thread, use delay because Thread blocks and delay does not block.)
fun main(){
    println("main start ${Thread.currentThread()}")
    GlobalScope.launch {
        load() }
    println("main end ${Thread.currentThread()}")
}
suspend fun load(){
    println("start ${Thread.currentThread()}")
    delay(2000)
    println("end ${Thread.currentThread()}")
}

Copy the code

Any coroutine has its own CoroutineScope, and numerous subcoroutines can be created within this coroutine domain. How to create a CoroutineScope :(generally use the first two)

  • Launch: Create a standalone CoroutineScope. Synchronization, no data is returned.
  • Async: asynchronous. Data needs to be returned.
  • RunBlocking: It creates a coroutine field on the current thread and this execution blocks the current thread.
  • GlobalScope: Creates a global CoroutineScope. This is not recommended. Lifecycle is scoped for the entire app. Disadvantages: Does not wait for GlobalScope’s coroutine to complete execution when the main thread ends. It creates a new thread.
Suspend: A suspend function can only be called from another suspend function or from a coroutineScope

GlobalScope run result

Calling the load method is delayed for another 2S because the main thread is executing too fast. So the main thread ends before the new thread starts.
  • 4. In order to solve the problem that child threads cannot be opened due to too fast speed, we can extend the execution time of the main thread and also delay, but delay is a suspension method, so we use sunBlocking.
fun main()= runBlocking{
    println("main start ${Thread.currentThread()}")
    GlobalScope.launch {
        load() }
     delay(3000)
    println("main end ${Thread.currentThread()}")
}
suspend fun load(){
    println("start ${Thread.currentThread()}")
    delay(2000)
    println("end ${Thread.currentThread()}")
}

Copy the code

RunBlocking running results

You can see that because the main thread has a delay that is longer than the child thread, the main thread does not end until the child thread completes.
Lunch and asyno
1. If you use runBlocking only, then the whole runBlocking is a coroutine field and the whole coroutine is blocked.
fun main()= runBlocking{
    println("main start ${Thread.currentThread()}")
    loadTask1()
    println("main end ${Thread.currentThread()}")
}
suspend fun loadTask1(){
    println("start1 ${Thread.currentThread()}")
    delay(1000)
    println("end1 ${Thread.currentThread()}")
}

Copy the code

The execution result

It’s obviously sequential, because they’re all in the same domain.
2. Create coroutines using Launch
fun main()= runBlocking{
    println("main start ${Thread.currentThread()}")
    launch {
        loadTask1()
    }
    println("main end ${Thread.currentThread()}")
}
suspend fun loadTask1(){
    println("start1 ${Thread.currentThread()}")
    delay(1000)
    println("end1 ${Thread.currentThread()}")
}

Copy the code

Launch Execution result

  • Launch does not block execution of the main thread if another launch is added
fun main()= runBlocking{
    println("main start ${Thread.currentThread()}")
    launch {
        loadTask1()
    }
    launch {
        loadTask2()
    }
    println("main end ${Thread.currentThread()}")
}
suspend fun loadTask1(){
    println("start1 ${Thread.currentThread()}")
    delay(1000)
    println("end1 ${Thread.currentThread()}")
}
suspend fun loadTask2(){
    println("start2 ${Thread.currentThread()}")
    delay(1000)
    println("end2 ${Thread.currentThread()}")
}

Copy the code

Two launch execution results

  • I’m still not blocking the main thread, but when I do 1, I get delay, so 1 is suspended, 2, 1 ends, 2 ends.
  • Use the measureTimeMillis method to calculate hang time
fun main()= runBlocking{
    println("main start ${Thread.currentThread()}")
   val time = measureTimeMillis {
       launch {
           loadTask1()
       }
       launch {
           loadTask2()
       }
   }
    println("time $time")
    println("main end ${Thread.currentThread()}")
}

Copy the code

The calculated result is 15ms

  • Why did this happen? Because we print only the allotted time, not the execution time. When we look at the launch source, we find a join method that suspends a coroutine until it ends.
fun main()= runBlocking{ println("main start ${Thread.currentThread()}") val time = measureTimeMillis { val job1 =launch  { loadTask1() } job1.join() val job2= launch { loadTask2() } job2.join() } println("time $time") println("main end ${Thread.currentThread()}") }Copy the code
  • Call the join method and find that end main is gone to the end.

The result of calling the Join method

  • So you can see here that the coroutine is executing synchronously, which means it doesn’t execute 2 until it executes 1, but it takes a little bit longer than 2s to start and close the thread.
3. Use async to create coroutines.
fun main()= runBlocking{
    println("main start ${Thread.currentThread()}")
   val time = measureTimeMillis {
       val job1 =async {
           loadTask1()
       }
      val job2= async {
           loadTask2()
       }
      job1.await()
      job2.await()
   }
    println("time $time")
    println("main end ${Thread.currentThread()}")
}

Copy the code

Asynchronous execution result

  • Async is performed asynchronously. Whoever finishes reading first executes, no order. Obviously asynchronous execution takes less time than synchronous execution.
CoroutineScope and CoroutineContext
1. When we execute the following code, we get the following result:
fun main(){
    runBlocking {
        println("1: ${Thread.currentThread()}")
        launch {
            println("2: ${Thread.currentThread()}")
        }
        println("3: ${Thread.currentThread()}")
    }
}

Copy the code

The execution result

Change launch to async and the result is the same. This means that even if you start the coroutine differently, but the thread is the same, it doesn’t start a new thread.
2.CoroutineScope: With launch and Async, you create a new scope
3.CoroutineContext: Use launch and Async in the same context as the parent scope
WithContext switches threads
1. What are the Dispatchers?
2. Thread switching mainly includes:
  • Dispatchers.Main – Use this scheduler to run coroutines on the Android Main thread. This scheduler can only be used to interact with interfaces and perform fast work. Examples include callingsuspendFunctions to run Android interface framework operations, as well as updatesLiveDataObject.
  • Dispatchers.IO – This scheduler has been optimized for performing disk or network I/O outside of the main thread. Examples include usingRoom component, reading data from or writing data to a file, and running any network operation.
  • Dispatchers.Default – This scheduler has been optimized for CPU intensive work outside of the main thread. Examples of use cases include sorting lists and parsing JSON.
2. Simulate the user login process. On the network, the user’s ID is read first, and then the user’s information is obtained. This is a way of switching threads from the IO thread to the main thread (which is automatically switched to the main thread after the IO thread finishes running).
data class User(val name:String) fun main(){ runBlocking { val result = async (Dispatchers.IO) { login() } val userInfo = async(Dispatchers.IO) { userInfo(result.await()) } println(userInfo.await().name) } } suspend fun login():Int{ Println (" start login") delay(1000) println("login succeeded ") return 1001} suspend fun userInfo(id:Int):User{println(" Obtain User information: $id") delay(1000) println($id") return User("jack")}Copy the code
The running results are as follows:

The results

3. If a task is to be executed on both the main thread and child threads, THEN I recommend specifying the IO thread when the main thread needs child threads, so that it will only jump once.
fun main(){
    runBlocking {
     launch (Dispatchers.Main){
          launch (Dispatchers.IO){

          }
      }
    }
}

Copy the code
  • Another option is to specify threads in advance within the function itself. This makes calling the function directly easier to understand.
fun main(){ runBlocking { val id= login() val user= userInfo(id) println("user: ${user.name}")}} suspend fun login():Int{return withContext(dispatchers.io){println(" start login") delay(1000) Println ("login successful ") 1001 // Default return value}} suspend fun userInfo(id:Int):User{return withContext(dispatchers.io){suspend fun userInfo(id:Int):User{return withContext(dispatchers.io){ Println ($id) delay(1000) println($id) delay(1000) User("jack")}}Copy the code

The results

4. Switch threads using withContext. This is still not as good as the one above.
 launch (Dispatchers.Main){
          withContext(Dispatchers.IO){
              login()
          }
          userInfo()
      }
    }

Copy the code
5. Compared to the equivalent implementation based on callbacks,withContext()There will be no additional overhead. In addition, in some cases, it can be optimizedwithContext()Call beyond the equivalent implementation based on callbacks. For example, if a function makes ten calls to a network, you can use externalwithContext()Tell Kotlin to switch threads only once. This way, even if the network library is used multiple timeswithContext()It also stays on the same scheduler and avoids thread switching. In addition, Kotlin has optimized itDispatchers.DefaultDispatchers.IOSwitch between threads to avoid thread switching as much as possible.
  • Important: Using a scheduler that uses thread pools (such as dispatchers. IO or dispatchers.default) does not guarantee that blocks are executed from top to bottom on the same thread. In some cases, the Kotlin coroutine may hand over execution to another thread after suspend and resume. This means that thread-local variables may not point to the same value for the entire withContext() block.
6. Using withContext will block the main thread. If you want to stop blocking the main thread, start a new thread and call login() and userInfo().
OkHttp
1. Import the dependency libraries into Gradle first
AndroidTestImplementation 'androidx. Test. Espresso: espresso - android: 3.3.0'Copy the code
2. Access the official website of OkHttpsquare.github.io/okhttp/Find the dependency library to import.
AndroidTestImplementation 'androidx. Test. Espresso: espresso - android: 3.3.0'Copy the code
OkHttp3 is a wrapper that contains:
  • OkHttpClient: provided to the user, the user uses this to create an OkHttp, or object.
  • Request: includes the address and other information requested.
  • Call interface: It defines some operations. What you’re really manipulating is a RealCall object.
  • Okio: Really do the core of transmission.
4. Application ->okhttp3->Caching or server. There is also an interceptor between Application and OkHttp3 (NetWorkInterpreter, which intercepts every operation on the network, that is, retrieving details)
  • OkHttpClient has a Call object pointing to RealCall and a Dispatchers, Caching
  • Request includes URL and method
5. See details for detailsSquare. Making. IO/okhttp/reci…
7. Okhttp gets data
1. There are two types of data:
  • XML: Hardly
  • JSON: Converts JSON data to the data type in Kotlin.
We need to use aggregated data here. We first apply for an account in the aggregated data in advance, and then select free items in the API, such as news headlines, and send requests according to its format

Request details

Enter the following url in the web page:V.j uhe. Cn/toutiao/ind…”, and then open a JSON parser that converts the data in the web page into code we can understand.

After the parsing

When you parse it and fold it up, you only have three elements, which is like three maps with keys and values.
A result:

When result expands

2. Layout activity_main. My layout adds a button and a progressBar:

Activity_main layout

3. Create a new project and import the dependency libraries we need.
/ / coroutine androidTestImplementation 'androidx. Test. Espresso: espresso - android: 3.3.0' androidTestImplementation 'androidx. Test. Espresso: espresso - core: 3.3.0' implementation (" org. Jetbrains. Kotlinx: kotlinx coroutines -- core: 1.3.9 ") / / okhttp implementation (" com. Squareup. Okhttp3: okhttp: 4.9.0 ") / / the ViewModel Implementation (" androidx. Lifecycle: lifecycle - viewmodel - KTX: 2.4.0 - alpha02 ") / / LiveData Implementation (" androidx. Lifecycle: lifecycle - livedata - KTX: 2.4.0 - alpha02 ") / / Lifecycles only (without the ViewModel or LiveData) implementation (" androidx. Lifecycle: lifecycle - runtime - KTX: 2.4.0 - alpha02 ")Copy the code
4. We are going to create a news presentation page based on the previous information, using MVVM mode, so we will create another class as ViewModel.
class NewsViewModel:ViewModel() { val news:MutableLiveData<String? > = MutableLiveData() init { news.value = null } fun loadNews(){ viewModelScope.launch { news.value = realLoad() } } suspend fun realLoad():String? { return withContext(Dispatchers.IO) { val client = OkHttpClient() val request = Request.Builder() .url("http://v.juhe.cn/toutiao/index?type=&key=4494d20d3e853ec01a1dafc8b901e716") .get() .build() val response = client.newCall(request).execute() if (response.isSuccessful) { delay(2000) response.body?.string() } else { null } } } }Copy the code
The MainActivity code is as follows:
class MainActivity : AppCompatActivity() { private lateinit var viewModel:NewsViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) ViewModelProvider(this,ViewModelProvider.NewInstanceFactory()) .get(NewsViewModel::class.java) viewModel.news.observe(this){value-> if(value! =null){ progressBar.visibility = View.GONE Log.v("swl","$value") } } button.setOnClickListener { progressBar.visibility = View.VISIBLE viewModel.loadNews() } } }Copy the code
5. After the command is successfully executed, the output is displayed.
8. API description of aggregated data headlines
1. Knowledge points to use for OkHttp3 and Retrofit request data
  • Aggregate data API usage
  • OkHttp3 requests data
  • Gson parses json data
  • Json To Kotlin plugin
  • Retrofit steps for requesting data
2. Access the official website of aggregated datawww.juhe.cn/Home page, choose life services, enter the news headlines section.

The headlines

3.V.j uhe. Cn/toutiao/ind…In this format, you can get the address of an interfaceV.j uhe. Cn/toutiao/ind…, type and key are shown in the picture below.

The type and the key

4. Input the web address we designed according to the requirements in the browser, and finally get a string of data, which will be parsed in the JSON parser. The parsing result is similar to the figure below:

Analytical results

Get data using OkHttp3
1. Go to Github, search okhttp, and click on the first one for details.Github.com/square/okht…
2. Add a dependency library.
Implementation (" com. Squareup. Okhttp3: okhttp: 4.9.1 ")Copy the code
3. Let’s create a new project and test it out by importing the dependency library and adding the following code to the manifest. The one in the back is inside.
<uses-permission android:name="android.permission.INTERNET"/>
 android:usesCleartextTraffic="true"

Copy the code
4. Write some code in MainActivity.
class MainActivity : AppCompatActivity() { private val xinwen_url = "http://v.juhe.cn/toutiao/index?type=guonei&key=4494d20d3e853ec01a1dafc8b901e716" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } override fun onTouchEvent(event: MotionEvent?): Boolean { if(event?.action == MotionEvent.ACTION_DOWN){ val httpClient = OkHttpClient() val request = Request.Builder() .url(xinwen_url) .build() httpClient.newCall(request).enqueue(object:Callback{ override fun onFailure(call: Call, e: IOException) { e.printStackTrace() } override fun onResponse(call: Call, response: Response) {if(response.issuccessful){val BodyStr = response.body?.string() log.v (" SWL "," $BodyStr") } } }) } return super.onTouchEvent(event) } }Copy the code
The final result is shown below, which means we resolved.

The results

Create the data model manually
1. According to the previous method, the data has been obtained, but it cannot be directly added into our project, so we need to build a data model first.
2. Open Github, search for Gson, and click the first one. Enter the following netgithub.com/google/gson
3. Import the dependency library according to the url above.
Implementation 'com. Google. Code. Gson: gson: 2.8.7'Copy the code
4. Create a data class, NewsModel, that contains data similar to the data parsed in the JSON parser.
data class NewsModel(
    val reason:String,
    val result:Result,
    val error_code:Int
)

Copy the code
data class Result (val data:List<New>, )

Copy the code
data class New(val title:String)

Copy the code
Use plug-ins to automatically create models
1. Before we manually create the model, in fact, it is very troublesome, for the structure of clear data no problem, but for the structure of complex data is very troublesome, easy to make mistakes. This is where you can use the plug-in.
2. Open Settings in Android Studio, hit plungs, search for JSON and download the first one.

Download the plugin

3. Then create a kotlin data class file from JSON and add theV.j uhe. Cn/toutiao/ind…Copy all the content of this website, nota single one can be missed, Annotation remember to check Gson, everything else is the same.

The creation process

4. The auto-created code is shown below, and I have deleted the unnecessary ones.
data class NewsModel(
    @SerializedName("result")
    val result: Result
)

Copy the code
data class Result(
    @SerializedName("data")
    val data: List<Data>,
)

Copy the code
data class Data(
    @SerializedName("author_name")
    val authorName: String,
    @SerializedName("category")
    val category: String,
    @SerializedName("date")
    val date: String,
    @SerializedName("is_content")
    val isContent: String,
    @SerializedName("thumbnail_pic_s")
    val thumbnailPicS: String,
    @SerializedName("title")
    val title: String,
    @SerializedName("uniquekey")
    val uniquekey: String,
    @SerializedName("url")
    val url: String
)

Copy the code
5. Inside MainActivity, parse after the previous code.
if(response.isSuccessful){ val bodyStr = response.body? .string() val gson = Gson() val model = gson.fromJson<NewsModel>(bodyStr,NewsModel::class.java) model.result.data.forEach { Log.v("swl",it.title) } }Copy the code
6. The running results are as follows:

Parsing the success

Get data using RetroFIT
1. Retrofit is simpler and cleaner than OkHTTP, so it’s better to use it. Go to thesquare.github.io/retrofit/Website to see how it is used.
2. Import the dependency libraries.
Implementation 'com. Squareup. Retrofit2: retrofit: 2.9.0' implementation 'com. Squareup. Retrofit2: converter - gson: 2.9.0' Implementation (" org. Jetbrains. Kotlinx: kotlinx coroutines - android: 1.3.9 ") Implementation (" androidx. Lifecycle: lifecycle - runtime - KTX: 2.4.0 - alpha02 ")Copy the code
3. Create an interface as an API.
interface NewsAPI { @GET("index? type=guonei&key=4494d20d3e853ec01a1dafc8b901e716") suspend fun getNews():NewsModel }Copy the code
4. Write a useRetrofit() method in MainActivity and useRetrofit to retrieve data. Call this method inside onTouchEvent.
fun useRetrofit(){
        val retrofit = Retrofit.Builder()
                .baseUrl("http://v.juhe.cn/toutiao/")
                .addConverterFactory(GsonConverterFactory.create())
                .build()
        val api =  retrofit.create(NewsAPI::class.java)
           lifecycleScope.launch {
           val news = api.getNews()
           news.result.data.forEach{
               Log.v("swl",it.title)
           }
       }
    }

Copy the code

The results

  • Because the news is refreshed every 3 minutes, the headlines are different.