多媒体
通知
通知渠道:程序对自己发出的通知进行分类,用户可根据渠道对消息进行屏蔽或设置响铃振动。
一个应用的通知渠道一旦创建就无法再修改,只能再创建新的
可在 Activity、BroadcastReceiver、Service 中使用,大多数场景是在后台时使用。
通知相关的API经常变,可以使用 AndroidX 库中的封装:NotificationCompat
点击效果要用 PendingIntent ,Intent表示立即执行某个动作,PendingIntent 表示延迟执行动作。
PendingIntent 对象提供几个静态方法:getActivity getBroadcast getService,这几个方法参数相同:
- Context
- 传入 0 即可
- Intent 对象
- 传入 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部分组成:
- Message
Message是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间传递数据。除了what字段还可以使用arg1和arg2字段来携带一些整型数据,使用obj字段携带一个Object对象。 - Handler
Handler是处理者的意思,用于发送和处理消息的。
发送消息用sendMessage()方法、post()方法等,
传递到Handler的handleMessage()方法中。 - MessageQueue
MessageQueue是消息队列的意思,
用于存放所有通过Handler发送的消息。这部分消息会一直存在于消息队列中,等待被处理。
每个线程中只会有一个MessageQueue对象。 - 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个泛型参数:
- Params。在执行AsyncTask时需要传入的参数,可用于在后台任务中使用。
- Progress。在后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。
- Result。当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。
要重写几个方法:
- onPreExecute()
后台任务执行前调用,进行界面初始化 - doInBackground(Params...)
在子线程执行,处理耗时任务。通过return返回结果,如果第三个泛型参数为Unit则可以不返回任务结果。
注意:这里不可以操作UI,可通过调用 publishProgress (Progress...) 反馈当前任务进度 - onProgressUpdate(Progress...)
子线程中调用的 publishProgress 方法会调用这个方法,参数就是子线程中传递来的。这里可操作UI - 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 one rror(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 one rror(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的上层接口封装,几个假设前提:
- 发起的网络请求绝大多数指向同一个域名
- 服务器提供的接口可按照功能模块划分,如用户类、商品类、订单类
- 开发者倾向于调用接口获取返回值的方式
// 数据解析为此类
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()
}
})
}
标签:02,Service,val,override,intent,fun,多线程,class From: https://www.cnblogs.com/zhh567/p/17025771.html