Android源码解析-EventBus

本篇主要介绍eventbus的使用以及源码分析

####Eventbus是什么

  1. 专门为Android设计的用于订阅,发布总线的库
  2. andorid组件之间通信
  3. 。。。

现有的通信方式

同一进程线程间:
  1. handler
  2. 共享静态变量
  3. eventbus
进程间:
  1. aidl
  2.  socket
  3. 广播
  4. contentProvider
  5. Messager

以上aidl跟广播也可以在同一个进程中使用,但一般同一个进程通信的话不太会用到

####为什么使用eventbus

相比于handler,handler通信是单向的,没有handler的线程可以向有handler的一方发消息。常见的是子线程d可以给主线程发消息(子线程做完耗时操作之后通知主线程修改ui)。如果主线程要给子线程发消息的话,需要子线程new looper,实现handler,并让主线程持有该handler,推荐直接使用HandlerThread(包含looper的thread)。相对来说代码量不小。

共享静态变量这种方法耗内存不推荐。

eventBus中子线程传递消息给主线程实质使用的还是handler,但是它比使用handler的方式简单很多。下面会给出详细分析。

使用方法

  1. 定义一个pojo:message的实体类
  2. 注册接受消息的类并在在该类中定义接收消息的方法
  3. 发送消息
  4. 合适的地方取消注册

示例:

实体类:

1
2
3
4
5
6
7
8
9
package com.example.kj_eventbus;

public class EventMessage {
public String name;

public EventMessage(String name) {
this.name = name;
}
}

接收消息的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MainActivity extends AppCompatActivity {

...
//接收消息的方法
@Subscribe(threadMode = ThreadMode.MAIN)
public void eventBus(EventMessage msg) {
Toast.makeText(this, msg.name, Toast.LENGTH_SHORT).show();
}

public void jump(View view) {
//注册接收消息的类
EventBus.getDefault().register(this);
startActivity(new Intent(this, SendMsgAct.class));
}

@Override
protected void onDestroy() {
super.onDestroy();
//取消注册
EventBus.getDefault().unregister(this);
}
}

发送消息:

1
2
3
4
5
6
7
8
9
10
11
12
public class SendMsgAct extends AppCompatActivity {
...
public void send(View view) {
new Thread(new Runnable() {
@Override
public void run() {
//发送消息
EventBus.getDefault().post(new EventMessage("你好朋羽毛!!"));
}
}).start();
}
}

源码解析

注解的使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
//接收消息的线程
ThreadMode threadMode() default ThreadMode.POSTING;
//是否是粘性消息
boolean sticky() default false;
//优先级
int priority() default 0;
}


public enum ThreadMode {
POSTING,
MAIN,
MAIN_ORDERED,
BACKGROUND,
ASYNC
}
  • 该注解运行时生效,用于注解方法,标示接收消息的线程,消息的优先级,以及是否是粘性的消息
  • 利用该注解可以在运行时通过反射获取到(类—方法—MessageType)对应的关系;
  • 具体代码后面会给出分析
初始化

这里使用了单例建造者模式构建evenbus实例。但是为什么构造函数是public的呢?有知道的同学可以告我一哈~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//单例 
static volatile EventBus defaultInstance;

public static EventBus getDefault() {
if (defaultInstance == null) {
synchronized (EventBus.class) {
if (defaultInstance == null) {
defaultInstance = new EventBus();
}
}
}
return defaultInstance;
}

public EventBus() {
this(DEFAULT_BUILDER);
}
//建造者(将构造参数分离了出来)
EventBus(EventBusBuilder builder) {
...
sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
throwSubscriberException = builder.throwSubscriberException;
eventInheritance = builder.eventInheritance;
executorService = builder.executorService;
}
public static EventBusBuilder builder() {
return new EventBusBuilder();
}
注册类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass();
//1
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
//2
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}

//接收消息的方法的封装实体类
public class SubscriberMethod {
final Method method;//方法
final ThreadMode threadMode;
final Class<?> eventType;//MessagedType
final int priority;
final boolean sticky;
/** Used for efficient comparison */
String methodString;
}

//类跟对应方法的实体类
class Subscription {
final Object subscriber;//注册类
final SubscriberMethod subscriberMethod;
}

该方法是注册入口,主要两步:

  1. 首先通过findSubscriberMethods找到该类的SubscirberMethod(接收消息的方法的封装)集合;
  2. 之后调用subscribe初始化subscriptionsByEventType(通过messageType找到subsciption)typesBySubscriber(通过注册类找到MessageType)

下面我们先跟踪下findSubscriberMethods:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
//从cache中获取
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
//ignoreGeneratedIndex如果为true,那么使用apt获取subscriberClass中接收消息的方法集合
//为false,那么通过反射获取
if (ignoreGeneratedIndex) {
subscriberMethods = findUsingReflection(subscriberClass);
} else {
subscriberMethods = findUsingInfo(subscriberClass);
}
if (subscriberMethods.isEmpty()) {
...
} else {
//放入cache
METHOD_CACHE.put(subscriberClass, subscriberMethods);
return subscriberMethods;
}
}

该方法先从cache中获取,之后通过apt或者反射获取。我们先分析反射(默认),也就是findUsingInfo()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private List<SubscriberMethod> 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);
}

之后进入findUsingReflectionInSingleClass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
...
methods = findState.clazz.getDeclaredMethods();
...
for (Method method : methods) {
////获取方法修饰符(public private static,,
//方法必须是publit not-static not-abstract
int modifiers = method.getModifiers();
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length == 1) {
//获取方法注解
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
if (subscribeAnnotation != null) {
//获取MesageType
Class<?> eventType = parameterTypes[0];
if (findState.checkAdd(method, eventType)) {
//获取注解的值,将method,MessageType,ThreadMode,Prority,Sticky关联起来
ThreadMode threadMode = subscribeAnnotation.threadMode();
findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
}
}
}
...
}
}
...
}
}

之后会调用getMethodsAndRelease

1
2
3
4
5
6
7
8
9
10
11
12
13
private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {
List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods);
findState.recycle();
synchronized (FIND_STATE_POOL) {
for (int i = 0; i < POOL_SIZE; i++) {
if (FIND_STATE_POOL[i] == null) {
FIND_STATE_POOL[i] = findState;
break;
}
}
}
return subscriberMethods;
}

最终返回的是该注册类中接收消息的方法集合。

接着我们进入第二步,subscribe:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class<?> eventType = subscriberMethod.eventType;
//1:初始化subscriptionsByEventType
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
//异步可以同时读写的list:CopyOnWriteArrayList
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
...
}

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;
}
}
//2:初始化typesBySubscriber
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);

if (subscriberMethod.sticky) {
...
}
}

以上,regist分析完毕。

发送消息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public void post(Object event) {
//将消息放入postingState的queue中
PostingThreadState postingState = currentPostingThreadState.get();
List<Object> eventQueue = postingState.eventQueue;
eventQueue.add(event);

if (!postingState.isPosting) {
postingState.isMainThread = isMainThread();
postingState.isPosting = true;
...
try {
while (!eventQueue.isEmpty()) {
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}
//其中的currentPostingThreadState是ThreadLocal(以线程为单位,存放变量,也就是同一个变量在不同线程中是不一样的)
private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
@Override
protected PostingThreadState initialValue() {
return new PostingThreadState();
}
};

//PostingThreadState
final static class PostingThreadState {
final List<Object> eventQueue = new ArrayList<>();
boolean isPosting;
boolean isMainThread;
Subscription subscription;
Object event;
boolean canceled;
}

其中的isMainThread()要从mainThreadSupport的初始化说起:

点击进入isMainThread()

1
2
3
private boolean isMainThread() {
return mainThreadSupport != null ? mainThreadSupport.isMainThread() : true;
}

可见是根据mainThreadSupport来判断的,接着我们找到mainThreadSupport初始化的地方

1
2
3
4
5
6
7
8
EventBus(EventBusBuilder builder) {
...
mainThreadSupport = builder.getMainThreadSupport();
mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
backgroundPoster = new BackgroundPoster(this);
asyncPoster = new AsyncPoster(this);
...
}

进入getMainThreadSupport

1
2
3
4
5
6
7
8
9
10
11
MainThreadSupport getMainThreadSupport() {
if (mainThreadSupport != null) {
return mainThreadSupport;
} else if (Logger.AndroidLogger.isAndroidLogAvailable()) {
Object looperOrNull = getAndroidMainLooperOrNull();
return looperOrNull == null ? null :
new MainThreadSupport.AndroidHandlerMainThreadSupport((Looper) looperOrNull);
} else {
return null;
}
}

我们看下Logger.AndroidLogger.isAndroidLogAvailable()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static class AndroidLogger implements Logger {
static final boolean ANDROID_LOG_AVAILABLE;

static {
boolean android = false;
try {
android = Class.forName("android.util.Log") != null;
} catch (ClassNotFoundException e) {
// OK
}
ANDROID_LOG_AVAILABLE = android;
}

public static boolean isAndroidLogAvailable() {
return ANDROID_LOG_AVAILABLE;
}
}

返回的是ANDROID_LOG_AVAILABLE,这个值如果存在“android.util.Log”,那么为true,否则为false,意思就是如果是运行在android中,那么为true,否则为false;

接着我们看下getAndroidMainLooperOrNull();

1
2
3
4
5
Object getAndroidMainLooperOrNull() {
...
return Looper.getMainLooper();
...
}

返回的是主线程的looper,一定不为空,

那么getMainThreadSupport的返回值就是 MainThreadSupport.AndroidHandlerMainThreadSupport((Looper) looperOrNull)

接着我们看下AndroidHandlerMainThreadSupport

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class AndroidHandlerMainThreadSupport implements MainThreadSupport {

private final Looper looper;

public AndroidHandlerMainThreadSupport(Looper looper) {
this.looper = looper;
}

@Override
public boolean isMainThread() {
return looper == Looper.myLooper();
}
...
}

其实就是把AndroidHandlerMainThreadSupport中的looper初始化为主线程的looper。

此时我们在看isMainThread()。如果Looper.myLooper返回的跟主线程的looper一致,那么自然就是主线程了。因为列子中是在子线程中发的消息,所以此时返回false。我们回到post方法中:

postingState的成员变量eventQueue中有我们发送的消息,isMainThread=false;

接着只要eventQueue不为空,就执行postSingleEvent(eventQueue.remove(0), postingState);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
Class<?> eventClass = event.getClass();
boolean subscriptionFound = false;
//是否算父类,默认是true
if (eventInheritance) {
List<Class<?>> 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) {
logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
}
if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
eventClass != SubscriberExceptionEvent.class) {
post(new NoSubscriberEvent(this, event));
}
}
}

那么接着我们进入lookupAllEventTypes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static List<Class<?>> lookupAllEventTypes(Class<?> eventClass) {
synchronized (eventTypesCache) {
List<Class<?>> eventTypes = eventTypesCache.get(eventClass);
if (eventTypes == null) {
eventTypes = new ArrayList<>();
Class<?> clazz = eventClass;
while (clazz != null) {
eventTypes.add(clazz);
addInterfaces(eventTypes, clazz.getInterfaces());
clazz = clazz.getSuperclass();
}
eventTypesCache.put(eventClass, eventTypes);
}
return eventTypes;
}
}

就是返回MessageType以及它的父类的List

返回之后接着执行postSingleEventForEventType(event, postingState, clazz)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
CopyOnWriteArrayList<Subscription> 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;
}

这里通过subscriptionsByEventType找到该MessageType对应的subscriptions,将它赋给postingState.subscription,将该MessageType赋给postingState.event,之后调用postToSubscription(subscription, event, postingState.isMainThread);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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 MAIN_ORDERED:
if (mainThreadPoster != null) {
mainThreadPoster.enqueue(subscription, event);
} else {
// temporary: technically not correct as poster not decoupled from subscriber
invokeSubscriber(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);
}
}

例子中threadMode是Main,发送消息是在子线程中,接下来会执行mainThreadPoster.enqueue(subscription, event)

这里就要从mainThreadPoster的初始化说起(Eventbus构造方法中):

1
mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;

mainThreadSupport.createPoster(this)

1
2
3
4
5
6
7
8
class AndroidHandlerMainThreadSupport implements MainThreadSupport {

...
@Override
public Poster createPoster(EventBus eventBus) {
return new HandlerPoster(eventBus, looper, 10);
}
}

我们上面提到过这里的looper是主线程的looper。我们接着进入new HandlerPoster(eventBus, looper, 10)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public class HandlerPoster extends Handler implements Poster {

private final PendingPostQueue queue;
private final int maxMillisInsideHandleMessage;
private final EventBus eventBus;
private boolean handlerActive;

protected HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) {
super(looper);
this.eventBus = eventBus;
this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage;
queue = new PendingPostQueue();
}

public void enqueue(Subscription subscription, Object event) {
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
synchronized (this) {
queue.enqueue(pendingPost);
if (!handlerActive) {
handlerActive = true;
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message");
}
}
}
}

@Override
public void handleMessage(Message msg) {
boolean rescheduled = false;
try {
long started = SystemClock.uptimeMillis();
while (true) {
PendingPost pendingPost = queue.poll();
if (pendingPost == null) {
synchronized (this) {
// Check again, this time in synchronized
pendingPost = queue.poll();
if (pendingPost == null) {
handlerActive = false;
return;
}
}
}
eventBus.invokeSubscriber(pendingPost);
long timeInMethod = SystemClock.uptimeMillis() - started;
if (timeInMethod >= maxMillisInsideHandleMessage) {
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message");
}
rescheduled = true;
return;
}
}
} finally {
handlerActive = rescheduled;
}
}
}

HandlerPoster是EventBus中比较重要的类,它继承自handler,因为我们传入的looper是主线程的,所以该handler是主线程的handler。

此时回到我们的初衷:mainThreadPoster.enqueue(subscription, event);

这里会将subscription跟event转化为PendingPost,pendingpost是用来做消息复用的,具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
static PendingPost obtainPendingPost(Subscription subscription, Object event) {
synchronized (pendingPostPool) {
int size = pendingPostPool.size();
if (size > 0) {
PendingPost pendingPost = pendingPostPool.remove(size - 1);
pendingPost.event = event;
pendingPost.subscription = subscription;
pendingPost.next = null;
return pendingPost;
}
}
return new PendingPost(event, subscription);
}

核心思路就是如果pendingPostPool这个池子里有,那么就从池子里拿,没有才会new 对象。拿到PendingPost之后,将它放入PendingPostQueue队列中。之后执行sendMessage(obtainMessage())发送一个空消息。

接着我们进入handleMessage。改方法会从PendingPostQueue队列中不断的poll数据,知道队列为空。每个数据都会执行eventBus.invokeSubscriber(pendingPost);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void invokeSubscriber(PendingPost pendingPost) {
Object event = pendingPost.event;
Subscription subscription = pendingPost.subscription;
PendingPost.releasePendingPost(pendingPost);
if (subscription.active) {
invokeSubscriber(subscription, event);
}
}

void invokeSubscriber(Subscription subscription, Object event) {
...
subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
...
}

改方法就是通过反射调用接收消息的方法。

以上,完成了子线程给主线程传递消息的整个过程。

如果接收消息也是子线程(也就是threadMode设置的是backGround),如果发送消息在主线程,那么会调用backgroundPoster.enqueue(subscription, event);将线程切换到子线程中,否者直接反射调用即可。最后我们看下backgroundPoster的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
final class BackgroundPoster implements Runnable, Poster {

private final PendingPostQueue queue;
private final EventBus eventBus;

private volatile boolean executorRunning;

BackgroundPoster(EventBus eventBus) {
this.eventBus = eventBus;
queue = new PendingPostQueue();
}

public void enqueue(Subscription subscription, Object event) {
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
synchronized (this) {
queue.enqueue(pendingPost);
if (!executorRunning) {
executorRunning = true;
eventBus.getExecutorService().execute(this);
}
}
}

@Override
public void run() {
try {
try {
while (true) {
PendingPost pendingPost = queue.poll(1000);
if (pendingPost == null) {
synchronized (this) {
// Check again, this time in synchronized
pendingPost = queue.poll();
if (pendingPost == null) {
executorRunning = false;
return;
}
}
}
eventBus.invokeSubscriber(pendingPost);
}
} catch (InterruptedException e) {
eventBus.getLogger().log(Level.WARNING, Thread.currentThread().getName() + " was interruppted", e);
}
} finally {
executorRunning = false;
}
}

}

它本身是一个runnable,维护着一个队列,入队列之后执行eventBus.getExecutorService().execute(this)把自己放入线程池中(cache线程池),此时就会执行run方法,而run方法会不断的从队列中poll数据,之后反射执行。

反射优化:apt

运行时使用反射对会有一定的性能损耗,使用apt(注解工具)通过编译时生成对应的代码,从而规避了运行时反射。

总结

实质:当发送某个消息时执行特定类的特定方法

原理:其实就是利用注解跟反射通过注册类的方式将(类—方法–消息类型)对应的关系表维护在Eventbus中,当post某个消息时查询该关系表,通过反射执行指定类的指定方法,切换线程使用handler跟线程池。

最后:evenbus中相关的知识点有很多:设计模式,线程池,handler,ThreadLocal,反射,注解,多线程…源码还是推荐大家简单看一下。

leetcode-组合总和 Ⅳ

给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
nums = [1, 2, 3]
target = 4

所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)

请注意,顺序不同的序列被视作不同的组合。

因此输出为 7。

进阶:
如果给定的数组中含有负数会怎么样?
问题会产生什么变化?
我们需要在题目中添加什么限制来允许负数的出现?

思路

使用dp,假设dp[i]表示组成i的组合数,那么以上面示例为例,以1开头,需要dp[3],以2开头,需要dp[2],以3开头,需要dp[1];

公式如下:

dp[target]=sum(dp[target-nums[i]]); i:0~~nums.length-1;

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public int combinationSum4(int[] nums, int target) {
//dp[target]=sum(dp[target-nums[i]]); i:0~~nums.length-1;
int [] dp=new int[target+1];

for(int i =0;i<dp.length;i++){
dp[i]=0;
}

dp[0]=1;

for(int i =1;i<dp.length;i++){
for(int j =0;j<nums.length;j++){
if(i>=nums[j]){
dp[i]+=dp[i-nums[j]];
}
}
System.out.println(dp[i]);
}

return dp[target];
}
}

Android - greenDao

导包

  1. rootProject中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    buildscript {

    repositories {
    google()
    jcenter()
    mavenCentral() // greenDao 111111111111
    }
    dependencies {
    classpath 'com.android.tools.build:gradle:3.1.2'

    classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2' // greenDao 2222222
    // NOTE: Do not place your application dependencies here; they belong
    // in the individual module build.gradle files
    }
    }

    allprojects {
    repositories {
    google()
    jcenter()
    maven { url "https://jitpack.io" }//greenDao update 333333333
    }
    }

    task clean(type: Delete) {
    delete rootProject.buildDir
    }
  2. project的build.gradle

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    apply plugin: 'com.android.application'
    apply plugin: 'org.greenrobot.greendao'//greenDao 111111111

    android {
    compileSdkVersion 28
    defaultConfig {
    applicationId "com.example.kj_greendao"
    minSdkVersion 15
    targetSdkVersion 28
    versionCode 1
    versionName "1.0"
    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
    release {
    minifyEnabled false
    proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
    }
    }
    //grendao 2222222222
    greendao {
    schemaVersion 7 //数据库版本号
    daoPackage 'com.example.kj_greendao.gen'//指定生成的Daomaster ,daosession,xxDao的位置
    targetGenDir 'src/main/java'
    }

    dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation 'com.android.support:appcompat-v7:28.0.0-rc01'
    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    implementation 'org.greenrobot:greendao:3.2.2'//greendao 3333333333333
    api 'com.github.yuweiguocn:GreenDaoUpgradeHelper:v2.1.0'//greeDaoUpDate 44444444444
    }

使用

  1. 定义Bean,之后build自动生成如下代码,同时生成了三个dao相关类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    package com.example.kj_greendao;

    import org.greenrobot.greendao.annotation.Entity;
    import org.greenrobot.greendao.annotation.Id;
    import org.greenrobot.greendao.annotation.Generated;

    @Entity
    public class StudentBean {
    //注意此处必须是Long,而不是long
    @Id
    private Long id;

    private String name;

    private int age;

    private boolean sex;

    private String test;

    @Override
    public String toString() {
    return "StudentBean{" +
    "id=" + id +
    ", name='" + name + '\'' +
    ", age=" + age +
    ", sex=" + sex +
    ", test='" + test + '\'' +
    '}';
    }

    @Generated(hash = 1767927902)
    public StudentBean(Long id, String name, int age, boolean sex, String test) {
    this.id = id;
    this.name = name;
    this.age = age;
    this.sex = sex;
    this.test = test;
    }

    @Generated(hash = 2097171990)
    public StudentBean() {
    }

    public Long getId() {
    return this.id;
    }

    public void setId(Long id) {
    this.id = id;
    }

    public String getName() {
    return this.name;
    }

    public void setName(String name) {
    this.name = name;
    }

    public int getAge() {
    return this.age;
    }

    public void setAge(int age) {
    this.age = age;
    }

    public boolean getSex() {
    return this.sex;
    }

    public void setSex(boolean sex) {
    this.sex = sex;
    }

    public String getTest() {
    return this.test;
    }

    public void setTest(String test) {
    this.test = test;
    }
    }
  1. 自定义OpenHelper 继承自DaoMaster.OpenHelper,重写onUpgrade方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    package com.example.kj_greendao;

    import android.content.Context;
    import android.database.sqlite.SQLiteDatabase;

    import com.example.kj_greendao.gen.DaoMaster;
    import com.example.kj_greendao.gen.StudentBeanDao;
    import com.github.yuweiguocn.library.greendao.MigrationHelper;

    import org.greenrobot.greendao.database.Database;

    public class GreenDaoOpenHelper extends DaoMaster.OpenHelper {
    public GreenDaoOpenHelper(Context context, String name) {
    super(context, name);
    }

    public GreenDaoOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
    super(context, name, factory);
    }

    @Override
    public void onUpgrade(Database db, int oldVersion, int newVersion) {
    MigrationHelper.migrate(db, new MigrationHelper.ReCreateAllTableListener() {

    @Override
    public void onCreateAllTables(Database db, boolean ifNotExists) {
    DaoMaster.createAllTables(db, ifNotExists);
    }

    @Override
    public void onDropAllTables(Database db, boolean ifExists) {
    DaoMaster.dropAllTables(db, ifExists);
    }
    },StudentBeanDao.class);
    }
    }
  1. 初始化以及增删改查

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    package com.example.kj_greendao;

    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.TextView;

    import com.example.kj_greendao.gen.DaoMaster;
    import com.example.kj_greendao.gen.DaoSession;
    import com.example.kj_greendao.gen.StudentBeanDao;

    import org.greenrobot.greendao.database.Database;
    import org.greenrobot.greendao.query.Query;

    import java.util.List;

    public class GreendaoHomeAct extends AppCompatActivity {

    private TextView mTv;

    private Query<StudentBean> mStudentAllItemBuild;
    private long id = 1;
    private StudentBeanDao mStudentBeanDao;

    @Override
    protected void onCreate(final Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_greendao_home);
    mTv = (TextView) this.findViewById(R.id.tv);
    (findViewById(R.id.bt_init)).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    GreenDaoOpenHelper greenDaoOpenHelper = new GreenDaoOpenHelper(GreendaoHomeAct.this, "kbjay-db");
    Database db = greenDaoOpenHelper.getWritableDb();
    DaoSession daoSession = new DaoMaster(db).newSession();
    mStudentBeanDao = daoSession.getStudentBeanDao();
    mStudentAllItemBuild = mStudentBeanDao.queryBuilder().build();
    print();
    }
    });
    (findViewById(R.id.bt_add)).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    StudentBean wzq = new StudentBean();
    wzq.setAge(10);
    wzq.setName("wzq"+(id++));
    mStudentBeanDao.insert(wzq);
    print();
    }
    });
    (findViewById(R.id.bt_change)).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    if (mStudentAllItemBuild.list() != null && mStudentAllItemBuild.list().size() > 0) {
    StudentBean studentBean = mStudentAllItemBuild.list().get(0);
    studentBean.setAge(18);
    mStudentBeanDao.update(studentBean);
    }
    print();
    }
    });
    (findViewById(R.id.bt_delete)).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    if (mStudentAllItemBuild.list() != null && mStudentAllItemBuild.list().size() > 0) {
    StudentBean studentBean = mStudentAllItemBuild.list().get(0);
    mStudentBeanDao.delete(studentBean);
    }
    print();
    }
    });
    (findViewById(R.id.bt_getAll)).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    print();
    }
    });

    (findViewById(R.id.bt_search)).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    List<StudentBean> list = mStudentBeanDao.queryBuilder().where(StudentBeanDao.Properties.Age.eq(18))
    .build().list();

    if (list != null && list.size() > 0) {
    StringBuffer sb = new StringBuffer();
    for (int i = 0; i < list.size(); i++) {
    sb.append(list.get(i).toString() + "\r\n");
    }
    mTv.setText(sb.toString());
    }else{
    mTv.setText("没有18岁的人!!!!");
    }
    }
    });
    (findViewById(R.id.bt_deletAll)).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    mStudentBeanDao.deleteAll();
    print();
    }
    });
    (findViewById(R.id.bt_update)).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {

    }
    });
    }

    public void print() {
    List<StudentBean> list = mStudentAllItemBuild.list();
    if (list != null && list.size() > 0) {
    StringBuffer sb = new StringBuffer();
    for (int i = 0; i < list.size(); i++) {
    sb.append(list.get(i).toString() + "\r\n");
    }
    mTv.setText(sb.toString());
    }else{
    mTv.setText("");
    }
    }
    }
  1. layout文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".GreendaoHomeAct">

    <Button
    android:id="@+id/bt_init"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="初始化" />

    <Button
    android:id="@+id/bt_add"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="增加" />

    <Button
    android:id="@+id/bt_delete"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="删除" />

    <Button
    android:id="@+id/bt_change"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="修改" />

    <Button
    android:id="@+id/bt_search"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="查询" />

    <Button
    android:id="@+id/bt_getAll"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="获取全部" />

    <Button
    android:id="@+id/bt_deletAll"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="清空表" />
    <Button
    android:id="@+id/bt_update"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="数据库升级" />

    <TextView
    android:id="@+id/tv"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#4a4a4a"
    android:textColor="#fff" />
    </LinearLayout>

升级

核心思路:

  1. 建立一个临时表(由原表copy一份)

  2. 删除旧表

  3. 建立新表

  4. 将临时表的数据迁移到新表

测试:

  1. 修改数据库版本号
  2. bean中添加新字段,重新运行即可

关于greenDao

  1. DevOpenHelper:创建SQLite数据库的SQLiteOpenHelper的具体实现
  2. DaoMaster:GreenDao的顶级对象,作为数据库对象、用于创建表和删除表
  3. DaoSession:管理所有的Dao对象,Dao对象中存在着增删改查等API

数据库版本升级:https://github.com/yuweiguocn/GreenDaoUpgradeHelper

https://stackoverflow.com/questions/13373170/greendao-schema-update-and-data-migration/30334668#30334668

使用参考:https://github.com/greenrobot/greenDAO

http://greenrobot.org/files/greendao/javadoc/current/

Api文档:http://greenrobot.org/files/greendao/javadoc/current/

Android-音频相关开发

问题

  1. 如何实现录音,并获取pcm数据
  2. 如何上传(要求用二进制流的content-type上传pcm数据):Content-Type: application/octet-stream
  3. 获取到服务器的音频信息之后如何播放(音频可能有两种,一种url,一种pcm数据流)

实现录音,存成pcm数据

有两个选择:

  • medioRecord
  • audioRecord

区别:

  • medioRecord相对audioRecord更加上层一点,底层使用的是audioRecord
  • medioRecord会把对音频数据做一些编码相关的处理,可以通过它获取到可以直接播放的音频文件,具体格式参见tip1,而audioRecord获取到的是pcm格式数据,该格式数据不能直接播放
  • audioRecord具有实时性

结论:

​ 因为录制的音频要上传给服务端,而且服务端要求pcm流数据,所有选择audioRecord实现。

注意

客户端跟服务端的pcm数据格式一定要一致(采样率,声道等)(坑了我好久。。。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package com.example.mylibrary.audio;

import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Environment;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

/**
* pcm录音utils
* @anthor kb_jay
* create at 2018/8/21 上午10:37
*/
public class AudioRecordUtil {
private static AudioRecordUtil mInstance;
private AudioRecord recorder;
//声音源
private static int audioSource = MediaRecorder.AudioSource.MIC;
//录音的采样频率
private static int audioRate = 16000;
//录音的声道,单声道
private static int audioChannel = AudioFormat.CHANNEL_IN_DEFAULT;
//量化的精度
private static int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
//缓存的大小
private static int bufferSize = AudioRecord.getMinBufferSize(audioRate, audioChannel, audioFormat);
//记录播放状态
private boolean isRecording = false;
//数字信号数组
private byte[] noteArray;
//PCM文件
private File pcmFile;
//wav文件
private File wavFile;
//文件输出流
private OutputStream os;
//文件根目录
private String basePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/xiaoIceStory/";

//pcm文件目录
private String inFileName = basePath + "/encode.pcm";

public String getPcmFilePath() {
return inFileName;
}

private AudioRecordUtil() {
//创建文件
createFile();
}

//创建文件夹,首先创建目录,然后创建对应的文件
private void createFile() {
File baseFile = new File(basePath);
if (!baseFile.exists()) {
baseFile.mkdirs();
}
pcmFile = new File(basePath + "/encode.pcm");

if (pcmFile.exists()) {
pcmFile.delete();
}
try {
pcmFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}

public static AudioRecordUtil getInstance() {
if (mInstance == null) {
synchronized (AudioRecordUtil.class){
if(mInstance==null){
mInstance = new AudioRecordUtil();
}
}
}
return mInstance;
}

//读取录音数字数据线程
class WriteThread implements Runnable {
@Override
public void run() {
writeData();
}
}

//录音线程执行体
private void writeData() {
noteArray = new byte[bufferSize];
//建立文件输出流
try {
os = new BufferedOutputStream(new FileOutputStream(pcmFile));
} catch (FileNotFoundException e) {
e.printStackTrace();
}

while (isRecording) {
int recordSize = recorder.read(noteArray, 0, bufferSize);
if (recordSize > 0) {
try {
os.write(noteArray);
} catch (IOException e) {
e.printStackTrace();
}
}
}

if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

//开始录音
public void startRecord() {
isRecording = true;
recorder = new AudioRecord(audioSource, audioRate,
audioChannel, audioFormat, bufferSize);
recorder.startRecording();
}

//记录数据
public void recordData() {
new Thread(new WriteThread()).start();
}

//停止录音
public void stopRecord() {
if (recorder != null) {
isRecording = false;
recorder.stop();
recorder.release();

}
}
}

参考:

https://developer.android.com/reference/android/media/MediaRecorder

https://developer.android.com/guide/topics/media/mediarecorder

https://www.jianshu.com/p/90c4071c7768

http://www.cnblogs.com/renhui/p/7463287.html

实现上传音频pcm数据流

  • content-type用来指定数据格式,比如multipart/form-data,application/json,application/octet-stream(二进制流),application/text等。详细参见http://www.runoob.com/http/http-content-type.html
  • okhttp中提供了设置content-type的方法:
1
2
3
4
/** Returns a new request body that transmits {@code content}. */
public static RequestBody create(final @Nullable MediaType contentType, final byte[] content) {
return create(contentType, content, 0, content.length);
}

因为服务端对requestBody格式有要求:

1
2
3
4
5
6
7
8
9
10
11
12
--this-is-a-boundary
Content-Disposition: form-data; name="metadata"
Content-Type: application/json; charset=UTF-8

{xxx这是一段json格式的数据xxx}

--this-is-a-boundary
Content-Disposition: form-data; name="audio"
Content-Type: application/octet-stream

pcm数据
--this-is-a-boundary--

所以采用如下方式:

1
2
@POST()
Flowable<ResponseBody> uploadPcm(@Url String url, @Body RequestBody body, @Header("xxx") String appId);

其中的body如下:

1
2
requestBody = RequestBody.create(MediaType.parse("*/*;charset=utf-8"), contentBytes, 0, contentBytes.length);
//contentBytes为服务端要求的requestBody对应的byte数组。

播放pcm数据使用AudioTrack

注意pcm数据格式需要跟服务端的一致:声道,采样率等。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.ms.xiaoicestorydemo.util;

import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;

import java.io.IOException;
import java.io.RandomAccessFile;

/**
* pcm数据播放utils
*
* @anthor kb_jay
* create at 2018/8/21 上午10:38
*/
public class AudioPlayUtils {

private final int BUFFER_SIZE = 1024 * 2;
private byte[] mBuffer = new byte[BUFFER_SIZE];
private AudioPlayUtils() {
}

private static AudioPlayUtils instance;

public static AudioPlayUtils getInstance() {
if (instance == null) {
synchronized (AudioPlayUtils.class) {
if (instance == null) {
instance = new AudioPlayUtils();
}
}
}
return instance;
}

//播放pcm文件音频
public void playPcmFileAudio(RandomAccessFile raf) {
int streamType = AudioManager.STREAM_MUSIC;
int simpleRate = 16000;
int channelConfig = AudioFormat.CHANNEL_IN_DEFAULT;
int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
int mode = AudioTrack.MODE_STREAM;

int minBufferSize = AudioTrack.getMinBufferSize(simpleRate, channelConfig, audioFormat);
AudioTrack audioTrack = new AudioTrack(streamType, simpleRate, channelConfig, audioFormat,
Math.max(minBufferSize, BUFFER_SIZE), mode);
audioTrack.play();
try {
int read;
while ((read = raf.read(mBuffer)) > 0) {
audioTrack.write(mBuffer, 0, read);
}
} catch (RuntimeException | IOException e) {
e.printStackTrace();
}
}
}

播放在线音频

使用exoMedia:git地址:https://github.com/brianwernick/ExoMedia

其中使用的是exoPlayer:git地址:https://github.com/google/ExoPlayer

tip1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    public static final int DEFAULT = 0;
/** 3GPP media file format*/
public static final int THREE_GPP = 1;
/** MPEG4 media file format*/
public static final int MPEG_4 = 2;
/** The following formats are audio only .aac or .amr formats */
/**
* AMR NB file format
* @deprecated Deprecated in favor of MediaRecorder.OutputFormat.AMR_NB
*/
public static final int RAW_AMR = 3;
/** AMR NB file format */
public static final int AMR_NB = 3;
/** AMR WB file format */
public static final int AMR_WB = 4;
/** @hide AAC ADIF file format */
public static final int AAC_ADIF = 5;
/** AAC ADTS file format */
public static final int AAC_ADTS = 6;
/** @hide Stream over a socket, limited to a single stream */
public static final int OUTPUT_FORMAT_RTP_AVP = 7;
/** H.264/AAC data encapsulated in MPEG2/TS */
public static final int MPEG_2_TS = 8;
/** VP8/VORBIS data in a WEBM container */
public static final int WEBM = 9;
};

Android-sdk签名验证

为什么要有签名验证

假设“人民日报”接入了我们开发的sdk,“今日头条”没有接入我们的sdk,那么对于我们来说,当然不希望“今日头条”可以使用我们暴漏的接口,此时就有了“签名验证”。

如何实现

  1. 我们拿到“人民日报”的apk,通过 JarFile 拿到该apk的签名信息。
  2. 将该信息打入我们的aar包中
  3. 当“人民日报”访问我们暴漏的接口的时候要求传入Context,我们可以通过Context获取packageInfo中的signature。
  4. 比对aar中的签名信息跟获取到的packageinfo中的签名信息,如果匹配,才允许访问。

如何通过jarFile获取apk签名信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class getApkSign {

public static void main(String[] args) throws IOException {

String path = "F:/myapk/test.apk";//apk的路径

List<String> list = getSignaturesFromApk(path);
// for (String s : list) {
// System.out.println(s);
// }
System.out.println(list.get(0));
}

/**
* 从APK中读取签名
*
* @param file
* @return
* @throws IOException
*/
private static List<String> getSignaturesFromApk(String strFile)
throws IOException {
File file = new File(strFile);
List<String> signatures = new ArrayList<String>();
JarFile jarFile = new JarFile(file);
try {
JarEntry je = jarFile.getJarEntry("AndroidManifest.xml");
byte[] readBuffer = new byte[8192];
Certificate[] certs = loadCertificates(jarFile, je, readBuffer);
if (certs != null) {
for (Certificate c : certs) {
String sig = toCharsString(c.getEncoded());
signatures.add(sig);
}
}
} catch (Exception ex) {
}
return signatures;
}

/**
* 加载签名
*
* @param jarFile
* @param je
* @param readBuffer
* @return
*/
private static Certificate[] loadCertificates(JarFile jarFile, JarEntry je,
byte[] readBuffer) {
try {
InputStream is = jarFile.getInputStream(je);
while (is.read(readBuffer, 0, readBuffer.length) != -1) {
}
is.close();
return je != null ? je.getCertificates() : null;
} catch (IOException e) {
}
return null;
}

/**
* 将签名转成转成可见字符串
*
* @param sigBytes
* @return
*/
private static String toCharsString(byte[] sigBytes) {
byte[] sig = sigBytes;
final int N = sig.length;
final int N2 = N * 2;
char[] text = new char[N2];
for (int j = 0; j < N; j++) {
byte v = sig[j];
int d = (v >> 4) & 0xf;
text[j * 2] = (char) (d >= 10 ? ('a' + d - 10) : ('0' + d));
d = v & 0xf;
text[j * 2 + 1] = (char) (d >= 10 ? ('a' + d - 10) : ('0' + d));
}
return new String(text);
}

}

如何将签名信息打入aar

可以在build.gradle中新建task,该task有两个任务:

  1. 通过JarFile获取签名string(参考以上代码)
  2. 将该信息copy到jni层(假设coreLib.cpp中)出于安全考虑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
task('writeApkSignature') << {
// 1,获取签名信息
def signatures = []
JarFile jarFile = new JarFile(file(apkPath))
JarEntry je = jarFile.getJarEntry("AndroidManifest.xml")
byte[] readBuffer = new byte[8192]
InputStream is = jarFile.getInputStream(je)
while (is.read(readBuffer, 0, readBuffer.length) != -1) {
}
is.close()
Certificate[] certs = je.getCertificates()
if (certs != null) {
for (Certificate c : certs) {
byte[] sig = c.getEncoded()
println new String(sig)
int N = sig.length
int N2 = N * 2
char[] text = new char[N2]

char charA = 'a'
char char0 = '0'
for (int j = 0; j < N; j++) {
byte v = sig[j]
byte d = (v >> 4) & 0xf
text[j * 2] = (char) (d >= 10 ? (charA + d - 10) : (char0 + d));
d = v & 0xf
text[j * 2 + 1] = (char) (d >= 10 ? (charA + d - 10) : (char0 + d))
}
String sigStr = new String(text)
signatures.add(sigStr)
}
}
def signature = signatures.get(0)
println signature
// 2,拷贝
def cppFile = file("src/main/cpp/common-corelib.cpp")
def cppFileText = cppFile.getText()
StringBuilder sb = new StringBuilder(cppFileText)
int start = sb.indexOf("RELEASE_SIGN")
int end = sb.indexOf("\";", start) + 1
sb.replace(start, end, "RELEASE_SIGN = \""+ signature +"\"")


cppFile.write(sb.toString())
}

如何通过context获取签名信息(jni的方式)

  1. java方式

    1
    2
    3
    PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 64);
    Signature[] signatures = packageInfo.signatures;
    String s = signatures[0].toCharsString();
  2. jni方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    char *getSignature(JNIEnv *env, jclass clazz, jobject contextObject) {
    jclass native_class = env->GetObjectClass(contextObject);
    //getPackageManager方法id
    jmethodID pm_id = env->GetMethodID(native_class, "getPackageManager",
    "()Landroid/content/pm/PackageManager;");
    //context.getPackageManager()->packageManager
    jobject pm_obj = env->CallObjectMethod(contextObject, pm_id);
    jclass pm_clazz = env->GetObjectClass(pm_obj);
    //获取pm.getpackageInfo的方法id
    jmethodID package_info_id = env->GetMethodID(pm_clazz, "getPackageInfo",
    "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
    jclass native_classs = env->GetObjectClass(contextObject);
    //context.getPackageName方法id
    jmethodID mId = env->GetMethodID(native_classs, "getPackageName", "()Ljava/lang/String;");
    //返回packageName
    jstring pkg_str = static_cast<jstring>(env->CallObjectMethod(contextObject, mId));
    //获取packageInfo
    jobject pi_obj = env->CallObjectMethod(pm_obj, package_info_id, pkg_str, 64);

    jclass pi_clazz = env->GetObjectClass(pi_obj);
    //获取packageInfo的成员变量signature的id
    jfieldID signatures_fieldId = env->GetFieldID(pi_clazz, "signatures",
    "[Landroid/content/pm/Signature;");
    //获取packageInfo的signature
    jobject signatures_obj = env->GetObjectField(pi_obj, signatures_fieldId);
    jobjectArray signaturesArray = (jobjectArray) signatures_obj;
    //获取signatures[0]
    jobject signature_obj = env->GetObjectArrayElement(signaturesArray, 0);
    jclass signature_clazz = env->GetObjectClass(signature_obj);
    jmethodID string_id = env->GetMethodID(signature_clazz, "toCharsString",
    "()Ljava/lang/String;");
    //调用signatures[0]的toCharsString方法返回string串
    jstring str = static_cast<jstring>(env->CallObjectMethod(signature_obj, string_id));
    return (char *) env->GetStringUTFChars(str, 0);
    }

Andriod 线程间通信二:线程池

线程池

为什么要用线程池?

new Thread()的缺点

  • 每次new Thread()耗费性能
  • 调用new Thread()创建的线程缺乏管理,被称为野线程,而且可以无限制创建,之间相互竞争,会导致过多占用系统资源导致系统瘫痪。
  • 不利于扩展,比如如定时执行、定期执行、线程中断

采用线程池的优点

  • 重用存在的线程,减少对象创建、消亡的开销,性能佳
  • 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞
  • 提供定时执行、定期执行、单线程、并发数控制等功能

#####线程池核心:ThreadPoolExcutor:

  • 核心线程数(coreThread)
  • 最大线程数(maxThread)
  • keepAliveTime,Unit(时间单位)
  • Queue(任务缓存队列)
  • threadFactory(线程创建工程)
  • RejectedExecutionHandler(拒绝执行handler)
  1. 当前正在执行的线程数 < 核心线程数的时候,新加入的任务就在新线程中执行
  2. 当前正在执行的线程数 > 核心线程数的时候,新加入的任务放入缓存队列
  3. 当前正在执行的线程数 >核心线程数的时候,缓存队列满了 且 当前正在执行的线程数<最大线程数,新建线程加入线程池
  4. 当前正在执行的线程数 >核心线程数的时候,缓存队列满了 且 当前正在执行的线程数=最大线程数,拒绝执行

一句话总结:先用核心线程,再用任务队列,再用”兼职“线程(最大 — 核心 ),最后拒绝执行

四种线程池(cfss)
  1. Excutors.newFixedThreadPool(5);

    1
    2
    3
    4
    5
    public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
    0L, TimeUnit.MILLISECONDS,
    new LinkedBlockingQueue<Runnable>());
    }

    总共只会创建5个线程, 开始执行五个线程,当五个线程都处于活动状态,再次提交的任务都会加入队列等到其他线程运行结束,当线程处于空闲状态时会被下一个任务复用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    ExecutorService es = Executors.newFixedThreadPool(5);
    for (int i = 0; i < 50; i++) {
    Runnable runnable = new Runnable() {
    @Override
    public void run() {
    Log.d("kb_jay", Thread.currentThread().getName());
    }
    };
    es.execute(runnable);
    }

    /*
    08-14 22:26:43.546 18684-19119/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-4
    08-14 22:26:43.547 18684-19119/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-4
    08-14 22:26:43.547 18684-19116/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-1
    08-14 22:26:43.548 18684-19116/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-1
    08-14 22:26:43.548 18684-19120/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-5
    08-14 22:26:43.548 18684-19117/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-2
    08-14 22:26:43.548 18684-19120/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-5
    08-14 22:26:43.548 18684-19117/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-2
    pool-1-thread-2
    08-14 22:26:43.548 18684-19119/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-4
    08-14 22:26:43.548 18684-19117/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-2
    08-14 22:26:43.548 18684-19118/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-3
    08-14 22:26:43.549 18684-19117/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-2
    08-14 22:26:43.549 18684-19116/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-1
    08-14 22:26:43.549 18684-19117/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-2
    08-14 22:26:43.549 18684-19118/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-3
    08-14 22:26:43.549 18684-19117/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-2
    08-14 22:26:43.549 18684-19120/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-5
    08-14 22:26:43.549 18684-19117/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-2
    08-14 22:26:43.549 18684-19120/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-5
    08-14 22:26:43.549 18684-19119/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-4
    08-14 22:26:43.550 18684-19119/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-4
    08-14 22:26:43.550 18684-19116/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-1
    */
  2. Excutors.newCachedThreadPool();

    缓存线程池大小是不定值,可以需要创建不同数量的线程,在使用缓存型池时,先查看池中有没有以前创建的线程,如果有,就复用.如果没有,就新建新的线程加入池中,缓存型池子通常用于执行一些生存期很短的异步型任务

    1
    2
    3
    4
    5
    6
    public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
    60L, TimeUnit.SECONDS,
    new SynchronousQueue<Runnable>());
    }
    //空闲线程在在60s内不会被回收

    执行如下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    ExecutorService es = Executors.newCachedThreadPool();
    for (int i = 0; i < 50; i++) {
    Runnable runnable = new Runnable() {
    @Override
    public void run() {
    try {
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    Log.d("kb_jay", Thread.currentThread().getName());
    }
    };
    es.execute(runnable);
    }
    /*
    08-14 22:41:57.302 21468-21584/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-2
    08-14 22:41:57.302 21468-21582/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-1
    08-14 22:41:57.302 21468-21585/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-3
    08-14 22:41:57.303 21468-21586/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-4
    08-14 22:41:57.303 21468-21587/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-5
    08-14 22:41:57.304 21468-21590/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-6
    08-14 22:41:57.304 21468-21591/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-7
    08-14 22:41:57.305 21468-21592/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-8
    08-14 22:41:57.305 21468-21593/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-9
    08-14 22:41:57.306 21468-21594/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-10
    08-14 22:41:57.308 21468-21596/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-11
    08-14 22:41:57.309 21468-21597/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-12
    08-14 22:41:57.309 21468-21598/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-13
    08-14 22:41:57.312 21468-21599/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-14
    08-14 22:41:57.312 21468-21600/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-15
    08-14 22:41:57.314 21468-21601/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-16
    08-14 22:41:57.315 21468-21603/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-18
    。。。
    */

    当线程sleep 1s时,几乎每个runnable都会创建一个新的线程,这是因为每个runnable开始执行时都没有cache线程(空闲时间没有超过60s的线程),所以自己要new一个线程出来。

    每个runnable的生存期长,不适合。

去掉sleep 1s后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private void start2() {
ExecutorService es = Executors.newCachedThreadPool();
for (int i = 0; i < 50; i++) {
Runnable runnable = new Runnable() {
@Override
public void run() {
Log.d("kb_jay", Thread.currentThread().getName());
}
};
es.execute(runnable);
}
}

/*
08-14 22:52:19.609 22498-22655/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-1
08-14 22:52:19.610 22498-22656/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-2
pool-1-thread-2
08-14 22:52:19.610 22498-22655/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-1
08-14 22:52:19.610 22498-22656/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-2
08-14 22:52:19.610 22498-22657/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-3
08-14 22:52:19.611 22498-22657/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-3
08-14 22:52:19.611 22498-22658/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-4
08-14 22:52:19.611 22498-22656/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-2
08-14 22:52:19.612 22498-22655/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-1
08-14 22:52:19.612 22498-22656/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-2
08-14 22:52:19.613 22498-22658/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-4
08-14 22:52:19.613 22498-22659/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-5
08-14 22:52:19.613 22498-22658/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-4
08-14 22:52:19.613 22498-22656/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-2
08-14 22:52:19.613 22498-22658/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-4
08-14 22:52:19.613 22498-22659/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-5
。。。
*/

会发现只创建了少量的线程,会有线程的复用情况。这是因为部分runnale开始执行时线程池中有空闲线程且该线程空闲时间没有超过60s,可以复用。

每一个runnable的生存期短,适合。

  1. Excutors.newScheduleThreadPool(5);

    这个跟fixedThreadPool相比,只是多了个延迟开始执行的功能

    执行如下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    ScheduledExecutorService es = Executors.newScheduledThreadPool(5);
    for (int i = 0; i < 50; i++) {
    Runnable runnable = new Runnable() {
    @Override
    public void run() {
    try {
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    Log.d("kb_jay", Thread.currentThread().getName());
    }
    };
    es.schedule(runnable,5000, TimeUnit.MILLISECONDS);
    }

    /*
    08-14 23:06:03.560 24100-24176/? D/kb_jay: pool-1-thread-1
    08-14 23:06:03.560 24100-24177/? D/kb_jay: pool-1-thread-2
    08-14 23:06:03.561 24100-24178/? D/kb_jay: pool-1-thread-3
    08-14 23:06:03.562 24100-24179/? D/kb_jay: pool-1-thread-4
    08-14 23:06:03.562 24100-24180/? D/kb_jay: pool-1-thread-5
    08-14 23:06:04.562 24100-24177/? D/kb_jay: pool-1-thread-2
    08-14 23:06:04.562 24100-24176/? D/kb_jay: pool-1-thread-1
    08-14 23:06:04.562 24100-24178/? D/kb_jay: pool-1-thread-3
    08-14 23:06:04.563 24100-24179/? D/kb_jay: pool-1-thread-4
    08-14 23:06:04.563 24100-24180/? D/kb_jay: pool-1-thread-5
    08-14 23:06:05.563 24100-24178/? D/kb_jay: pool-1-thread-3
    08-14 23:06:05.563 24100-24177/? D/kb_jay: pool-1-thread-2
    08-14 23:06:05.564 24100-24180/? D/kb_jay: pool-1-thread-5
    08-14 23:06:05.564 24100-24176/? D/kb_jay: pool-1-thread-1
    。。。
    */

    schedule(Runnable command,long delay, TimeUnit unit)

    创建并执行在给定延迟后启用的一次性操作

**scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnitunit)**  

创建并执行一个在给定初始延迟后首次启用的定期操作,后续操作具有给定的周期;也就是将在 initialDelay 后开始执行,然后在initialDelay+period 后执行,接着在 initialDelay + 2 * period 后执行,依此类推

  1. Excutors.newSingleThreadExcutor();

    创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

    1
    2
    3
    4
    5
    6
    public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
    (new ThreadPoolExecutor(1, 1,
    0L, TimeUnit.MILLISECONDS,
    new LinkedBlockingQueue<Runnable>()));
    }

    执行如下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    ExecutorService es = Executors.newSingleThreadExecutor();
    for (int i = 0; i < 50; i++) {
    Runnable runnable = new Runnable() {
    @Override
    public void run() {
    try {
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    Log.d("kb_jay", Thread.currentThread().getName());
    }
    };
    es.execute(runnable);
    }

    /*
    08-14 23:20:28.503 26101-26241/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-1
    08-14 23:20:29.504 26101-26241/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-1
    08-14 23:20:30.506 26101-26241/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-1
    08-14 23:20:31.507 26101-26241/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-1
    08-14 23:20:32.509 26101-26241/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-1
    08-14 23:20:33.511 26101-26241/com.example.kb_jay.kj_thread D/kb_jay: pool-1-thread-1
    。。。
    */

####

####

HMAC-sha1 加密算法

####c++代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
#include <stdlib.h>
#include <stdio.h>
#include <cstring>
#include "hmac_sha1.h"
#define MAX_MESSAGE_LENGTH 4096
/*****************************/
/**** Function Prototypes ****/
/*****************************/

unsigned long int ft(
int t,
unsigned long int x,
unsigned long int y,
unsigned long int z
);

void sha1 (
unsigned char *message,
int message_length,
unsigned char *digest
);

/**************************/
/* Debug out */
/**************************/

#ifdef HMAC_DEBUG
debug_out(
unsigned char *label,
unsigned char *data,
int data_length
)
{
int i,j;
int num_blocks;
int block_remainder;
num_blocks = data_length / 16;
block_remainder = data_length % 16;

printf("%s\n",label);

for (i=0; i< num_blocks;i++)
{
printf("\t");
for (j=0; j< 16;j++)
{
printf("%02x ", data[j + (i*16)]);
}
printf("\n");
}

if (block_remainder > 0)
{
printf("\t");
for (j=0; j<block_remainder; j++)
{
printf("%02x ", data[j+(num_blocks*16)]);
}
printf("\n");
}
}
#endif

/****************************************/
/* sha1() */
/* Performs the NIST SHA-1 algorithm */
/****************************************/

unsigned long int ft(
int t,
unsigned long int x,
unsigned long int y,
unsigned long int z
)
{
unsigned long int a,b,c;

if (t < 20)
{
a = x & y;
b = (~x) & z;
c = a ^ b;
}
else if (t < 40)
{
c = x ^ y ^ z;
}
else if (t < 60)
{
a = x & y;
b = a ^ (x & z);
c = b ^ (y & z);
}
else if (t < 80)
{
c = (x ^ y) ^ z;
}

return c;
}

unsigned long int k(int t)
{
unsigned long int c;

if (t < 20)
{
c = 0x5a827999;
}
else if (t < 40)
{
c = 0x6ed9eba1;
}
else if (t < 60)
{
c = 0x8f1bbcdc;
}
else if (t < 80)
{
c = 0xca62c1d6;
}

return c;
}

unsigned long int rotr(int bits, unsigned long int a)
{
unsigned long int c,d,e,f,g;
c = (0x0001 << bits)-1;
d = ~c;

e = (a & d) >> bits;
f = (a & c) << (32 - bits);

g = e | f;

return (g & 0xffffffff );

}

unsigned long int rotl(int bits, unsigned long int a)
{
unsigned long int c,d,e,f,g;
c = (0x0001 << (32-bits))-1;
d = ~c;

e = (a & c) << bits;
f = (a & d) >> (32 - bits);

g = e | f;

return (g & 0xffffffff );

}


void sha1 (
unsigned char *message,
int message_length,
unsigned char *digest
)
{
int i;
int num_blocks;
int block_remainder;
int padded_length;

unsigned long int l;
unsigned long int t;
unsigned long int h[5];
unsigned long int a,b,c,d,e;
unsigned long int w[80];
unsigned long int temp;

#ifdef SHA1_DEBUG
int x,y;
#endif

/* Calculate the number of 512 bit blocks */

padded_length = message_length + 8; /* Add length for l */
padded_length = padded_length + 1; /* Add the 0x01 bit postfix */

l = message_length * 8;

num_blocks = padded_length / 64;
block_remainder = padded_length % 64;


if (block_remainder > 0)
{
num_blocks++;
}

padded_length = padded_length + (64 - block_remainder);

/* clear the padding field */
for (i = message_length; i < (num_blocks * 64); i++)
{
message[i] = 0x00;
}

/* insert b1 padding bit */
message[message_length] = 0x80;

/* Insert l */
message[(num_blocks*64)-1] = (unsigned char)( l & 0xff);
message[(num_blocks*64)-2] = (unsigned char)((l >> 8) & 0xff);
message[(num_blocks*64)-3] = (unsigned char)((l >> 16) & 0xff);
message[(num_blocks*64)-4] = (unsigned char)((l >> 24) & 0xff);

/* Set initial hash state */
h[0] = 0x67452301;
h[1] = 0xefcdab89;
h[2] = 0x98badcfe;
h[3] = 0x10325476;
h[4] = 0xc3d2e1f0;

#ifdef SHA1_DEBUG
printf("INITIAL message_length = %d\n", message_length);
printf("INITIAL padded_length = %d\n", padded_length);
printf("INITIAL num_blocks = %d\n", num_blocks);

for (x=0;x<num_blocks; x++)
{
printf("\t\t");
for (y=0; y<16;y++)
{
printf("%02x ",message[y + (x*64)]);
}
printf("\n");
printf("\t\t");
for (y=0; y<16;y++)
{
printf("%02x ",message[16 + y + (x*64)]);
}
printf("\n");
printf("\t\t");
for (y=0; y<16;y++)
{
printf("%02x ",message[32 + y + (x*64)]);
}
printf("\n");
printf("\t\t");
for (y=0; y<16;y++)
{
printf("%02x ",message[48 + y + (x*64)]);
}
printf("\n");
}

#endif

for (i = 0; i < num_blocks; i++)
{
/* Prepare the message schedule */
for (t=0; t < 80; t++)
{
if (t < 16)
{
w[t] = (256*256*256) * message[(i*64)+(t*4)];
w[t] += (256*256 ) * message[(i*64)+(t*4) + 1];
w[t] += (256 ) * message[(i*64)+(t*4) + 2];
w[t] += message[(i*64)+(t*4) + 3];
}
else if (t < 80)
{
w[t] = rotl(1,(w[t-3] ^ w[t-8] ^ w[t-14] ^ w[t-16]));
}
}

#ifdef SHA1_DEBUG
printf("\tW(0) = %08lX \t W(9) = %08lX \n", w[0], w[8]);
printf("\tW(1) = %08lX \t W(10) = %08lX \n", w[1], w[9]);
printf("\tW(2) = %08lX \t W(11) = %08lX \n", w[2], w[10]);
printf("\tW(3) = %08lX \t W(12) = %08lX \n", w[3], w[11]);
printf("\tW(4) = %08lX \t W(13) = %08lX \n", w[4], w[12]);
printf("\tW(5) = %08lX \t W(14) = %08lX \n", w[5], w[13]);
printf("\tW(6) = %08lX \t W(15) = %08lX \n", w[6], w[14]);
printf("\tW(7) = %08lX \t W(16) = %08lX \n\n", w[7], w[15]);

#endif
/* Initialize the five working variables */
a = h[0];
b = h[1];
c = h[2];
d = h[3];
e = h[4];

/* iterate a-e 80 times */

for (t = 0; t < 80; t++)
{
temp = (rotl(5,a) + ft(t,b,c,d)) & 0xffffffff;
temp = (temp + e) & 0xffffffff;
temp = (temp + k(t)) & 0xffffffff;
temp = (temp + w[t]) & 0xffffffff;
e = d;
d = c;
c = rotl(30,b);
b = a;
a = temp;
#ifdef SHA1_DEBUG
printf("t = %2ld\t %08lx, %08lx, %08lx, %08lx, %08lx\n", t,a,b,c,d,e);
#endif

}

/* compute the ith intermediate hash value */
#ifdef SHA1_DEBUG
printf(" + \t %08lx, %08lx, %08lx, %08lx, %08lx\n", h[0],h[1],h[2],h[3],h[4]);
#endif
h[0] = (a + h[0]) & 0xffffffff;
h[1] = (b + h[1]) & 0xffffffff;
h[2] = (c + h[2]) & 0xffffffff;
h[3] = (d + h[3]) & 0xffffffff;
h[4] = (e + h[4]) & 0xffffffff;

#ifdef SHA1_DEBUG
printf(" = \t %08lx, %08lx, %08lx, %08lx, %08lx\n", h[0],h[1],h[2],h[3],h[4]);
#endif

}

digest[3] = (unsigned char) ( h[0] & 0xff);
digest[2] = (unsigned char) ((h[0] >> 8) & 0xff);
digest[1] = (unsigned char) ((h[0] >> 16) & 0xff);
digest[0] = (unsigned char) ((h[0] >> 24) & 0xff);

digest[7] = (unsigned char) ( h[1] & 0xff);
digest[6] = (unsigned char) ((h[1] >> 8) & 0xff);
digest[5] = (unsigned char) ((h[1] >> 16) & 0xff);
digest[4] = (unsigned char) ((h[1] >> 24) & 0xff);

digest[11] = (unsigned char) ( h[2] & 0xff);
digest[10] = (unsigned char) ((h[2] >> 8) & 0xff);
digest[9] = (unsigned char) ((h[2] >> 16) & 0xff);
digest[8] = (unsigned char) ((h[2] >> 24) & 0xff);

digest[15] = (unsigned char) ( h[3] & 0xff);
digest[14] = (unsigned char) ((h[3] >> 8) & 0xff);
digest[13] = (unsigned char) ((h[3] >> 16) & 0xff);
digest[12] = (unsigned char) ((h[3] >> 24) & 0xff);

digest[19] = (unsigned char) ( h[4] & 0xff);
digest[18] = (unsigned char) ((h[4] >> 8) & 0xff);
digest[17] = (unsigned char) ((h[4] >> 16) & 0xff);
digest[16] = (unsigned char) ((h[4] >> 24) & 0xff);

}

/******************************************************/
/* hmac-sha1() */
/* Performs the hmac-sha1 keyed secure hash algorithm */
/******************************************************/

void hmac_sha1(
unsigned char *key,
int key_length,
unsigned char *data,
int data_length,
unsigned char *digest
)

{
int b = 64; /* blocksize */
unsigned char ipad = 0x36;

unsigned char opad = 0x5c;

unsigned char k0[64];
unsigned char k0xorIpad[64];
unsigned char step7data[64];
unsigned char step5data[MAX_MESSAGE_LENGTH+128];
unsigned char step8data[64+20];
int i;

for (i=0; i<64; i++)
{
k0[i] = 0x00;
}



if (key_length != b) /* Step 1 */
{
/* Step 2 */
if (key_length > b)
{
sha1(key, key_length, digest);
for (i=0;i<20;i++)
{
k0[i]=digest[i];
}
}
else if (key_length < b) /* Step 3 */
{
for (i=0; i<key_length; i++)
{
k0[i] = key[i];
}
}
}
else
{
for (i=0;i<b;i++)
{
k0[i] = key[i];
}
}
#ifdef HMAC_DEBUG
debug_out("k0",k0,64);
#endif
/* Step 4 */
for (i=0; i<64; i++)
{
k0xorIpad[i] = k0[i] ^ ipad;
}
#ifdef HMAC_DEBUG
debug_out("k0 xor ipad",k0xorIpad,64);
#endif
/* Step 5 */
for (i=0; i<64; i++)
{
step5data[i] = k0xorIpad[i];
}
for (i=0;i<data_length;i++)
{
step5data[i+64] = data[i];
}
#ifdef HMAC_DEBUG
debug_out("(k0 xor ipad) || text",step5data,data_length+64);
#endif

/* Step 6 */
sha1(step5data, data_length+b, digest);

#ifdef HMAC_DEBUG
debug_out("Hash((k0 xor ipad) || text)",digest,20);
#endif

/* Step 7 */
for (i=0; i<64; i++)
{
step7data[i] = k0[i] ^ opad;
}

#ifdef HMAC_DEBUG
debug_out("(k0 xor opad)",step7data,64);
#endif

/* Step 8 */
for (i=0;i<64;i++)
{
step8data[i] = step7data[i];
}
for (i=0;i<20;i++)
{
step8data[i+64] = digest[i];
}

#ifdef HMAC_DEBUG
debug_out("(k0 xor opad) || Hash((k0 xor ipad) || text)",step8data,20+64);
#endif

/* Step 9 */
sha1(step8data, b+20, digest);

#ifdef HMAC_DEBUG
debug_out("HASH((k0 xor opad) || Hash((k0 xor ipad) || text))",digest,20);
#endif
}

####原理

#####hmac(hashing message authentication code)信息认证码,一种计算认证码的算法,

  • 输入:secret,message
  • 输出:byte[];
  • 简单步骤:hash(secret ^ out , hash(serret ^ in , message )) ;
    • new char in 0x36 ;new char out 0x5c
    • 将secret跟in异或之后加入message,结果保存为message1
    • 将message1做hash运算(sha1),结果保存为item1
    • 将secret跟out异或之后加入item1,结果保存为message2;
    • 将message2做hash运算(sha1),结果return;

#####sha1 一种hash算法,在hmac算法过程中会用到

ps:hmac-sha1之后获取的20位byte

####作用

​ hmac主要应用在身份验证中,它的使用方法是这样的:  

  1. 客户端发出登录请求(假设是浏览器的GET请求)   

  2. 服务器返回一个随机值,并在会话中记录这个随机值   

  3. 客户端将该随机值作为密钥,用户密码进行hmac运算,然后提交给服务器   

  4. 服务器读取用户数据库中的用户密码和步骤2中发送的随机值做与客户端一样的hmac运算,然后与用户发送的结果比较,如果结果一致则验证用户合法 。  

在这个过程中,可能遭到安全攻击的是服务器发送的随机值和用户发送的hmac结果,而对于截获 了这两个值的黑客而言这两个值是没有意义的,绝无获取用户密码的可能性,随机值的引入使hmac只在当前会话中有效,大大增强了安全性和实用性。

Android-微信小程序

开发工具

  • phpStorm
  • 官方工具

phpstorm设置:

1.首先FileType下Cascading Style Sheet 添加*.wxss

2.FileType下HTML 添加*.wxml

3.将其中的wecharCode.jar下载下来,然后在webStorm 的 File -> import settings 中导入即可

jar包地址:https://github.com/miaozhang9/wecharCodejar

参考:https://www.jianshu.com/p/00724ab30c89

参考:https://developers.weixin.qq.com/miniprogram/dev/quickstart/basic/file.html

Android-反编译详细流程

总体流程:

  1. 拿到需要破解的apk包,解压获取dex文件
  2. 使用dex2jar工具获取jar文件
  3. 使用jd-gui打开jar文件
  4. Android studio 安装可以将java转换成smali的插件:java2smali
  5. 编写InjectLog.class文件,将该文件转为smali文件备用(生成好的smali文件参见tip2)
  6. 使用apktool工具“解压”apk文件,获取smali文件
  7. 修改manifest文件,application下添加 andriod:debuggable=true;
  8. 将InjectLog.smali根据包名(“com/hook/testsmali/InjectLog”)放入指定位置
  9. 使用python文件(参见tip1)给smali文件注入日志,修改walker=os.walk(“/Users/kb_jay/Documents/injectLog4/msc/smali/com”)中的参数可以指定那些smali文件需要注入日志
  10. 使用apktool生成签名文件
  11. 使用apktool将注入完日志的smali文件重新打包签名生成apk文件; jarsigner -verbose -keystore mykeystore -signedjar android_signed.apk(目标名字) TestSMSDemo.apk(要签名的apk) linlin
  12. 安装运行apk,触发关注的事件,根据Tag(InjectLog)获取方法调用栈的日志
  13. 将日志保存备用
  14. Android Studio安装smalidea插件
  15. 将smali文件夹导入到Android Studio中
  16. 将smali文件夹设置为source root
  17. 设置project的jdk
  18. 在as中创建remote调试,记住设置的端口号
  19. 在as终端输入adb shell am start -D -n com.xxx.xxx/com.xxx.xxx.xxx 以debug方式打开apk
  20. 查看该apk所有进程的Pid:adb shell ps | grep com.xxx.xxx
  21. 连接debug的remote端口(电脑)跟apk的进程(手机):adb forward tcp:remote调试的端口号 jdwp:查找到的pid eg:adb forward tcp:5005 jdwp:12000
  22. 根据保存好的日志文件在smali文件中加入断点
  23. run->debug (done)

参考:

https://my.oschina.net/zhibuji/blog/410993

https://www.cnblogs.com/gordon0918/p/5570811.html

http://www.cnblogs.com/goodhacker/p/5592313.html

tip

  1. 注入日志的python文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

# encoding: utf-8
import os

class ParserError(Exception):
pass

# 注入代码到一个函数块中
def inject_code_to_method_section(method_section):
# 静态构造函数,无需处理
if method_section[0].find("static constructor") != -1:
return method_section
# synthetic函数,无需处理
if method_section[0].find("synthetic") != -1:
return method_section
# 抽象方法,无需处理
if method_section[0].find("abstract") != -1:
return method_section
# 生成待插入代码行
inject_code = [
'\n',
' invoke-static {}, Lcom/hook/testsmali/InjectLog;->PrintFunc()V\n',
'\n'
]
#插入到.prologue的下一行
is_inject = False
for i in range(0, len(method_section)):
if method_section[i].find(".prologue") != -1:
is_inject = True
method_section[i + 1: i + 1] = inject_code
break

return method_section


def inject_log_code(content):
new_content = []
method_section = []
is_method_begin = False
for line in content:
if line[:7] == ".method":
is_method_begin = True
method_section.append(line)
continue
if is_method_begin:
method_section.append(line)
else:
new_content.append(line)
if line[:11] == ".end method":
if not is_method_begin:
raise ParserError(".method不对称")
is_method_begin = False
new_method_section = inject_code_to_method_section(method_section)
new_content.extend(new_method_section)
del method_section[:]

return new_content


def main():
walker = os.walk("/Users/kb_jay/Documents/injectLog4/msc/smali/com")
for root, directory, files in walker:
for file_name in files:
if file_name[-6:] != ".smali":
continue
file_path = root + "/" + file_name
print(file_path)
file = open(file_path)
lines = file.readlines()
file.close()
new_code = inject_log_code(lines)
file = open(file_path, "w")
file.writelines(new_code)
file.close()


if __name__ == '__main__':
main()
  1. 注入日志的smali文件,注意包名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
.class public Lcom/hook/testsmali/InjectLog;
.super Ljava/lang/Object;
.source "InjectLog.java"


# direct methods
.method public constructor <init>()V
.registers 1

.prologue

.line 3
invoke-direct {p0}, Ljava/lang/Object;-><init>()V

return-void
.end method

.method public static PrintFunc()V
.registers 6

.prologue

.line 7
invoke-static {}, Ljava/lang/Thread;->currentThread()Ljava/lang/Thread;

move-result-object v0

.line 8
.local v0, "cur_thread":Ljava/lang/Thread;
invoke-virtual {v0}, Ljava/lang/Thread;->getStackTrace()[Ljava/lang/StackTraceElement;

move-result-object v1

.line 9
.local v1, "stack":[Ljava/lang/StackTraceElement;
const-string v2, "InjectLog"

new-instance v3, Ljava/lang/StringBuilder;

invoke-direct {v3}, Ljava/lang/StringBuilder;-><init>()V

const/4 v4, 0x3

aget-object v4, v1, v4

invoke-virtual {v4}, Ljava/lang/StackTraceElement;->toString()Ljava/lang/String;

move-result-object v4

invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

move-result-object v3

const-string v4, "["

invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

move-result-object v3

invoke-virtual {v0}, Ljava/lang/Thread;->getId()J

move-result-wide v4

invoke-virtual {v3, v4, v5}, Ljava/lang/StringBuilder;->append(J)Ljava/lang/StringBuilder;

move-result-object v3

const-string v4, "]"

invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

move-result-object v3

invoke-virtual {v3}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

move-result-object v3

invoke-static {v2, v3}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

.line 10
return-void
.end method
  1. 关于smali语法

参考:https://www.jianshu.com/p/ba9b374346dd

  1. 打开apk自带的log:找到对应的logging类,修改smali文件,重新打包签名运行即可。

leetcode-摆动序列

如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。少于两个元素的序列也是摆动序列。

例如, [1,7,4,9,2,5] 是一个摆动序列,因为差值 (6,-3,5,-7,3) 是正负交替出现的。相反, [1,4,7,2,5][1,7,4,5,5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。

给定一个整数序列,返回作为摆动序列的最长子序列的长度。 通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。

示例:

1
2
3
4
5
6
7
8
9
10
输入: [1,7,4,9,2,5]
输出: 6
解释: 整个序列就是一个摆动序列。

输入: [1,17,5,10,13,15,10,5,16,8]
输出: 7
解释: 它的几个子序列满足摆动序列。其中一个是[1,17,10,13,10,16,8]。

输入: [1,2,3,4,5,6,7,8,9]
输出: 2

思路

f(n)=max{f(0)+n,f(1)+n,…,f(n-1)+n};

f(n)表示以n结尾的最大的个数。

o(n*n)

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
class Solution {
public int wiggleMaxLength(int[] a) {

int l=a.length;
Item[] items = new Item[l];
int re =0;
for(int i=0;i<l;i++){
Item item= new Item();
if(i==0){
item.num=1;
item.needBig=2;
}else{
int max=0;
int nb=0;
int temp =a[i];
for(int j=0;j<i;j++){
int number=1;
int t=a[j];
int temp1=items[j].num;
int b = items[j].needBig;
if(temp>t){
if(b==0){
number=2;
if(number>max){
max=number;
nb=0;
}
}else if(b==1){
number=temp1+1;
if(number>max){
max=number;
nb=0;
}
}else {
number=2;
if(number>max){
max=number;
nb=0;
}
}
}else if(temp==t){
number=1;
if(number>max){
max=number;
nb=2;
}
}else{
if(b==0){
number=temp1+1;
if(number>max){
max=number;
nb=1;
}
}else if(b==1){
number=2;
if(number>max){
max=number;
nb=1;
}
}else{
number=2;
if(number>max){
max=number;
nb=1;
}
}
}
}
item.num=max;
item.needBig=nb;
}
items[i]=item;
re=re>item.num?re:item.num;
System.out.println(item.num+" "+item.needBig);
}
return re;
}
class Item{
int num;
int needBig;//0 1 2
}
}