在接入微信支付的时候,为了传递支付结果的值,就使用了EventBus,那时候只是简单了解了EventBus的使用,现在有时间就来深入研究一下EventBus的实现原理和源码。
EventBus的介绍
可能有部分同学还没有使用过EventBus,那我们首先来了解一下EventBus的作用。 EventBus是一款基于观察者模式的事件发布/订阅框架。简化了应用程序内各组件间、组件与后台线程间的通讯。优点是开销小,优化更优雅,以及将发送者和接受者解耦。如果Activity和Activity进行交互大家都知道,而Fragment和Fragment之间通讯写法五花八门、跨线程/跨界面我们就开始头疼了,那么我们来学习一下EventBus3。
EventBus的主要三要素:
Event:事件。可以是任意类型的对象。
Subscriber:事件订阅者。在EventBus3之前消息处理通过在onEvent、onEventMainTain、onEventBackgroundThread和onEventAsync,他们代表着四种线程模型。在EventBus3之后,事件处理的方法可以按照自己需要命名,但是需要添加一个注解@Subscribe和指定线程模型。
Publisher:事件发布者。可以在任意线程和位置发送事件。
EventBus的四种ThreadMode
POSTING(默认):如果使用事件处理函数指定了线程模型为POSTING,那么该事件在哪个线程发布出来的,事件处理函数就会在这个线程中运行,也就是说发布事件和接收事件在同一个线程。在线程模型为POSTING的事件处理函数中精良避免执行耗时操作,因为它会阻塞事件的传递。
MAIN:事件的处理会在UI线程中执行,事件处理的时间不能太长,可能出现ANR。
BACKFROUND:如果事件在UI线程发布的,那么该线程就会在新的线程中运行,如果事件本来就是子线程发布的,那么该事件处理函数直接在发布事件的线程中执行,在此事件处理函数中禁止进行UI更新操作。
ASYNC:无论事件在哪个线程发布,该事件处理函数都会在新建的子线程中执行,同样,此事件处理函数中禁止进行UI更新操作。
EventBus的基本用法
Android Studio 配置Gradle:
compile 'org.greenrobot:eventbus:3.0.0'
定义一个事件类
public class EventBusEvent {
private String message;
public EventBusEvent (String message) {
this .message = message;
}
public String getMessage () {
return message;
}
public void setMessage (String message) {
this .message = message;
}
}
注册事件
EventBus.getDefault ().register (this)
发送事件
EventBus.getDefault ().post (message)
处理事件
@Subscribe (threadMode = ThreadMode.BACKGROUND)
public void XXXXXX (EventBusEvent message) {
}
取消事件订阅
EventBus.getDefault ().unregister (this)
EventBus源码分析
我们首先从开头注册开始分析EventBus.getDefault()其实就是一个单例。
/** Convenience singleton for apps using a process-wide EventBus instance. */
public static EventBus getDefault () {
if (defaultInstance == null ) {
synchronized (EventBus.class) {
if (defaultInstance == null ) {
defaultInstance = new EventBus();
}
}
}
return defaultInstance;
}
使用双重判断方式,防止并发。接着看看 new EventBus()。
/**
* Creates a new EventBus instance; each instance is a separate scope in which events are delivered. To use a
* central bus, consider {@link #getDefault()}.
*/
public EventBus () {
this (DEFAULT_BUILDER);
}
所在的参DEFAULT_BUILDER,在EventBus.class的全局进行初始化:
private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
在看一下this调用了EventBus的另一构造方法。使用的是现在常用的Builder模式:
EventBus(EventBusBuilder builder) {
subscriptionsByEventType = new HashMap<>();
typesBySubscriber = new HashMap<>();
stickyEvents = new ConcurrentHashMap<>();
mainThreadPoster = new HandlerPoster(this , Looper.getMainLooper(), 10 );
backgroundPoster = new BackgroundPoster(this );
asyncPoster = new AsyncPoster(this );
···
}
我们得到EventBus对象后,我们就可以调用register方法。
public void register (Object subscriber) {
Class subscriberClass = subscriber.getClass();
List subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this ) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
先是获取订阅者的class,接着交给subscriberMethodFinder.findSubscriberMethods()处理,返回的是List类型的集合,对于SubscriberMethod类中,主要是用于保存订阅方法的Method对象,线程模式、事件类型、优先级、是否是粘性事件等属性。下面我们直接看看这个方法:
List findSubscriberMethods(Class subscriberClass ) {
List subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null ) {
return subscriberMethods;
}
if (ignoreGeneratedIndex) {
subscriberMethods = findUsingReflection(subscriberClass);
} else {
subscriberMethods = findUsingInfo(subscriberClass);
}
if (subscriberMethods.isEmpty()) {
throw new EventBusException("Subscriber " + subscriberClass
+ " and its super classes have no public methods with the @Subscribe annotation" );
} else {
METHOD_CACHE.put(subscriberClass, subscriberMethods);
return subscriberMethods;
}
}
首先从缓存中查找,如果缓存没有会根据ignoreGeneratedIndex选择如何查找订阅方法,ignoreGeneratedIndex默认false,可以通过EventBusBuilder来设置它的值。我们一般是通过getDefault()获取默认的EventBus对象,也就ignoreGeneratedIndex为false的情况,这时调用的是 findUsingInfo(),我们接着看看这个方法:
private List findUsingInfo(Class subscriberClass ) {
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null ) {
findState.subscriberInfo = getSubscriberInfo(findState);
if (findState.subscriberInfo != null ) {
SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
for (SubscriberMethod subscriberMethod : array ) {
if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
findState.subscriberMethods.add(subscriberMethod);
}
}
} else {
findUsingReflectionInSingleClass(findState);
}
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState);
}
上面用到了FindState这个内部类是保存订阅者的信息,我们看看它的内部结构:
static class FindState {
final List subscriberMethods = new ArrayList<>();
final Map anyMethodByEventType = new HashMap<>();
final Map subscriberClassByMethodKey = new HashMap<>();
final StringBuilder methodKeyBuilder = new StringBuilder(128 );
Class subscriberClass;
Class clazz;
boolean skipSuperClasses;
SubscriberInfo subscriberInfo;
void initForSubscriber(Class subscriberClass) {
this .subscriberClass = clazz = subscriberClass;
skipSuperClasses = false ;
subscriberInfo = null ;
}
···
}
可以看出,该内部类保存了订阅者及其订阅方法的信息,用Map一一对应来保存,接着利用initForSubscriber()进行初始化,在初始化的时候subscriberInfo设置为null,所以SubscriberMethodFinder的findUsingInfo()里面在初始化后会调用findUsingReflectionInSingleClass()方法。那么我们接着看看这个方法:
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods
try {
// This is faster than getMethods, especially when subscribers are fat classes like Activities
methods = findState.clazz .getDeclaredMethods ()
} catch (Throwable th) {
// Workaround for java.lang .NoClassDefFoundError , see https://github.com /greenrobot/EventBus/issues/149
methods = findState.clazz .getMethods ()
findState.skipSuperClasses = true
}
for (Method method : methods) {
int modifiers = method.getModifiers ()
if ((modifiers & Modifier.PUBLIC ) != 0 && (modifiers & MODIFIERS_IGNORE) == 0 ) {
Class[] parameterTypes = method.getParameterTypes ()
if (parameterTypes.length == 1 ) {
//获取该方法的@Subscribe注解
Subscribe subscribeAnnotation = method.getAnnotation (Subscribe.class )
if (subscribeAnnotation != null) {
Class eventType = parameterTypes[0 ]
if (findState.checkAdd (method, eventType)) {
ThreadMode threadMode = subscribeAnnotation.threadMode ()
findState.subscriberMethods .add (new SubscriberMethod(method, eventType, threadMode,
subscribeAnnotation.priority (), subscribeAnnotation.sticky ()))
}
}
} else if (strictMethodVerification && method.isAnnotationPresent (Subscribe.class )) {
String methodName = method.getDeclaringClass ().getName () + "." + method.getName ()
throw new EventBusException("@Subscribe method " + methodName +
"must have exactly 1 parameter but has " + parameterTypes.length )
}
} else if (strictMethodVerification && method.isAnnotationPresent (Subscribe.class )) {
String methodName = method.getDeclaringClass ().getName () + "." + method.getName ()
throw new EventBusException(methodName +
" is a illegal @Subscribe method: must be public, non-static, and non-abstract" )
}
}
}
这里主要是使用了Java的反射和对注解的解析。首先是通过反射来获取订阅者中的所有方法,并根据方法的类型、参数和注解来找到订阅方法。找到订阅方法后将订阅方法相关信息保存到FindState中。
通过上述的方法,把SubscriberMethods不断逐层返回,返回到EventBus.register(),并开始遍历每一个订阅方法,并调用subscribe(subscriber, subscriberMethod)方法对所有订阅方法进行注册,那么我们接着去这方法的实现:
private void subscribe (Object subscriber, SubscriberMethod subscriberMethod) {
Class eventType = subscriberMethod.eventType;
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
CopyOnWriteArrayList subscriptions = subscriptionsByEventType.get (eventType);
if (subscriptions == null ) {
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
}
int size = subscriptions.size();
for (int i = 0 ; i <= size; i++) {
if (i == size || subscriberMethod.priority > subscriptions.get (i).subscriberMethod.priority) {
subscriptions.add(i, newSubscription);
break ;
}
}
List> subscribedEvents = typesBySubscriber.get (subscriber);
if (subscribedEvents == null ) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
if (subscriberMethod.sticky) {
if (eventInheritance) {
Set, Object>> entries = stickyEvents.entrySet();
for (Map.Entry, Object> entry : entries) {
Class candidateEventType = entry.getKey();
if (eventType.isAssignableFrom(candidateEventType)) {
Object stickyEvent = entry.getValue();
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
} else {
Object stickyEvent = stickyEvents.get (eventType);
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}
订阅的代码主要就是做两件事,第一件是将我们的订阅方法和订阅者封装到subscriptionsByEventType和typesBySubscriber中;subscriptionsByEventType是我们投递订阅事件的时候,就是根据我们的EventType找到我们的订阅事件,从而去分发事件,处理事件的。typesBySubscriber在调用unregister()的时候,根据订阅者找到EventType,再根据EventType找到订阅事件,从而对订阅者进行解绑。 第二件是如果是粘性事件的话,就立马执行。
分析完事件注册后,到事件发送,我们通过post方法来进行对事件的提交。接下来我们看一下post的方法:
/** Posts the given event to the event bus. */
public void post (Object event) {
PostingThreadState postingState = currentPostingThreadState.get();
List eventQueue = postingState.eventQueue;
eventQueue.add(event);
if (!postingState.isPosting) {
postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
postingState.isPosting = true ;
if (postingState.canceled) {
throw new EventBusException("Internal error. Abort state was not reset" );
}
try {
while (!eventQueue.isEmpty()) {
postSingleEvent(eventQueue.remove(0 ), postingState);
}
} finally {
postingState.isPosting = false ;
postingState.isMainThread = false ;
}
}
}
代码很清晰,首先是获取一个PostingThreadState,我们看一下PostingThreadState:
/** For ThreadLocal, much faster to set (and get multiple values). */
final static class PostingThreadState {
final List eventQueue = new ArrayList();
boolean isPosting;
boolean isMainThread;
Subscription subscription;
Object event;
boolean canceled;
}
看出该PostingThreadState封装了当前线程的信息,以及订阅者、订阅事件等。那么我们看一下怎么获取PostingThreadState的:
private final ThreadLocal currentPostingThreadState = new ThreadLocal() {
@Override
protected PostingThreadState initialValue () {
return new PostingThreadState();
}
};
原来 currentPostingThreadState是一个ThreadLocal。我们回到post()方法里,继续是一个while循环,不断从队列中取出时间,调用postSingleEvent()。进去看一下:
private void postSingleEvent (Object event , PostingThreadState postingState) throws Error {
Class eventClass = event .getClass();
boolean subscriptionFound = false ;
if (eventInheritance) {
List> eventTypes = lookupAllEventTypes(eventClass);
int countTypes = eventTypes.size();
for (int h = 0 ; h < countTypes; h++) {
Class clazz = eventTypes.get (h);
subscriptionFound |= postSingleEventForEventType(event , postingState, clazz);
}
} else {
subscriptionFound = postSingleEventForEventType(event , postingState, eventClass);
}
if (!subscriptionFound) {
if (logNoSubscriberMessages) {
Log.d(TAG, "No subscribers registered for event " + eventClass);
}
if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
eventClass != SubscriberExceptionEvent.class) {
post(new NoSubscriberEvent(this , event ));
}
}
}
eventInheritance表示是否向上查找事件的父类,它的默认值为true,可以通过在EventBusBuilder中来进行配置。当eventInheritance为true时,则通过lookupAllEventTypes找到所有的父类事件并存在List中,然后通过postSingleEventForEventType方法对事件逐一处理,接下来看看postSingleEventForEventType方法:
private boolean postSingleEventForEventType (Object event , PostingThreadState postingState, Class eventClass) {
CopyOnWriteArrayList subscriptions;
synchronized (this ) {
subscriptions = subscriptionsByEventType.get (eventClass);
}
if (subscriptions != null && !subscriptions.isEmpty()) {
for (Subscription subscription : subscriptions) {
postingState.event = event ;
postingState.subscription = subscription;
boolean aborted = false ;
try {
postToSubscription(subscription, event , postingState.isMainThread);
aborted = postingState.canceled;
} finally {
postingState.event = null ;
postingState.subscription = null ;
postingState.canceled = false ;
}
if (aborted) {
break ;
}
}
return true ;
}
return false ;
}
同步取出该事件对应的subscriptions集合并遍历该集合,将事件的event和subscription传进postToSubscription()方法里。
private void postToSubscription (Subscription subscription, Object event , boolean isMainThread) {
switch (subscription.subscriberMethod.threadMode) {
case POSTING:
invokeSubscriber(subscription, event );
break ;
case MAIN:
if (isMainThread) {
invokeSubscriber(subscription, event );
} else {
mainThreadPoster.enqueue(subscription, event );
}
break ;
case BACKGROUND:
if (isMainThread) {
backgroundPoster.enqueue(subscription, event );
} else {
invokeSubscriber(subscription, event );
}
break ;
case ASYNC:
asyncPoster.enqueue(subscription, event );
break ;
default :
throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
}
}
根据取出的订阅方法的线程模式分别处理。这里我们根据图来理顺一下。
在我们的对应界面的生命周期中,会在onDestory()中对订阅者取消注册。那么我们来看一下unregister()方法。
/** Unregisters the given subscriber from all event classes. */
public synchronized void unregister (Object subscriber) {
List> subscribedTypes = typesBySubscriber.get(subscriber);
if (subscribedTypes != null ) {
for (Class eventType : subscribedTypes) {
unsubscribeByEventType(subscriber, eventType);
}
typesBySubscriber.remove(subscriber);
} else {
Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
}
}
typesBySubscriber我们在订阅者注册的过程中讲到过这个属性,他根据订阅者找到EventType,然后根据EventType和订阅者来得到订阅事件来对订阅者进行解绑。 看一下unsubscribeByEventType():
/** Only updates subscriptionsByEventType, not typesBySubscriber! Caller must update typesBySubscriber. */
private void unsubscribeByEventType (Object subscriber, Class eventType) {
List subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions != null ) {
int size = subscriptions.size();
for (int i = 0 ; i < size; i++) {
Subscription subscription = subscriptions.get(i);
if (subscription.subscriber == subscriber) {
subscription.active = false ;
subscriptions.remove(i);
i--;
size--;
}
}
}
}
逻辑十分清晰,从从subscriptionsByEventType中获取相应的subscriptions 集合,并遍历所有的subscriptions,逐一移除。
总结: 整个EventBus是基于观察者模式而构建的,我们看一下EventBus的核心架构。 EventBus好处很明显,简化我们某些界面、线程间的通讯,实现的代码也很简单。但是由于EventBus的优点导致实现的代码可能分散得较为严重,会出现后期维护较为困难,代码的可读性大大下降。
参考:
【Bugly干货分享】老司机教你 “飙” EventBus 3
EventBus 3.0进阶:源码及其设计模式 完全解析
Android事件总线(二)EventBus3.0源码解析
你可能感兴趣的:(Android)
php过滤文字中的表情字符和mysql服务端对emoji的支持
angzhan5306
php 移动开发 数据库
1.过滤emoji表情的原因在我们的项目开发中,emoji表情是个麻烦的东西,即使我们可以能存储,也不一定能完美显示,因为它的更新速度很快:在iOS以外的平台上,例如PC或者android。如果你需要显示emoji,就得准备一大堆emoji图片并使用第三方前端类库才行。即便如此,还是可能因为emoji图片不够全而出现无法显示的情况在大多数业务场景下,emoji也不是非要不可的。我们可以适当地考虑干
Unity打包APK报错 using a newer Android Gradle plugin to use compileSdk = 35
[奋斗不止]
unity-android unity Unity apk 报错
Unity打包APK报错usinganewerAndroidGradleplugintousecompileSdk=35三个报错信息如下第一个WARNING:WerecommendusinganewerAndroidGradleplugintousecompileSdk=35ThisAndroidGradleplugin(7.1.2)wastesteduptocompileSdk=32Thiswa
Android仿人人客户端(v5(2)
2401_87555477
android harmonyos 华为
@OverrideprotectedvoidsetupView(){mTopNavbar=(TopNavbar)findViewById(R.id.rl_top_navbar);mWebView=(WebView)findViewById(R.id.wv_auth);mWebView.setVerticalScrollBarEnabled(false);mWebView.setHorizontal
android学习,android后端服务器的搭建
2401_84413531
程序员 android 学习 服务器
那么在市场紧缩以及大前端的趋势下,我们移动端程序员如何突破职业瓶颈,保持个人的核心竞争力呢?一、硬技能:专业的技术知识1.Java/Kotlin开发语言Java常用数据结构:ArrayList、Vector、CopyOnWriteArrayList、HaspMap、ConcurrentHashMap、HashTable等使用场景。JVM虚拟机包括Java内存管理,GC垃圾回收机制,类加载机制。推荐
Android从零开始搭建MVVM架构(1)————DataBinding
2401_87555477
android 架构 java
type=“android.view.View.OnClickListener”/>1.2、和别名alias的使用这里我们先定义同名的2个类User。放在不同包里。publicclassUser{privateStringname;privateintage;publicUser(Stringname,intage){this.name=name;this.age=age;}}之前我们的标签就可以
spacedesk 使用教程
念九_ysl
windows
spacedesk是一款免费的软件,它允许你将一个设备用作另一个设备的扩展屏幕,支持Windows、Android和iOS等多平台设备。下面是如何使用Spacedesk的基本教程:1.安装Spacedesk在主机(WindowsPC)上安装:访问Spacedesk官网:https://www.spacedesk.net。在主页上下载并安装SpacedeskDriver。安装后,启动Spacedes
Android 数据加载与分页业务
抹香鲸的弟弟蓝鲸
android kotlin android java
1,前言这篇文章写的是数据加载和分页。用到的库有'androidx.lifecycle:lifecycle-service:2.4.0''androidx.lifecycle:lifecycle-extensions:2.2.0''androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'io.reactivex.rxjava3:rxjava:3.0.4io
Android输入事件传递流程系统源码级解析
古苏
android
1.硬件层到Linux内核设备节点:触摸事件由内核驱动捕获,写入/dev/input/eventX。关键结构体:input_event(包含时间戳、类型、代码、值)。2.Native层处理(system_server进程)2.1EventHub路径:frameworks/native/services/inputflinger/EventHub.cpp职责:通过epoll监听设备节点,读取原始事件
Jenkins 构建 Unity打包APK
[奋斗不止]
jenkins Unity jenkins unity 批处理 编辑器
Jenkins构建Unity打包APK一、创建一个Pipeline任务在项目跟目录创建Pipeline脚本jenkins_scripts\Pipeline\android_master_pipeline脚本如下//AndroidMaster打包apkpipeline{agentanystages{stage('TestParameter'){steps{script{//shell脚本目录ANDR
带经纬度的水印相机_水印今日相机安卓版下载-水印相机拍照时间地点app下载v2.8.8.14-西西软件下载...
健康菌
带经纬度的水印相机
水印相机拍照时间地点app是一款可以定位的拍照软件,在这里拍摄的照片都可以自动添加,照片拍摄时间和地点还有经纬度,不管多久都可以知道照片的故事,需要的伙伴,赶紧来西西下载吧!水印相机拍照时间地点app简介:记录宝宝出生天数,工程管理、外出考勤、综合执法都在用!2018年iPhone获奖相机App,登录上线Android版本!300万人都在用的相机App!今天相机,最美的视频画面,最丰富的记录元素,
Jetpack Architecture系列教程之(一)——Jetpack介绍
l软件定制开发工作室
Jetpack Architecture教程 android jetpack
目录背景Support库Support库的弊端AndroidX简介Jetpack分类Foundation(基础组件):Architecture(架构组件):Behavior(行为):UI(界面组件):背景Support库早之前的Android更新迭代是,所有的功能更新都是跟随着每一个特定的Android版本所发布的。例如:Fragment是在Android3.0更新的。MaterialDesign
做一套手机UI自动化测试的全套系统,支持对Android、ios进行UI自动化测试,使用什么样的后端、前端、UI自动化框架、持续集成和部署方案
Zero_pl
自动化
构建一个支持Android和iOSUI自动化测试的全套系统,需要选择适合的技术栈来满足不同平台的需求。以下是推荐的技术组合,涵盖后端、前端、UI自动化框架、以及持续集成和部署方案:---###**1.后端:**后端用于管理测试用例、执行任务、查看测试结果等功能。-**框架/语言:**-**Python+FastAPI**:轻量、高性能,易于扩展,支持异步操作。-**Node.js+Express*
Android中TabLayout修改item的间距
依旧00
android TabLayout TabItem Android
在Android的TabLayout中item的间距是默认的,之前找了很多方法都没有找到调整的方法,如果要修改则可以给TabLayout添加以下两个属性app:tabMaxWidth=""app:tabMinWidth="添加完这两个属性后item之间默认的间距就会消失,这个时候就可以自己给item设置间距了,还是在TabLayout中添加以下属性app:tabPaddingStart=""app
首次开机android.intent.action.BOOT_COMPLETED开机广播发送慢的问题
IT生活课堂
android
1.背景做过android开发的同学相信一定做个这种逻辑:app接收BOOT_COMPLETED开机广播,自启动,或者收到广播做一些事情。目前在我们的项目上遇到首次开机,BOOT_COMPLETED开机广播发送慢的问题。接下来分享记录下如何定位这类问题。2.分析过程01-0108:00:33.68924710141840Iwm_create_activity:[0,42774215,7,com.a
android tab选中变大,Android之解决TabLayout里面每个Tab项的间距和修改指示线的长度(非反射)和修改选中字体大小...
应仁学术
android tab选中变大
1问题1)由于TabLayout设置了可滑动Tab参数app:tabMode="scrollable"导致每个Tab之间的间距很大,但是UI设计图里面的字体之间的间距很小,我们改怎么解决?2)修改TabLayout指示线的长度(非反射)3)修改选中的字体大小并且加粗2解决办法问题1解决办法:对TabLayout进行自定义,然后反射下面字段进行修改scrollableTabMinWidthimpor
android申请蓝牙的权限,Android Studio 真机调试捕获异常申请和蓝牙权限代码
weixin_39853210
android申请蓝牙的权限
AndroidStudio真机调试捕获异常申请和蓝牙权限代码第一步:在Android的manifest.xml文档中加入下面的声明:android:name="android.hardware.bluetooth_le"android:required="true"/>第二步:在onCreate()方法加入如下两行:checkAndRequestPermission();//第一步:targetS
android自定义tab下划线变大,一篇文章带你解决Android TabLayout缺陷,不同方式带你改变Tab下划线宽度【实践总结】...
我家大宝贝拢龙
TabLayout我们再熟悉不过了,在开发中,像这种tab切换的需求都会用到TabLayout,它是由官方提供的一个控件,在supportdesign包中。使用起来非常简单方便,交互效果也很不错,能满足我们开发中95%的需求。但是它有一个缺陷:不能改变Tab下划线(Indicator)的宽度。本篇文章给你带来改变Tab下划线宽度的几种方式:1.通过反射设置Tab下划线的宽度2.通过TabLayou
Android中自定义权限permission
code_wang_hui
Android android app 开发人员 应用
Android中权限是什么概念呢?首先一点,权限是针对于Android组件来说的,同时权限在不同应用间才起作用。Android权限就相当于一把锁,将Android组件锁上,只有应用内的组件以及有权限的不同应用可以访问。Android中的运行时安全性检查是在进程级别和操作级别上进行的。在进程级别,Android禁止一个应用程序直接访问另一个应用程序的数据。实现方法是,每个应用程序都在不同的进程中运行
Unity 权限 之 Android 【权限 动态申请】功能的简单封装
仙魁XAN
Unity 进阶 unity android 权限申请 动态权限申请 permission
Unity权限之Android【权限动态申请】功能的简单封装目录Unity权限之Android【权限动态申请】功能的简单封装一、简单介绍二、Android权限动态申请三、实现原理四、注意事项五、案例实现简单步骤附录:一、进一步优化二、多个权限申请代码参考一、简单介绍Unity是一个功能强大的跨平台游戏引擎,广泛用于开发视频游戏和其他实时3D互动内容,如模拟器和虚拟现实应用。游戏引擎:Unity:U
Android GreenDAO 适配 AGP 8.0+
nukix
android android
在Android中使用GreenDao,由于GreenDao现在不维护,所以更新到新版本的Gradle经常出问题,在这记录一些升级遇到的问题,并且记录解决方案。博主博客https://blog.uso6.comhttps://blog.csdn.net/dxk539687357一、‘:app:greendao’withoutdeclaringanexplicitorimplicitdependen
Android HID设备(键盘、遥控等)功能实现流程及键值映射关系
飞猿_SIR
android 计算机外设
HID(HumanInterfaceDevice,人机接口设备)是USB设备中常用的设备类型,是直接与人交互的USB设备,例如键盘、遥控器、鼠标与游戏杆等。在USB设备中,HID设备的成本较低。之前文章android键盘(遥控)键值定义大全中整理了android中各种功能键值定义,那么从键盘按键到安卓设备控制生效的流程是怎样的呢?Android设备上的键盘操作实现流程:EventHub从evdev
Android 开机自动加载新wifi模块驱动
飞猿_SIR
android
在androidROM开发中经常会遇到产品更换wifi模块的情况,一般常见的wifi驱动android内核中自带都有,无需过多调试,要是碰上内核中没有的就需要根据厂商提供的驱动进行移植。wifi驱动移植过程不多说,根据厂商提供的文档说明进行移植编译出驱动文件xxx.ko即可。在成功编译出驱动ko文件后,手动insmod加载驱动是可以的,但是开机时不会自动加载,这时可按以下步骤进行调整,让设备开机自
Android TabLayout 实现随意控制item之间的间距
-优势在我
android ui java
效果红色标注是不同的间距。实现方式1、xml中定义2、代码动态添加自定义tabfor(inti=0;i3、去除tablayout原有padding,并设置两个tab之间的间距for(inti=0;i0){params.setMargins(DisplayUtils.dp2px(86),0,0,0);//设置左右间距}else{params.setMargins(DisplayUtils.dp2px
android监控app被杀死,Android App前后台监控
weixin_39543773
android监控app被杀死
说到App的前后台监控,有什么使用场景呢?例如:IM模块,收到消息时,需要判断当前App是否在前台,如果在前台则震动一下提醒用户,如果在后台则发送一条通知提醒用户。收到Push推送,需要判断App是否存活,如果存活则直接跳转到目标界面,如果不存活则先启动App,再跳转到目标页面。有没有简便的ApiiOS上的AppDelegateapplicationDidEnterBackground方法回调提醒
android adb杀死服务,Android app是如何杀掉的
乔一鸭
android adb杀死服务
1.adbshellkill-9pid_of_appAMS定义了AppDeathRecipientAPP在attachApplication->attachApplicationLockedAMS里会注册App进程的BinderDeath通知AppDeathRecipientadr=newAppDeathRecipient(app,pid,thread);thread.asBinder().lin
android app如何在后台保活,不被系统杀掉
大模型大数据攻城狮
android android面试 安卓面试 android进程 定时唤醒 android通知 双进程
目录什么是进程保活系统杀死进程的原因前台服务双进程守护JobSchedulerWorkManager系统白名单关闭电池优化通知栏常驻定时唤醒合理使用资源遵循系统规范什么是进程保活在Android系统中,进程保活是一种关键的优化策略,旨在确保应用程序即使在后台也能维持一定程度的活跃状态。这一技术的核心目标是提高用户体验,特别是在处理即时通讯或需要持续接收更新的应用场景下。通过巧妙的设计和实现,开发者
RK3568平台开发系列:Linux性能调试工具汇总与Android
技术猎手
linux android 服务器 Android
RK3568平台开发系列:Linux性能调试工具汇总与Android在RK3568平台的开发过程中,对于Linux性能调试工具的了解和掌握是非常重要的。本文将介绍一些常用的Linux性能调试工具以及在Android开发中的应用。top命令top命令可以实时地显示系统中各个进程的资源占用情况,包括CPU使用率、内存使用率等。通过top命令,可以快速了解系统当前的负载情况和各个进程的资源消耗情况。$t
android手机的分区,安卓系统分区介绍
孙佳纯
android手机的分区
本帖最后由刘向臻LXZ于2014-7-1114:32编辑安卓系统分区介绍:英文原文:AndroidPartitionsExplained:boot,system,recovery,data,cache&misc地址:安卓手机和平板一般包括以下标准内部分区:/boot/system/recovery/data/cache/misc另外还与SD卡分区:/sdcard/sd-ext现中英文对照如下:注意
通过传说中的某东QQ设置码,谈QQ支付系统安全性分析及防范措施
自学不成才
算法 安全 ios
背景分析最近在进行移动支付安全研究时,有人跟我说外面流传着一种qq设置码让我研究下,然后就发现了一个关于QQ支付的有趣技术案例。通过逆向工程和网络分析,我们发现了一些值得关注的安全隐患。技术发现逆向分析下单,付款,发现竟然没有qq支付。使用jadx-gui对Android应用进行反编译分析,然后我们在支付流程中发现了关键的tokenId生成机制。这些tokenId通常呈现以下特征:前缀模式:6M或
什么是手机的boot分区
王景程
智能手机 模块测试
什么是手机的Boot分区?Boot分区(BootPartition)是Android设备启动时最关键的分区,它包含了引导系统所需的核心文件,包括Linux内核(Kernel)和启动镜像(Ramdisk)。当你开机时,手机的Bootloader(引导加载程序)会先读取Boot分区的内容,然后加载操作系统。Boot分区的作用Boot分区的主要作用是:加载Linux内核:内核(Kernel)是Andro
eclipse maven
IXHONG
eclipse
eclipse中使用maven插件的时候,运行run as maven build的时候报错
-Dmaven.multiModuleProjectDirectory system propery is not set. Check $M2_HOME environment variable and mvn script match.
可以设一个环境变量M2_HOME指
timer cancel方法的一个小实例
alleni123
多线程 timer
package com.lj.timer;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class MyTimer extends TimerTask
{
private int a;
private Timer timer;
pub
MySQL数据库在Linux下的安装
ducklsl
mysql
1.建好一个专门放置MySQL的目录
/mysql/db数据库目录
/mysql/data数据库数据文件目录
2.配置用户,添加专门的MySQL管理用户
>groupadd mysql ----添加用户组
>useradd -g mysql mysql ----在mysql用户组中添加一个mysql用户
3.配置,生成并安装MySQL
>cmake -D
spring------>>cvc-elt.1: Cannot find the declaration of element
Array_06
spring bean
将--------
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3
maven发布第三方jar的一些问题
cugfy
maven
maven中发布 第三方jar到nexus仓库使用的是 deploy:deploy-file命令
有许多参数,具体可查看
http://maven.apache.org/plugins/maven-deploy-plugin/deploy-file-mojo.html
以下是一个例子:
mvn deploy:deploy-file -DgroupId=xpp3
MYSQL下载及安装
357029540
mysql
好久没有去安装过MYSQL,今天自己在安装完MYSQL过后用navicat for mysql去厕测试链接的时候出现了10061的问题,因为的的MYSQL是最新版本为5.6.24,所以下载的文件夹里没有my.ini文件,所以在网上找了很多方法还是没有找到怎么解决问题,最后看到了一篇百度经验里有这个的介绍,按照其步骤也完成了安装,在这里给大家分享下这个链接的地址
ios TableView cell的布局
张亚雄
tableview
cell.imageView.image = [UIImage imageNamed:[imageArray objectAtIndex:[indexPath row]]];
CGSize itemSize = CGSizeMake(60, 50);
&nbs
Java编码转义
adminjun
java 编码转义
import java.io.UnsupportedEncodingException;
/**
* 转换字符串的编码
*/
public class ChangeCharset {
/** 7位ASCII字符,也叫作ISO646-US、Unicode字符集的基本拉丁块 */
public static final Strin
Tomcat 配置和spring
aijuans
spring
简介
Tomcat启动时,先找系统变量CATALINA_BASE,如果没有,则找CATALINA_HOME。然后找这个变量所指的目录下的conf文件夹,从中读取配置文件。最重要的配置文件:server.xml 。要配置tomcat,基本上了解server.xml,context.xml和web.xml。
Server.xml -- tomcat主
Java打印当前目录下的所有子目录和文件
ayaoxinchao
递归 File
其实这个没啥技术含量,大湿们不要操笑哦,只是做一个简单的记录,简单用了一下递归算法。
import java.io.File;
/**
* @author Perlin
* @date 2014-6-30
*/
public class PrintDirectory {
public static void printDirectory(File f
linux安装mysql出现libs报冲突解决
BigBird2012
linux
linux安装mysql出现libs报冲突解决
安装mysql出现
file /usr/share/mysql/ukrainian/errmsg.sys from install of MySQL-server-5.5.33-1.linux2.6.i386 conflicts with file from package mysql-libs-5.1.61-4.el6.i686
jedis连接池使用实例
bijian1013
redis jedis连接池 jedis
实例代码:
package com.bijian.study;
import java.util.ArrayList;
import java.util.List;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoo
关于朋友
bingyingao
朋友 兴趣爱好 维持
成为朋友的必要条件:
志相同,道不合,可以成为朋友。譬如马云、周星驰一个是商人,一个是影星,可谓道不同,但都很有梦想,都要在各自领域里做到最好,当他们遇到一起,互相欣赏,可以畅谈两个小时。
志不同,道相合,也可以成为朋友。譬如有时候看到两个一个成绩很好每次考试争做第一,一个成绩很差的同学是好朋友。他们志向不相同,但他
【Spark七十九】Spark RDD API一
bit1129
spark
aggregate
package spark.examples.rddapi
import org.apache.spark.{SparkConf, SparkContext}
//测试RDD的aggregate方法
object AggregateTest {
def main(args: Array[String]) {
val conf = new Spar
ktap 0.1 released
bookjovi
kernel tracing
Dear,
I'm pleased to announce that ktap release v0.1, this is the first official
release of ktap project, it is expected that this release is not fully
functional or very stable and we welcome bu
能保存Properties文件注释的Properties工具类
BrokenDreams
properties
今天遇到一个小需求:由于java.util.Properties读取属性文件时会忽略注释,当写回去的时候,注释都没了。恰好一个项目中的配置文件会在部署后被某个Java程序修改一下,但修改了之后注释全没了,可能会给以后的参数调整带来困难。所以要解决这个问题。
&nb
读《研磨设计模式》-代码笔记-外观模式-Facade
bylijinnan
java 设计模式
声明: 本文只为方便我个人查阅和理解,详细的分析以及源代码请移步 原作者的博客http://chjavach.iteye.com/
/*
* 百度百科的定义:
* Facade(外观)模式为子系统中的各类(或结构与方法)提供一个简明一致的界面,
* 隐藏子系统的复杂性,使子系统更加容易使用。他是为子系统中的一组接口所提供的一个一致的界面
*
* 可简单地
After Effects教程收集
cherishLC
After Effects
1、中文入门
http://study.163.com/course/courseMain.htm?courseId=730009
2、videocopilot英文入门教程(中文字幕)
http://www.youku.com/playlist_show/id_17893193.html
英文原址:
http://www.videocopilot.net/basic/
素
Linux Apache 安装过程
crabdave
apache
Linux Apache 安装过程
下载新版本:
apr-1.4.2.tar.gz(下载网站:http://apr.apache.org/download.cgi)
apr-util-1.3.9.tar.gz(下载网站:http://apr.apache.org/download.cgi)
httpd-2.2.15.tar.gz(下载网站:http://httpd.apac
Shell学习 之 变量赋值和引用
daizj
shell 变量引用 赋值
本文转自:http://www.cnblogs.com/papam/articles/1548679.html
Shell编程中,使用变量无需事先声明,同时变量名的命名须遵循如下规则:
首个字符必须为字母(a-z,A-Z)
中间不能有空格,可以使用下划线(_)
不能使用标点符号
不能使用bash里的关键字(可用help命令查看保留关键字)
需要给变量赋值时,可以这么写:
Java SE 第一讲(Java SE入门、JDK的下载与安装、第一个Java程序、Java程序的编译与执行)
dcj3sjt126com
java jdk
Java SE 第一讲:
Java SE:Java Standard Edition
Java ME: Java Mobile Edition
Java EE:Java Enterprise Edition
Java是由Sun公司推出的(今年初被Oracle公司收购)。
收购价格:74亿美金
J2SE、J2ME、J2EE
JDK:Java Development
YII给用户登录加上验证码
dcj3sjt126com
yii
1、在SiteController中添加如下代码:
/**
* Declares class-based actions.
*/
public function actions() {
return array(
// captcha action renders the CAPTCHA image displ
Lucene使用说明
dyy_gusi
Lucene search 分词器
Lucene使用说明
1、lucene简介
1.1、什么是lucene
Lucene是一个全文搜索框架,而不是应用产品。因此它并不像baidu或者googleDesktop那种拿来就能用,它只是提供了一种工具让你能实现这些产品和功能。
1.2、lucene能做什么
要回答这个问题,先要了解lucene的本质。实际
学习编程并不难,做到以下几点即可!
gcq511120594
数据结构 编程 算法
不论你是想自己设计游戏,还是开发iPhone或安卓手机上的应用,还是仅仅为了娱乐,学习编程语言都是一条必经之路。编程语言种类繁多,用途各 异,然而一旦掌握其中之一,其他的也就迎刃而解。作为初学者,你可能要先从Java或HTML开始学,一旦掌握了一门编程语言,你就发挥无穷的想象,开发 各种神奇的软件啦。
1、确定目标
学习编程语言既充满乐趣,又充满挑战。有些花费多年时间学习一门编程语言的大学生到
Java面试十问之三:Java与C++内存回收机制的差别
HNUlanwei
java C++ finalize() 堆栈 内存回收
大家知道, Java 除了那 8 种基本类型以外,其他都是对象类型(又称为引用类型)的数据。 JVM 会把程序创建的对象存放在堆空间中,那什么又是堆空间呢?其实,堆( Heap)是一个运行时的数据存储区,从它可以分配大小各异的空间。一般,运行时的数据存储区有堆( Heap)和堆栈( Stack),所以要先看它们里面可以分配哪些类型的对象实体,然后才知道如何均衡使用这两种存储区。一般来说,栈中存放的
第二章 Nginx+Lua开发入门
jinnianshilongnian
nginx lua
Nginx入门
本文目的是学习Nginx+Lua开发,对于Nginx基本知识可以参考如下文章:
nginx启动、关闭、重启
http://www.cnblogs.com/derekchen/archive/2011/02/17/1957209.html
agentzh 的 Nginx 教程
http://openresty.org/download/agentzh-nginx-tutor
MongoDB windows安装 基本命令
liyonghui160com
windows安装
安装目录:
D:\MongoDB\
新建目录
D:\MongoDB\data\db
4.启动进城:
cd D:\MongoDB\bin
mongod -dbpath D:\MongoDB\data\db
&n
Linux下通过源码编译安装程序
pda158
linux
一、程序的组成部分 Linux下程序大都是由以下几部分组成: 二进制文件:也就是可以运行的程序文件 库文件:就是通常我们见到的lib目录下的文件 配置文件:这个不必多说,都知道 帮助文档:通常是我们在linux下用man命令查看的命令的文档
二、linux下程序的存放目录 linux程序的存放目录大致有三个地方: /etc, /b
WEB开发编程的职业生涯4个阶段
shw3588
编程 Web 工作 生活
觉得自己什么都会
2007年从学校毕业,凭借自己原创的ASP毕业设计,以为自己很厉害似的,信心满满去东莞找工作,找面试成功率确实很高,只是工资不高,但依旧无法磨灭那过分的自信,那时候什么考勤系统、什么OA系统、什么ERP,什么都觉得有信心,这样的生涯大概持续了约一年。
根本不是自己想的那样
2008年开始接触很多工作相关的东西,发现太多东西自己根本不会,都需要去学,不管是asp还是js,
遭遇jsonp同域下变作post请求的坑
vb2005xu
jsonp 同域post
今天迁移一个站点时遇到一个坑爹问题,同一个jsonp接口在跨域时都能调用成功,但是在同域下调用虽然成功,但是数据却有问题. 此处贴出我的后端代码片段
$mi_id = htmlspecialchars(trim($_GET['mi_id ']));
$mi_cv = htmlspecialchars(trim($_GET['mi_cv ']));
贴出我前端代码片段:
$.aj