android基础03 - 多媒体、多线程与异步任务、Service、网络

多媒体

通知

通知渠道:程序对自己发出的通知进行分类,用户可根据渠道对消息进行屏蔽或设置响铃振动。
一个应用的通知渠道一旦创建就无法再修改,只能再创建新的
可在 Activity、BroadcastReceiver、Service 中使用,大多数场景是在后台时使用。
通知相关的API经常变,可以使用 AndroidX 库中的封装:NotificationCompat

点击效果要用 PendingIntent ,Intent表示立即执行某个动作,PendingIntent 表示延迟执行动作。
PendingIntent 对象提供几个静态方法:getActivity getBroadcast getService,这几个方法参数相同:

  1. Context
  2. 传入 0 即可
  3. Intent 对象
  4. 传入 0 即可。行为,有4种参数:FLAG_ONE_SHOT、FLAG_NO_CREATE、FLAG_CANCEL_CURRENT、FLAG_UPDATE_CURRENT
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 创建通知渠道
        // Context提供的方法
        val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            // 指定重要等级参数的初始值: IMPORTANCE_HIGH、IMPORTANCE_DEFAULT、IMPORTANCE_LOW、IMPORTANCE_MIN。用户可手动更改
            val channel = NotificationChannel("normal", "Normal",NotificationManager.IMPORTANCE_DEFAULT)
            manager.createNotificationChannel(channel)
        }
        // 通知对象
        sendNotice.setOnClickListener {
            val intent = Intent(this, NotificationActivity::class.java)
            val pi = PendingIntent.getActivity(this, 0, intent, 0)

            val notification = NotificationCompat.Builder(this, "normal")  // 这个对象位于 AndroidX 库中
                .setContentTitle("This is content title")
                .setContentText("This is content text")
                .setSmallIcon(R.drawable.small_icon)
                .setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.large_icon))
                .setContentIntent(pi) // 指定点击行为
                /* 也可在动作执行完成后获得 NotificationManager 调用 manager.cancel(notificationId) 取消*/
                .setAutoCancel(true)  // 点击后消失
                .build()
            // 发送通知对象,第一个参数为id,要保证每个通知都不同
            manager.notify(1, notification)
        }
    }
}

NotificationCompat 的一些其他API,如 setStyle 

摄像头

class MainActivity : AppCompatActivity() {
    val takePhoto = 1
    lateinit var imageUri: Uri
    lateinit var outputImage: File
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        takePhotoBtn.setOnClickListener {
            // 创建File对象,用于存储拍照后的图片
            // externalCacheDir 路径为调用Context方法获得的关联缓存位置: /sdcard/Android/data/<package_name>/cache
            outputImage = File(externalCacheDir, "output_image.jpg")
            if (outputImage.exists()) {
                outputImage.delete()
            }
            outputImage.createNewFile()
            // 安卓7.0后为了安全无法使用本地真实路径Uri,要使用 FileProvider
            imageUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                FileProvider.getUriForFile(this, "com.example.cameraalbumtest.fileprovider", outputImage)
            } else {
                Uri.fromFile(outputImage)
            }
            /********************  启动相机程序  ******************/
            val intent = Intent("android.media.action.IMAGE_CAPTURE")
            intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri)
            startActivityForResult(intent, takePhoto)
        }
    }
    // 拍照动作结束后返回此处
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        when (requestCode) {
            takePhoto -> {
                if (resultCode == Activity.RESULT_OK) {
                    // 将拍摄的照片显示出来
                    val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(imageUri))
                    imageView.setImageBitmap(rotateIfRequired(bitmap))
                }
            }
        }
    }
    // 手机认为打开摄像头拍照时就应该横屏,所有有时需要旋转图片
    private fun rotateIfRequired(bitmap: Bitmap): Bitmap {
        val exif = ExifInterface(outputImage.path)
        val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
        return when (orientation) {
            ExifInterface.ORIENTATION_ROTATE_90 -> rotateBitmap(bitmap, 90)
            ExifInterface.ORIENTATION_ROTATE_180 -> rotateBitmap(bitmap, 180)
            ExifInterface.ORIENTATION_ROTATE_270 -> rotateBitmap(bitmap, 270)
            else -> bitmap
        }
    }
    private fun rotateBitmap(bitmap: Bitmap, degree: Int): Bitmap {
        val matrix = Matrix()
        matrix.postRotate(degree.toFloat())
        val rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
        bitmap.recycle() // 将不再需要的Bitmap对象回收
        return rotatedBitmap
    }
}

在 AndroidManifest.xml 中配置 ContentProvider:

<provider
          android:name="androidx.core.content.FileProvider"              // 固定
          android:authorities="com.example.cameraalbumtest.fileprovider" // FileProvider.getUriForFile()方法中的第二个参数一致
          android:exported="false"
          android:grantUriPermissions="true">
    <meta-data
               android:name="android.support.FILE_PROVIDER_PATHS"
               android:resource="@xml/file_paths" />  // 指定Uri的共享路径
</provider>

创建 res/xml/file_paths.xml 用于引用:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="my_images" path="/" />
</paths>

相册

class MainActivity : AppCompatActivity() {
    val fromAlbum = 2
    override fun onCreate(savedInstanceState: Bundle?) {
        // ...
        fromAlbumBtn.setOnClickListener {
            // 打开文件选择器
            val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
            intent.addCategory(Intent.CATEGORY_OPENABLE)
            // 指定只显示图片
            intent.type = "image/*"
            startActivityForResult(intent, fromAlbum)
        }
    }
    // 时间完成后的回调函数
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        when (requestCode) {
            // ...
            fromAlbum -> {
                if (resultCode == Activity.RESULT_OK && data != null) {
                    data.data?.let { uri ->
                        // 将选择的图片显示
                        val bitmap = getBitmapFromUri(uri)
                        imageView.setImageBitmap(bitmap)
                    }
                }
            }
        }
    }
    private fun getBitmapFromUri(uri: Uri) = contentResolver
        .openFileDescriptor(uri, "r")?.use {
            BitmapFactory.decodeFileDescriptor(it.fileDescriptor)
        }
}

Android多线程 与 异步消息机制

// 通过继承类
class MyThread : Thread() {
    override fun run() {
        // ...
    }
}
MyThread().start()

// 使用 lambda 表达式
Thread {
    // ...
}.start()

// kotlin提供的简写方法
thread {
    // ...
}

// 使用接口
class MyThread : Runnable {
    override fun run() {
    }
}
val myThread = MyThread()
Thread(myThread).start()

异步消息机制

4部分组成:

  1. Message
    Message是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间传递数据。除了what字段还可以使用arg1和arg2字段来携带一些整型数据,使用obj字段携带一个Object对象。
  2. Handler
    Handler是处理者的意思,用于发送和处理消息的。
    发送消息用sendMessage()方法、post()方法等,
    传递到Handler的handleMessage()方法中。
  3. MessageQueue
    MessageQueue是消息队列的意思,
    用于存放所有通过Handler发送的消息。这部分消息会一直存在于消息队列中,等待被处理。
    每个线程中只会有一个MessageQueue对象。
  4. Looper
    Looper是每个线程中的MessageQueue的管家,调用Looper的loop()方法后,就会进入一个无限循环当中,然后每当发现MessageQueue中存在一条消息时,就会将它取出,并传递到Handler的handleMessage()方法中。每个线程中只会有一个Looper对象。

主线程中使用 Looper.getMaininLooper() 创建Handler对象并重写处理消息的 handleMessage 方法,子线程要操作UI时就创建Message对象通过handle的 sendMessage 发送到MessageQueue中,Looper会一直尝试从MessageQueue中获取Message并发送到Handler的handleMessage方法处理。消息从子线程传递到主线程,可以更新UI。

/**
 * UI是线程不安全的,默认不能使用子线程修改
 * 但是可能在子线程执行耗时任务,根据结果更新UI控件
 * 可以通过异步消息处理机制在子线程中更新UI
 */
class MainActivity : AppCompatActivity() {
    // 用于指定动作,一个页面中可能有多个更新UI的操作
    val updateText = 1
    val handler = object : Handler(Looper.getMaininLooper()) {
        // 此代码在主线程中运行
        override fun handleMessage(msg: Message) {
            // 在这里可以进行UI操作
            when (msg.what) {
                updateText -> textView.text = "Nice to meet you"
            }
        }
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        changeTextBtn.setOnClickListener {
            thread {
                // 显示执行耗时操作...
                val msg = Message()      // 创建 android.os.Message 对象
                msg.what = updateText
                handler.sendMessage(msg) // 将Message对象发送出去
            }
        }
    }
}

AsyncTask

对异步消息机制的封装,AsyncTask是一个抽象类,有3个泛型参数:

  1. Params。在执行AsyncTask时需要传入的参数,可用于在后台任务中使用。
  2. Progress。在后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。
  3. Result。当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。 

要重写几个方法:

  1. onPreExecute()
    后台任务执行前调用,进行界面初始化
  2. doInBackground(Params...)
    在子线程执行,处理耗时任务。通过return返回结果,如果第三个泛型参数为Unit则可以不返回任务结果。
    注意:这里不可以操作UI,可通过调用 publishProgress (Progress...) 反馈当前任务进度
  3. onProgressUpdate(Progress...)
    子线程中调用的 publishProgress 方法会调用这个方法,参数就是子线程中传递来的。这里可操作UI
  4. onPostExecute(Result)
    子线程结束返回后调用此方法,可修改UI
class DownloadTask : AsyncTask<Unit, Int, Boolean>() {
    override fun onPreExecute() {
        progressDialog.show() // 显示进度对话框
    }
    override fun doInBackground(vararg params: Unit?) = try {
        while (true) {
            val downloadPercent = doDownload() // 这是一个虚构的方法
            publishProgress(downloadPercent)
            if (downloadPercent >= 100) {
                break
            }
        }
        true
    } catch (e: Exception) {
        false
    }
    override fun onProgressUpdate(vararg values: Int?) {
        // 在这里更新下载进度
        progressDialog.setMessage("Downloaded ${values[0]}%")
    }
    override fun onPostExecute(result: Boolean) {
        progressDialog.dismiss()// 关闭进度对话框
        // 在这里提示下载结果
        if (result) {
            Toast.makeText(context, "Download succeeded", Toast.LENGTH_SHORT).show()
        } else {
            Toast.makeText(context, " Download failed", Toast.LENGTH_SHORT).show()
        }
    }
}

// 启动任务,可以在execute中增加参数,会传入 doInBackground
DownloadTask().execute()

Service

Service并不是运行在一个独立的进程当中的,而是依赖于创建Service时所在的应用程序进程。当某个应用程序进程被杀掉时,所有依赖于该进程的Service也会停止运行。
Service并不会自动开启线程,所有的代码都是默认运行在主线程当中的。也就是说,我们需要在Service的内部手动创建子线程,并在这里执行具体的任务,否则就有可能出现主线程被阻塞的情况。

Android8.0 后后台功能大幅消减,后台Service随时可能回收,需要长期在后台执行任务可使用前台Service活WorkManager

自动在 AndroidManifest.xml 中注册:

<service
     android:name=".MyService"
     android:enabled="true"
     android:exported="true">
</service>

Service对象:

class MyService : Service() {
    // 创建时调用
    override fun onCreate() {
        super.onCreate()
    }
    // 每次启动时调用
    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        return super.onStartCommand(intent, flags, startId)
    }
    // 销毁时调用
    override fun onDestroy() {
        super.onDestroy()
    }

    private val mBinder = DownloadBinder()
    class DownloadBinder : Binder() {
        // 模拟开始下载
        fun startDownload() {
            Log.d("MyService", "startDownload executed")
        }
        // 模拟查看下载进度
        fun getProgress(): Int {
            Log.d("MyService", "getProgress executed")
            return 0
        }
    }
    // 用于与 Activity 进行交互
    override fun onBind(intent: Intent): IBinder {
        return mBinder
    }
}

Activity 中 启动停止 Service,绑定Service进行操作

class MainActivity : AppCompatActivity() {
    /**
     * 任何一个Service在整个应用程序范围内都是通用的,
     * 即MyService不仅可以和MainActivity绑定,还可以和任何一个其他的Activity进行绑定,
     * 而且在绑定完成后,它们都可以获取相同的DownloadBinder实例。
     */
    lateinit var downloadBinder: MyService.DownloadBinder
    private val connection = object : ServiceConnection {
        // 绑定成功时调用
        override fun onServiceConnected(name: ComponentName, service: IBinder) {
            // 获得了Binder类,可以操纵Service中动作
            downloadBinder = service as MyService.DownloadBinder
            downloadBinder.startDownload()
            downloadBinder.getProgress()
        }
        // Service 进程崩溃或被杀掉时调用,不常用
        override fun onServiceDisconnected(name: ComponentName) {
        }
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        // 启动Service
        startServiceBtn.setOnClickListener {
            val intent = Intent(this, MyService::class.java)
            startService(intent) // 启动Service,第一次点击时调用 onCreate 和 onStartCommand,之后再点击只调用 onStartCommand
        }
        // 关闭Service
        stopServiceBtn.setOnClickListener {
            val intent = Intent(this, MyService::class.java)
            stopService(intent) // 停止Service
        }
        // 绑定本 Activity 与 Service
        bindServiceBtn.setOnClickListener {
            val intent = Intent(this, MyService::class.java)
            // 第3个参数是标志位,表示绑定后自动创建Service,会执行 onCreate 而不会执行 onStartCommand
            bindService(intent, connection, Context.BIND_AUTO_CREATE)
        }
        // 解绑
        unbindServiceBtn.setOnClickListener {
            unbindService(connection) // 解绑Service
        }
    }
}

前台 Service

不会随时被系统回收,有一个正在运行的图标在系统的状态栏显示,类似通知。

class MyService : Service() {
    override fun onCreate() {
        super.onCreate()

        val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel("my_service", "前台Service通知", NotificationManager.IMPORTANCE_DEFAULT)
            manager.createNotificationChannel(channel)
        }
        val intent = Intent(this, MainActivity::class.java)
        val pi = PendingIntent.getActivity(this, 0, intent, 0)
        // 创建通知
        val notification = NotificationCompat.Builder(this, "my_service")
            .setContentTitle("This is content title")
            .setContentText("This is content text")
            .setSmallIcon(R.drawable.small_icon)
            .setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.large_icon))
            .setContentIntent(pi)
            .build()
        // 通知中使用 NotificationManager 进行显示,前台Service调用 startForeground()
        startForeground(1 /* 通知的id */, notification)
    }
}

 Android9.0 后要权限声明:

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

 即使退出前台Service也会一直运行,手动杀掉应用后Service也会停止运行。

IntentService

// Service在主线程中执行,耗时操作要使用子线程,结束后要记得手动结束Service,否则会一直执行。
class MyService : Service() {
    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        thread {
            // 耗时操作
            stopSelf()
        }
        return super.onStartCommand(intent, flags, startId)
    }
}

IntentService 用于简化上面的操作

class MyIntentService : IntentService("MyIntentService") /* 父类构造的参数只在调试时有用 */ {
    // 默认在子线程中调用,可执行耗时操作
    override fun onHandleIntent(intent: Intent?) {
        // 打印当前线程的id
        Log.d("MyIntentService", "Thread id is ${Thread.currentThread().name}")
    }
    override fun onDestroy() {
        super.onDestroy()
        Log.d("MyIntentService", "onDestroy executed")
    }
}

网络

WebView

声明网络权限: <uses-permission android:name="android.permission.INTERNET" />

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent" >
    <WebView
             android:id="@+id/webView"
             android:layout_width="match_parent"
             android:layout_height="match_parent" />
</LinearLayout>
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        webView.settings.javaScriptEnabled=true
        webView.webViewClient = WebViewClient()
        webView.loadUrl("https://www.baidu.com")
    }
}

HTTP

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        sendRequestBtn.setOnClickListener {
            sendRequestWithHttpURLConnection()
        }
    }
    private fun sendRequestWithHttpURLConnection() {
        // 开启线程发起网络请求
        thread {
            var connection: HttpURLConnection? = null
            try {
                val response = StringBuilder()
                val url = URL("https://www.baidu.com")
                connection = url.openConnection() as HttpURLConnection
                connection.connectTimeout = 8000
                connection.readTimeout = 8000
                val input = connection.inputStream
                // 下面对获取到的输入流进行读取
                val reader = BufferedReader(InputStreamReader(input))
                reader.use {
                    reader.forEachLine {
                        response.append(it)
                    }
                }
                showResponse(response.toString())
            } catch (e: Exception) {
                e.printStackTrace()
            } finally {
                connection?.disconnect()
            }
        }
    }
    private fun showResponse(response: String) {
        runOnUiThread {
            // 在这里进行UI操作,将结果显示到界面上
            responseText.text = response
        }
    }
}
// POST 请求
connection.requestMethod = "POST"
val output = DataOutputStream(connection.outputStream)
output.writeBytes("username=admin&password=123456")

OkHttp

// 在 app/build.gradle 中添加依赖
dependencies {
    implementation 'com.squareup.okhttp3:okhttp:4.1.0'
}
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        sendRequestBtn.setOnClickListener {
            sendRequestWithOkHttp()
        }
    }
    private fun sendRequestWithOkHttp() {
        thread {
            try {
                val client = OkHttpClient()
                val request = Request.Builder()
                    .url("https://www.baidu.com")
                    .build()
                val response = client.newCall(request).execute()
                val responseData = response.body?.string()
                if (responseData != null) {
                    showResponse(responseData)
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }
}

JSON

// 自带的 JSONObject 库
private fun parseJSONWithJSONObject(jsonData: String) {
    try {
        val jsonArray = JSONArray(jsonData)
        for (i in 0 until jsonArray.length()) {
            val jsonObject = jsonArray.getJSONObject(i)
            val id = jsonObject.getString("id")
            val name = jsonObject.getString("name")
            val version = jsonObject.getString("version")
            Log.d("MainActivity", "id is $id")
            Log.d("MainActivity", "name is $name")
            Log.d("MainActivity", "version is $version")
        }
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

// 谷歌的第三方库 GSON
// 将 [{"name":"Tom","age":20}, {"name":"Jack","age":25}, {"name":"Lily","age":22}] 
// 解析为 class App(val id: String, val name: String, val version: String)
private fun parseJSONWithGSON(jsonData: String) {
    val gson = Gson()
    val typeOf = object : TypeToken<List<App>>() {}.type
    val appList = gson.fromJson<List<App>>(jsonData, typeOf)
    for (app in appList) {
        Log.d("MainActivity", "id is ${app.id}")
        Log.d("MainActivity", "name is ${app.name}")
        Log.d("MainActivity", "version is ${app.version}")
    }
}

网络请求回调 - 封装网络工具类

应当使用工具类封装网络请求。网络请求是耗时操作,要在子线程中进行。使用回调机制处理请求结果。

封装 HttpURLConnection

// 封装成工具类
interface HttpCallbackListener {
    fun onFinish(response: String)
    fun onError(e: Exception)
}
object HttpUtil {
    fun sendHttpRequest(address: String, listener: HttpCallbackListener) {
        thread {
            var connection: HttpURLConnection? = null
            try {
                val response = StringBuilder()
                val url = URL(address)
                connection = url.openConnection() as HttpURLConnection
                connection.connectTimeout = 8000
                connection.readTimeout = 8000
                val input = connection.inputStream
                val reader = BufferedReader(InputStreamReader(input))
                reader.use {
                    reader.forEachLine {
                        response.append(it)
                    }
                }
                // 回调onFinish()方法
                listener.onFinish(response.toString())
            } catch (e: Exception) {
                e.printStackTrace()
                // 回调onError()方法
                listener.onError(e)
            } finally {
                connection?.disconnect()
            }
        }
    }
}

// 工具类的使用
HttpUtil.sendHttpRequest(address, object : HttpCallbackListener {
    override fun onFinish(response: String) {
        // 得到服务器返回的具体内容
    }
    override fun onError(e: Exception) {
        // 在这里对异常情况进行处理
    }
})

封装 OkHttp

object HttpUtil {
    fun sendOkHttpRequest(address: String, callback: okhttp3.Callback) {
        val client = OkHttpClient()
        val request = Request.Builder()
            .url(address)
            .build()
        client.newCall(request).enqueue(callback)
    }
}
// 使用工具类
HttpUtil.sendOkHttpRequest(address, object : Callback {
    override fun onResponse(call: Call, response: Response) {
        // 得到服务器返回的具体内容
        val responseData = response.body?.string()
    }
    override fun onFailure(call: Call, e: IOException) {
        // 在这里对异常情况进行处理
    }
})

Retrofit

第三方库,依赖 OkHttp、GSON。基于OkHttp的上层接口封装,几个假设前提:

  1. 发起的网络请求绝大多数指向同一个域名
  2. 服务器提供的接口可按照功能模块划分,如用户类、商品类、订单类
  3. 开发者倾向于调用接口获取返回值的方式
// 数据解析为此类
class App(val id: String, val name: String, val version: String)
    
interface ExampleService {
    @GET("get_data.json")
    fun getAppData(): Call<List<App>>

    @GET("{page}/get_data.json")
    fun getData(@Path("page") page: Int): Call<Data>

    @Headers("User-Agent: okhttp", "Cache-Control: max-age=0")
    @GET("get_data.json")
    fun getData(@Query("u") user: String, @Query("t") token: String): Call<Data>

    @DELETE("data/{id}")
    fun deleteData(@Path("id") id: String,
                   @Header("User-Agent") userAgent: String, // 动态指定头
                   @Header("Cache-Control"): Call<ResponseBody>

    @POST("data/create")
    fun createData(@Body data: Data): Call<ResponseBody>
}

getAppDataBtn.setOnClickListener {
    val retrofit = Retrofit.Builder()
        .baseUrl("http://10.0.2.2/")
        .addConverterFactory(GsonConverterFactory.create())
        .build()
    val appService = retrofit.create(AppService::class.java)
    // 调用 enqueue 时开启子线程,数据回到callback时切换到主线程
    appService.getAppData().enqueue(object : Callback<List<App>> {
        override fun onResponse(call: Call<List<App>>, response: Response<List<App>>) {
            val list = response.body()
            if (list != null) {
                for (app in list) {
                    Log.d("MainActivity", "id is ${app.id}")
                    Log.d("MainActivity", "name is ${app.name}")
                    Log.d("MainActivity", "version is ${app.version}")
                }
            }
        }
        override fun onFailure(call: Call<List<App>>, t: Throwable) {
            t.printStackTrace()
        }
    })
}

 

posted @ 2023-01-05 13:49  某某人8265  阅读(34)  评论(0编辑  收藏  举报