这一篇要实现双向控制,也就是说可以通过通知栏控制Activity的UI,也可以通过Activity来控制通知栏上的UI。这里可以通过观察者模式来实现,什么是观察者模式呢?就是一对多,举个例子,平时各位大佬可能会看技术博客,会关注某一些博主,而你关注的某一个博主就是被观察者,你则是观察者,而一个博主可能有很多个粉丝,也就是可以由多个观察者,每个观察者都能知道这个博主的最新动态和文章。这样的方式叫做观察者模式,Android中的广播,EventBus、 RXBus、LiveData都可以实现这种模式,当前最新的应该是LiveData了,这一个也是Jetpack全家桶里面的一个重要成员,下面就用这个LiveData来实现通知栏和页面的双向通讯。
首先在com.llw.goodmusic下新建一个livedata包,然后新建一个LiveDataBus类,如下图所示,这个里面就是存放不同的Livedata的
代码如下:
package com.llw.goodmusic.livedata;
import androidx.annotation.NonNull;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
-
liveData管道总线
-
@author llw
*/
public class LiveDataBus {
/**
- LiveData集合
*/
private Map<String, BusMutableLiveData> liveDataMap;
private static LiveDataBus liveDataBus = new LiveDataBus();
private LiveDataBus(){
liveDataMap = new HashMap<>();
}
public static LiveDataBus getInstance(){
return liveDataBus;
}
/**
-
这个是存和取一体的方法
-
@param key
-
@param clazz
-
@param
-
@return
*/
public synchronized BusMutableLiveData with(String key,Class clazz){
if(!liveDataMap.containsKey(key)){
liveDataMap.put(key,new BusMutableLiveData());
}
return (BusMutableLiveData) liveDataMap.get(key);
}
public static class BusMutableLiveData extends MutableLiveData {
//是否需要粘性事件
private boolean isHad = false;
//重写observe的方法
public void observe(@NonNull LifecycleOwner owner, boolean isHad,@NonNull Observer<? super T> observer) {
if(isHad){
this.isHad = true;
}else{
this.isHad = false;
}
this.observe(owner,observer);
}
//重写observe的方法
@Override
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
super.observe(owner, observer);
//改变observer.mLastVersion >= mVersion这个判断 然后拦截onChanged
try {
if(this.isHad){
hook((Observer) observer);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
-
hook方法 hook系统源码 改变系统的一些参数
-
@param observer
*/
private void hook(Observer observer) throws Exception {
//获取到LiveData的类对象
Class liveDataClass = LiveData.class;
//获取到mObservers的反射对象
Field mObserversField = liveDataClass.getDeclaredField(“mObservers”);
//让mObserversField可以被访问
mObserversField.setAccessible(true);
//获取到这个mObserversField的值
Object mObservers = mObserversField.get(this);
//获取到mObservers的get方法的反射对象
Method get = mObservers.getClass().getDeclaredMethod(“get”, Object.class);
//设置这个反射对象可以被访问
get.setAccessible(true);
//执行这个方法 得到Entry
Object invokeEntry = get.invoke(mObservers, observer);
//定义一个空的对象 LifecycleBoundObserver
Object observerWrapper = null;
if(invokeEntry!=null && invokeEntry instanceof Map.Entry){
observerWrapper = ((Map.Entry)invokeEntry).getValue();
}
if(observerWrapper == null){
throw new NullPointerException(“ObserverWrapper不能为空”);
}
//获取到ObserverWrapper的类对象
Class<?> superclass = observerWrapper.getClass().getSuperclass();
//获取搭配这个类中的mLastVersion成员变量
Field mLastVersionField = superclass.getDeclaredField(“mLastVersion”);
mLastVersionField.setAccessible(true);
//获取到mVersion的反射对象
Field mVersionField = liveDataClass.getDeclaredField(“mVersion”);
//打开权限
mVersionField.setAccessible(true);
//得到的就是mVersion在当前类中的值
Object o = mVersionField.get(this);
//把它的值给mLastVersion
mLastVersionField.set(observerWrapper,o);
}
}
}
这里面的代码是我在第一次听课的过程中一步一步写的,整体的思路并不是我想的,也可以说是学习了。上面的代码也都有注释,就不过多解释了,下面看怎么去用,首先通过通知栏控制Activity,也就是页面,下面我就都用页面二字代替Activity了。说白了其中的思路就是当我点击了通知栏上的某一个按钮的时候,服务会知道这个事情,因为之前注册了广播,但是页面怎么知道这个事情呢,所以其实就是要让服务去告诉页面,我当前点击了通知栏上某一个按钮。然后页面就去做出相应的操作,在这里服务是被观察者,而页面是观察者,所以我们可以在服务中安插一个卧底,让它告诉我们通知栏干了啥事。这么一想思路就很清晰了,下面来看具体的实现。
进入MusicService中,
/**
- 通知栏控制Activity页面UI
*/
private LiveDataBus.BusMutableLiveData activityLiveData;
创建对象,传入String就代表接收的也是String,这里你可以传递任何类型的数据。
然后要在onCreate进行实例化。
activityLiveData = LiveDataBus.getInstance().with(“activity_control”, String.class);
这样,卧底就安插好了,下面就是要告诉卧底什么时候要给页面传递消息了。
首先当然是播放了,注意上图中红色方框中的代码,postValue就是发送消息,PLAY是在Constant中定义的全局变量。就是告诉页面,我当前的音乐开始播放了。
当我点击通知的播放按钮时回调用这个方法,那么在这个方法里面首先控制了通知栏本身的按钮UI,于是我再加上对页面的控制,就是一举两得。
关闭通知栏的时候也要告诉页面,这个时候页面上的UI也要做出改变,
下一首和上一首都要通知页面,到这里该告诉页面的信息都说了,再说的话卧底可能就要暴露了,不太好吧。既然卧底的消息都发出去了,那么页面怎么做出相应的改变呢?进入MainActivity。
/**
- 当Service中通知栏有变化时接收到消息
*/
private LiveDataBus.BusMutableLiveData activityLiveData;
创建对象,然后在initData中调用notificationObserver方法对服务发送过来的消息进行处理。
notificationObserver的代码如下:
/**
- 通知栏动作观察者
*/
private void notificationObserver() {
activityLiveData = LiveDataBus.getInstance().with(“activity_control”, String.class);
activityLiveData.observe(MainActivity.this, true, new Observer() {
@Override
public void onChanged(String state) {
switch (state) {
case PLAY:
btnPlay.setIcon(getDrawable(R.mipmap.icon_pause));
btnPlay.setIconTint(getColorStateList(R.color.gold_color));
changeUI(musicService.getPlayPosition());
break;
case PAUSE:
case CLOSE:
btnPlay.setIcon(getDrawable(R.mipmap.icon_play));
btnPlay.setIconTint(getColorStateList(R.color.white));
changeUI(musicService.getPlayPosition());
break;
case PREV:
BLog.d(TAG, “上一曲”);
changeUI(musicService.getPlayPosition());
break;
case NEXT:
BLog.d(TAG, “下一曲”);
changeUI(musicService.getPlayPosition());
break;
default:
break;
}
}
});
}
在上面我通过在服务中安插卧底得知通知栏的一些机密信息,那么通知栏也不是省油的灯,也可以安插卧底在Activity中,得知页面的信息,然后更好的伪装自己。再次之前,首先要添加一个依赖。打开app下的build.gradle,在dependencies闭包中添加如下依赖库,然后Sync
//Service中使用lifecycle
implementation “androidx.lifecycle:lifecycle-service:2.2.0”
到这一步就先不管这个了。还是在MainActivity中,
/**
- 当在Activity中做出播放状态的改变时,通知做出相应改变
*/
private LiveDataBus.BusMutableLiveData notificationLiveData;
然后在initData中,增加一个notification_control的标识key
目前我只是通过MainActivity的底部播放按钮开始播放音乐,这个时候时候通知栏有变化,但是如果这时再点击一次按钮,就是暂停了,而不是重新播放,暂停后再点就是继续播放。所以要在底部按钮的点击事件中,发送一个消息过去。
下面进入到MusicService。
/**
- Activity控制通知栏UI
*/
private LiveDataBus.BusMutableLiveData notificationLiveData;
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
分享读者
作者2013年java转到Android开发,在小厂待过,也去过华为,OPPO等大厂待过,18年四月份进了阿里一直到现在。
被人面试过,也面试过很多人。深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长,而且极易碰到天花板技术停滞不前!
我们整理了一份阿里P7级别的Android架构师全套学习资料,特别适合有3-5年以上经验的小伙伴深入学习提升。
主要包括腾讯,以及字节跳动,阿里,华为,小米,等一线互联网公司主流架构技术。
如果你觉得自己学习效率低,缺乏正确的指导,可以一起学习交流!
我们致力打造一个平等,高质量的Android交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。
35岁中年危机大多是因为被短期的利益牵着走,过早压榨掉了价值,如果能一开始就树立一个正确的长远的职业规划。35岁后的你只会比周围的人更值钱。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
被人面试过,也面试过很多人。深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长,而且极易碰到天花板技术停滞不前!
我们整理了一份阿里P7级别的Android架构师全套学习资料,特别适合有3-5年以上经验的小伙伴深入学习提升。
主要包括腾讯,以及字节跳动,阿里,华为,小米,等一线互联网公司主流架构技术。
[外链图片转存中…(img-FLxoEETa-1713772039359)]
如果你觉得自己学习效率低,缺乏正确的指导,可以一起学习交流!
我们致力打造一个平等,高质量的Android交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。
35岁中年危机大多是因为被短期的利益牵着走,过早压榨掉了价值,如果能一开始就树立一个正确的长远的职业规划。35岁后的你只会比周围的人更值钱。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!