RemoteView(kotlin)

使用场景:通知栏&桌面部件

自定义通知栏

  1. 通知权限申请
    manifest配置
java 复制代码
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

权限动态申请

java 复制代码
package com.example.kotlinlearn.Common;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.Settings;
import android.widget.Toast;

import androidx.activity.ComponentActivity;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.core.content.ContextCompat;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class PermissionUtils {
    private static PermissionUtils permissionUtils;
    private String[] permissions = {
            Manifest.permission.POST_NOTIFICATIONS
    };

    private List<String> permissionList = new ArrayList<>();

    private ActivityResultLauncher<String[]> permissionLauncher;

    public static synchronized PermissionUtils getInstance() {
        if (permissionUtils == null) {
            permissionUtils = new PermissionUtils();
        }
        return permissionUtils;
    }

    private PermissionUtils() {
    }

    public void checkPermission(ComponentActivity activity) {
        permissionList.clear(); // Clear previous permission requests

        // Initialize the launcher if not already initialized
        if (permissionLauncher == null) {
            initLaunchers(activity);
        }


        for (String permission : permissions) {
            if (ContextCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED) {
                permissionList.add(permission);
            }
        }
        permissionLauncher.launch(permissionList.toArray(new String[0]));

    }

    private void initLaunchers(ComponentActivity activity) {
        // Initialize the launcher for requesting permissions
        permissionLauncher = activity.registerForActivityResult(
                new ActivityResultContracts.RequestMultiplePermissions(),
                new ActivityResultCallback<Map<String, Boolean>>() {
                    @Override
                    public void onActivityResult(Map<String, Boolean> result) {
                    }
                }
        );
    }
}
  1. 实现通知
kotlin 复制代码
package com.example.kotlinlearn.RemoteView

import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Build
import android.widget.RemoteViews
import androidx.core.app.NotificationCompat
import com.example.kotlinlearn.R

object NotificationUtil {
    private const val CHANNEL_ID = "my_channel_id"
    private const val CHANNEL_NAME = "My Channel"

    fun showCustomNotification(context: Context) {
        // 创建通知渠道(仅适用于 Android O 及以上版本)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                CHANNEL_ID, CHANNEL_NAME,
                NotificationManager.IMPORTANCE_DEFAULT
            ).apply {
                enableLights(true)
                lightColor = Color.RED
                enableVibration(true)
            }
            val notificationManager = context.getSystemService(NotificationManager::class.java)
            notificationManager.createNotificationChannel(channel)
        }

        // 创建 RemoteView
        val remoteViews = RemoteViews(context.packageName, R.layout.notification_layout).apply {
            setTextViewText(R.id.notification_title, "自定义通知标题")
            setTextViewText(R.id.notification_content, "这是自定义通知内容")
        }

        // 设置点击通知的行为
        val intent = Intent(context, RemoteViewActivity::class.java)
        val pendingIntent = PendingIntent.getActivity(
            context,
            0,
            intent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )

        val notification = NotificationCompat.Builder(context, CHANNEL_ID)
            .setSmallIcon(R.drawable.ic_launcher_foreground)
            .setCustomContentView(remoteViews)
            .setContentIntent(pendingIntent)
            .build()

        val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        manager.notify(1, notification)
    }
}
  1. activity中申请权限后直接调用就行
kotlin 复制代码
package com.example.kotlinlearn.RemoteView

import android.os.Bundle
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
import com.example.kotlinlearn.Common.PermissionUtils
import com.example.kotlinlearn.R


class RemoteViewActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        PermissionUtils.getInstance().checkPermission(this)
        setContentView(R.layout.activity_remote_view)
        var button = findViewById<Button>(R.id.button)
        button.setOnClickListener {
            NotificationUtil.showCustomNotification(this);
        }
    }
}

效果

自定义桌面小组件

  1. 定义组件的布局样式widget_layout.xml
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <TextView
        android:id="@+id/widget_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello Widget"
        android:textSize="18sp" />

    <Button
        android:id="@+id/widget_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Update" />
</LinearLayout>
  1. MyWidgetProvider,需要继承自AppWidgetProvider
kotlin 复制代码
package com.example.kotlinlearn.RemoteView

import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.widget.RemoteViews
import com.example.kotlinlearn.R
import java.util.Random

class MyWidgetProvider : AppWidgetProvider() {
    override fun onUpdate(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetIds: IntArray
    ) {
        for (appWidgetId in appWidgetIds) {
            val views = RemoteViews(context.packageName, R.layout.widget_layout)
            val intent = Intent(context, MyWidgetProvider::class.java)
            intent.setAction(BUTTON_CLICKED)
            val pendingIntent = PendingIntent.getBroadcast(
                context,
                0,
                intent,
                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
            )
            views.setOnClickPendingIntent(R.id.widget_button, pendingIntent)
            appWidgetManager.updateAppWidget(appWidgetId, views)
        }
    }

    override fun onReceive(context: Context, intent: Intent) {
        super.onReceive(context, intent)
        if (BUTTON_CLICKED == intent.action) {
            val appWidgetManager = AppWidgetManager.getInstance(context)
            val views = RemoteViews(context.packageName, R.layout.widget_layout)
            views.setTextViewText(R.id.widget_text, "Updated!" + Random().nextInt())
            val componentName = ComponentName(context, MyWidgetProvider::class.java)
            appWidgetManager.updateAppWidget(componentName, views)
        }
    }

    companion object {
        private const val BUTTON_CLICKED = "com.example.BUTTON_CLICKED"
    }
}
  1. 在res/xml下创建组件的属性文件my_widget_info.xml,包括大小等值
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="125dp"
    android:minHeight="50dp"
    android:updatePeriodMillis="86400000"
    android:initialLayout="@layout/widget_layout" />
  1. 在manifest中配置receiver,与activity同级
xml 复制代码
        <receiver android:name=".RemoteView.MyWidgetProvider" android:exported="true">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>

            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/my_widget_info" />
        </receiver>

效果

点击后text会显示随机的数字。

原理

  1. 可以很简单的看到,RemoteViews实现了Parcelable,所以是可序列化的,可以在进程之间传递。
  2. 在官网可以看到,RemoteViews只支持基础的view,不支持自定义view,支持的布局以及组件如下所示
  3. 从上面的代码示例中可以知道,在更改组件属性时使用的是setTextViewText,而不是findById。

    从以上的调用链可以知道,view的设置被封装在反射对象,存在mActions中,是在接收者进行真正的设置。
    可以在Action的子类中找到getMethod(view, this.methodName, param, false /* async */).invoke(view, value);,接收者正是通过反射的方式调用action中封装的view设置方法。
相关推荐
CYRUS_STUDIO4 小时前
Frida 检测与对抗实战:进程、maps、线程、符号全特征清除
android·逆向
csj505 小时前
安卓基础之《(28)—Service组件》
android
lhbian7 小时前
PHP、C++和C语言对比:哪个更适合你?
android·数据库·spring boot·mysql·kafka
catoop8 小时前
Android 最佳实践、分层架构与全流程解析(2025)
android
ZHANG13HAO8 小时前
Android 13 特权应用(Android Studio 开发)调用 AOSP 隐藏 API 完整教程
android·ide·android studio
田梓燊8 小时前
leetcode 142
android·java·leetcode
angerdream9 小时前
Android手把手编写儿童手机远程监控App之JAVA基础
android
菠萝地亚狂想曲9 小时前
Zephyr_01, environment
android·java·javascript
sTone873759 小时前
跨端框架通信机制全解析:从 URL Schema 到 JSI 到 Platform Channel
android·前端
sTone8737510 小时前
Java 注解完全指南:从 "这是什么" 到 "自己写一个"
android·前端