diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..39fb081 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..7ac24c7 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..cc72de3 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + 1.8 + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..ee92a4a --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..ba5ccce --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,33 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 26 + defaultConfig { + applicationId "com.example.bob.nativemessagequeue" + minSdkVersion 15 + targetSdkVersion 26 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + externalNativeBuild { + cmake { + path "src/main/cpp/CMakeLists.txt" + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.android.support:appcompat-v7:26.1.0' + implementation 'com.android.support.constraint:constraint-layout:1.0.2' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.1' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/androidTest/java/com/example/bob/nativemessagequeue/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/example/bob/nativemessagequeue/ExampleInstrumentedTest.java new file mode 100644 index 0000000..7e97809 --- /dev/null +++ b/app/src/androidTest/java/com/example/bob/nativemessagequeue/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.example.bob.nativemessagequeue; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.example.bob.nativemessagequeue", appContext.getPackageName()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..b4f8252 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt new file mode 100644 index 0000000..9a79bbc --- /dev/null +++ b/app/src/main/cpp/CMakeLists.txt @@ -0,0 +1,14 @@ +# For more information about using CMake with Android Studio, read the +# documentation: https://d.android.com/studio/projects/add-native-code.html + +# Sets the minimum version of CMake required to build the native library. + +cmake_minimum_required(VERSION 3.4.1) + +aux_source_directory(. JNI_SRC) + +add_library(native-lib SHARED ${JNI_SRC}) + +find_library(log-lib log) + +target_link_libraries(native-lib ${log-lib} ) \ No newline at end of file diff --git a/app/src/main/cpp/debug.h b/app/src/main/cpp/debug.h new file mode 100644 index 0000000..5975f8c --- /dev/null +++ b/app/src/main/cpp/debug.h @@ -0,0 +1,25 @@ +// +// Created by bob on 17-4-24. +// + +#ifndef DEBUG_H +#define DEBUG_H + +#ifdef __ANDROID__ + +#include +#include + +#define logi(TAG, ...) ((void)__android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)) +#define logw(TAG, ...) ((void)__android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__)) +#define loge(TAG, ...) ((void)__android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)) + +#else + +#define logi(...) +#define logw(...) +#define loge(...) + +#endif + +#endif //DEBUG_H diff --git a/app/src/main/cpp/msg_queue.h b/app/src/main/cpp/msg_queue.h new file mode 100644 index 0000000..12c31bd --- /dev/null +++ b/app/src/main/cpp/msg_queue.h @@ -0,0 +1,270 @@ +#ifndef MSG_QUEUE_H +#define MSG_QUEUE_H + +#include "mutex.h" +#include +#include + +#define MSG_FLUSH 0 + +typedef struct AVMessage { + int what; + int arg1; + int arg2; + int len; + void *obj; + void (*free_l)(void *obj); + struct AVMessage *next; +} AVMessage; + +typedef struct MessageQueue { + AVMessage *first_msg, *last_msg; + int nb_messages; + int abort_request; + Mutex *mutex; + Cond *cond; + + AVMessage *recycle_msg; + int recycle_count; + int alloc_count; +} MessageQueue; + +inline static void msg_free_res(AVMessage *msg) +{ + if (!msg || !msg->obj) + return; + assert(msg->free_l); + msg->free_l(msg->obj); + msg->obj = NULL; +} + +inline static int msg_queue_put_private(MessageQueue *q, AVMessage *msg) +{ + AVMessage *msg1; + + if (q->abort_request) + return -1; + + msg1 = q->recycle_msg; + if (msg1) { + q->recycle_msg = msg1->next; + q->recycle_count++; + } else { + q->alloc_count++; + msg1 = (AVMessage *) malloc(sizeof(AVMessage)); + } + if (!msg1) + return -1; + + *msg1 = *msg; + msg1->next = NULL; + + if (!q->last_msg) + q->first_msg = msg1; + else + q->last_msg->next = msg1; + q->last_msg = msg1; + q->nb_messages++; + CondSignal(q->cond); + return 0; +} + +inline static int msg_queue_put(MessageQueue *q, AVMessage *msg) +{ + int ret; + + LockMutex(q->mutex); + ret = msg_queue_put_private(q, msg); + UnlockMutex(q->mutex); + + return ret; +} + +inline static void msg_init_msg(AVMessage *msg) +{ + memset(msg, 0, sizeof(AVMessage)); +} + +inline static void msg_queue_put_simple1(MessageQueue *q, int what) +{ + AVMessage msg; + msg_init_msg(&msg); + msg.what = what; + msg_queue_put(q, &msg); +} + +inline static void msg_queue_put_simple2(MessageQueue *q, int what, int arg1) +{ + AVMessage msg; + msg_init_msg(&msg); + msg.what = what; + msg.arg1 = arg1; + msg_queue_put(q, &msg); +} + +inline static void msg_queue_put_simple3(MessageQueue *q, int what, int arg1, int arg2) +{ + AVMessage msg; + msg_init_msg(&msg); + msg.what = what; + msg.arg1 = arg1; + msg.arg2 = arg2; + msg_queue_put(q, &msg); +} + +inline static void msg_obj_free_l(void *obj) +{ + free(obj); +} + +inline static void msg_queue_put_simple4(MessageQueue *q, int what, int arg1, int arg2, void *obj, int obj_len) +{ + AVMessage msg; + msg_init_msg(&msg); + msg.what = what; + msg.arg1 = arg1; + msg.arg2 = arg2; + msg.len = obj_len; + msg.obj = malloc((size_t) obj_len); + memcpy(msg.obj, obj, (size_t) obj_len); + msg.free_l = msg_obj_free_l; + msg_queue_put(q, &msg); +} + +inline static void msg_queue_init(MessageQueue *q) +{ + memset(q, 0, sizeof(MessageQueue)); + q->mutex = CreateMutex(); + q->cond = CreateCond(); + q->abort_request = 1; +} + +inline static void msg_queue_flush(MessageQueue *q) +{ + AVMessage *msg, *msg1; + + LockMutex(q->mutex); + for (msg = q->first_msg; msg != NULL; msg = msg1) { + msg1 = msg->next; + msg->next = q->recycle_msg; + q->recycle_msg = msg; + } + q->last_msg = NULL; + q->first_msg = NULL; + q->nb_messages = 0; + UnlockMutex(q->mutex); +} + +inline static void msg_queue_destroy(MessageQueue *q) +{ + msg_queue_flush(q); + + LockMutex(q->mutex); + while(q->recycle_msg) { + AVMessage *msg = q->recycle_msg; + if (msg) + q->recycle_msg = msg->next; + msg_free_res(msg); + free(msg); + } + UnlockMutex(q->mutex); + + DestroyMutex(q->mutex); + DestroyCond(q->cond); +} + +inline static void msg_queue_abort(MessageQueue *q) +{ + LockMutex(q->mutex); + + q->abort_request = 1; + + CondSignal(q->cond); + + UnlockMutex(q->mutex); +} + +inline static void msg_queue_start(MessageQueue *q) +{ + LockMutex(q->mutex); + q->abort_request = 0; + + AVMessage msg; + msg_init_msg(&msg); + msg.what = MSG_FLUSH; + msg_queue_put_private(q, &msg); + UnlockMutex(q->mutex); +} + +/* return < 0 if aborted, 0 if no msg and > 0 if msg. */ +inline static int msg_queue_get(MessageQueue *q, AVMessage *msg, int block) +{ + AVMessage *msg1; + int ret; + + LockMutex(q->mutex); + + for (;;) { + if (q->abort_request) { + ret = -1; + break; + } + + msg1 = q->first_msg; + if (msg1) { + q->first_msg = msg1->next; + if (!q->first_msg) + q->last_msg = NULL; + q->nb_messages--; + *msg = *msg1; + msg1->obj = NULL; + msg1->next = q->recycle_msg; + q->recycle_msg = msg1; + ret = 1; + break; + } else if (!block) { + ret = 0; + break; + } else { + CondWait(q->cond, q->mutex); + } + } + UnlockMutex(q->mutex); + return ret; +} + +inline static void msg_queue_remove(MessageQueue *q, int what) +{ + AVMessage **p_msg, *msg, *last_msg; + LockMutex(q->mutex); + + last_msg = q->first_msg; + + if (!q->abort_request && q->first_msg) { + p_msg = &q->first_msg; + while (*p_msg) { + msg = *p_msg; + + if (msg->what == what) { + *p_msg = msg->next; + msg_free_res(msg); + msg->next = q->recycle_msg; + q->recycle_msg = msg; + q->nb_messages--; + } else { + last_msg = msg; + p_msg = &msg->next; + } + } + + if (q->first_msg) { + q->last_msg = last_msg; + } else { + q->last_msg = NULL; + } + } + + UnlockMutex(q->mutex); +} + +#endif diff --git a/app/src/main/cpp/mutex.c b/app/src/main/cpp/mutex.c new file mode 100644 index 0000000..48bbfea --- /dev/null +++ b/app/src/main/cpp/mutex.c @@ -0,0 +1,150 @@ +#include +#include +#include +#include +#include +#include "mutex.h" + +Mutex *CreateMutex(void) +{ + Mutex *mutex; + mutex = (Mutex *) malloc(sizeof(Mutex)); + if (!mutex) + return NULL; + + if (pthread_mutex_init(&mutex->id, NULL) != 0) { + free(mutex); + return NULL; + } + + return mutex; +} + +void DestroyMutex(Mutex *mutex) +{ + if (mutex) { + pthread_mutex_destroy(&mutex->id); + free(mutex); + } +} + +void DestroyMutexP(Mutex **mutex) +{ + if (mutex) { + DestroyMutex(*mutex); + *mutex = NULL; + } +} + +int LockMutex(Mutex *mutex) +{ + assert(mutex); + if (!mutex) + return -1; + + return pthread_mutex_lock(&mutex->id); +} + +int UnlockMutex(Mutex *mutex) +{ + assert(mutex); + if (!mutex) + return -1; + + return pthread_mutex_unlock(&mutex->id); +} + +Cond *CreateCond(void) +{ + Cond *cond; + cond = (Cond *) malloc(sizeof(Cond)); + if (!cond) + return NULL; + + if (pthread_cond_init(&cond->id, NULL) != 0) { + free(cond); + return NULL; + } + + return cond; +} + +void DestroyCond(Cond *cond) +{ + if (cond) { + pthread_cond_destroy(&cond->id); + free(cond); + } +} + +void DestroyCondP(Cond **cond) +{ + if (cond) { + DestroyCond(*cond); + *cond = NULL; + } +} + +int CondSignal(Cond *cond) +{ + assert(cond); + if (!cond) + return -1; + + return pthread_cond_signal(&cond->id); +} + +int CondBroadcast(Cond *cond) +{ + assert(cond); + if (!cond) + return -1; + + return pthread_cond_broadcast(&cond->id); +} + +int CondWaitTimeout(Cond *cond, Mutex *mutex, uint32_t ms) +{ + int retval; + struct timeval delta; + struct timespec abstime; + + assert(cond); + assert(mutex); + if (!cond || !mutex) { + return -1; + } + + gettimeofday(&delta, NULL); + + abstime.tv_sec = delta.tv_sec + (ms / 1000); + abstime.tv_nsec = (delta.tv_usec + (ms % 1000) * 1000) * 1000; + if (abstime.tv_nsec > 1000000000) { + abstime.tv_sec += 1; + abstime.tv_nsec -= 1000000000; + } + + while (1) { + retval = pthread_cond_timedwait(&cond->id, &mutex->id, &abstime); + if (retval == 0) + return 0; + else if (retval == EINTR) + continue; + else if (retval == ETIMEDOUT) + return MUTEX_TIMEDOUT; + else + break; + } + + return -1; +} + +int CondWait(Cond *cond, Mutex *mutex) +{ + assert(cond); + assert(mutex); + if (!cond || !mutex) + return -1; + + return pthread_cond_wait(&cond->id, &mutex->id); +} diff --git a/app/src/main/cpp/mutex.h b/app/src/main/cpp/mutex.h new file mode 100644 index 0000000..5dcff59 --- /dev/null +++ b/app/src/main/cpp/mutex.h @@ -0,0 +1,33 @@ +#ifndef MUTEX_H +#define MUTEX_H + +#include +#include + +#define MUTEX_TIMEDOUT 1 +#define MUTEX_MAXWAIT (~(uint32_t)0) + +typedef struct _mutex { + pthread_mutex_t id; +} Mutex; + +Mutex *CreateMutex(void); +void DestroyMutex(Mutex *mutex); +void DestroyMutexP(Mutex **mutex); +int LockMutex(Mutex *mutex); +int UnlockMutex(Mutex *mutex); + +typedef struct _cond { + pthread_cond_t id; +} Cond; + +Cond *CreateCond(void); +void DestroyCond(Cond *cond); +void DestroyCondP(Cond **mutex); +int CondSignal(Cond *cond); +int CondBroadcast(Cond *cond); +int CondWaitTimeout(Cond *cond, Mutex *mutex, uint32_t ms); +int CondWait(Cond *cond, Mutex *mutex); + +#endif + diff --git a/app/src/main/cpp/native-lib.c b/app/src/main/cpp/native-lib.c new file mode 100644 index 0000000..0350bc2 --- /dev/null +++ b/app/src/main/cpp/native-lib.c @@ -0,0 +1,147 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "debug.h" +#include "msg_queue.h" + +#define tag "test" +#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0]))) + +static MessageQueue msg_queue; +static int running = 0; +static pthread_t tid; +static void *queue_runnable(void *arg); +//static jmethodID on_event_cb_id; +static JavaVM *gJVM = NULL; +static jobject gObj = NULL; + +JNIEXPORT jstring jni_get_string(JNIEnv *env, jobject thiz) { + const char* hello = "Hello, test native code with mwmwatch."; + return (*env)->NewStringUTF(env, hello); +} +static jboolean jni_init(JNIEnv *env, jobject thiz) +{ + gObj = (*env)->NewGlobalRef(env, thiz); + jclass clazz = (*env)->GetObjectClass(env, thiz); + if (clazz == NULL) { + (*env)->ThrowNew(env, "java/lang/NullPointerException", "Unable to find exception class"); + } + //on_event_cb_id = (*env)->GetMethodID(env, clazz, "postEventFromNative", "(IIILjava/lang/Object;)V"); + //if (!on_event_cb_id) return JNI_FALSE; + int ret = pthread_create(&tid, NULL, queue_runnable, NULL); + if (ret != NULL) return JNI_FALSE; + return JNI_TRUE; +} +static jboolean jni_destroy(JNIEnv *env, jobject thiz) +{ + logi(tag, "%s", __func__); + msg_queue_abort(&msg_queue); + running = 0; + msg_queue_destroy(&msg_queue); + return JNI_TRUE; +} +static void on_event_callback(int what, int arg1, int arg2, void *obj, int len) +{ + assert(gJVM != NULL); + JNIEnv *env = NULL; + jboolean isAttached = JNI_FALSE; + + if ((*gJVM)->GetEnv(gJVM, (void**) &env, JNI_VERSION_1_6) < 0) + { + if ((*gJVM)->AttachCurrentThread(gJVM, &env, NULL) < 0) + { + loge(tag, "AttachCurrentThread failed"); + return; + } + isAttached = JNI_TRUE; + } + assert(env != NULL); + jstring jstring1 = NULL; + if (obj) jstring1 = (*env)->NewStringUTF(env, obj); + //(*env)->CallVoidMethod (env, gObj, on_event_cb_id, (jint)what, arg1, arg2, jstring1); + jclass cls = (*env)->GetObjectClass(env, gObj); + if (cls == NULL) { + return; + } + jmethodID static_method_id = (*env)->GetStaticMethodID(env, cls, "postEventFromNative", "(Ljava/lang/Object;IIILjava/lang/Object;)V"); + if (!static_method_id) { + loge(tag, "method id is null"); + return; + } + (*env)->CallStaticVoidMethod(env, cls, static_method_id, gObj, what, arg1,arg2, jstring1); + if (jstring1) (*env)->DeleteLocalRef(env, jstring1); + if (isAttached) (*gJVM)->DetachCurrentThread(gJVM); +} +static void *queue_runnable(void *arg) +{ + pthread_detach(pthread_self()); + msg_queue_init(&msg_queue); + msg_queue_start(&msg_queue); + running = 1; + //logw(tag, "start running..."); + int ret; + while (running) + { + AVMessage message; + ret = msg_queue_get(&msg_queue, &message, 1); + if (ret <= 0) + { + msg_free_res(&message); + continue; + } + logw(tag, "ret=%d, msg what=%d", ret, message.what); + if (message.obj) + { + on_event_callback(message.what, message.arg1, message.arg2, message.obj, message.len); + } + else + { + on_event_callback(message.what, message.arg1, message.arg2, NULL, 0); + } + msg_free_res(&message); + } + logi(tag, "queue runnable over"); + pthread_exit(NULL); +} +static jboolean jni_put_msg(JNIEnv *env, jobject thiz, jint jmsg) +{ + //msg_queue_put_simple1(&msg_queue, what); + char *c = malloc(16); + sprintf(c, "%s %d", "message is ", jmsg); + //loge(tag, "C===%s", c); + msg_queue_put_simple4(&msg_queue, jmsg, 1, 2, c, 16); + if (c) free(c); + return JNI_TRUE; +} +static JNINativeMethod method[] = { + {"nativeInit", "()Z", (void *) jni_init}, + {"getNativeString", "()Ljava/lang/String;", (void*) jni_get_string}, + {"putMessage", "(I)Z", (void*)jni_put_msg}, + {"destroy", "()Z", (void*)jni_destroy} +}; +JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) +{ + JNIEnv *env = NULL; + if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_6) != JNI_OK) return JNI_ERR; + gJVM = vm; + jclass gClass = (*env)->FindClass(env, "com/example/bob/nativemessagequeue/api/MessageClient"); + if (gClass == NULL) return JNI_ERR; + + (*env)->RegisterNatives(env, gClass, method, NELEM(method)); + + return JNI_VERSION_1_6; +} \ No newline at end of file diff --git a/app/src/main/java/com/example/bob/nativemessagequeue/MainActivity.java b/app/src/main/java/com/example/bob/nativemessagequeue/MainActivity.java new file mode 100644 index 0000000..717a032 --- /dev/null +++ b/app/src/main/java/com/example/bob/nativemessagequeue/MainActivity.java @@ -0,0 +1,44 @@ +package com.example.bob.nativemessagequeue; + +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.view.View; +import android.widget.Button; +import android.widget.Toast; + +import com.example.bob.nativemessagequeue.api.IPlayer; +import com.example.bob.nativemessagequeue.api.MessageClient; + +public class MainActivity extends AppCompatActivity { + MessageClient messageClient; + int what = 100; + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + Button button = findViewById(R.id.button); + messageClient = new MessageClient(); + messageClient.setOnCompletionListener(onCompletionListener); + + button.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + messageClient.putMessage(what); + what+=100; + } + }); + } + + private final IPlayer.OnCompletionListener onCompletionListener = new IPlayer.OnCompletionListener() { + @Override + public void onCompletion() { + Toast.makeText(MainActivity.this, "On completion", Toast.LENGTH_LONG).show(); + } + }; + + @Override + protected void onStop() { + super.onStop(); + messageClient.destroy(); + } +} diff --git a/app/src/main/java/com/example/bob/nativemessagequeue/api/AbstractMQ.java b/app/src/main/java/com/example/bob/nativemessagequeue/api/AbstractMQ.java new file mode 100644 index 0000000..f6c17da --- /dev/null +++ b/app/src/main/java/com/example/bob/nativemessagequeue/api/AbstractMQ.java @@ -0,0 +1,28 @@ +package com.example.bob.nativemessagequeue.api; + +/** + * Created by bob on 2018/4/8. + */ + +public abstract class AbstractMQ implements IPlayer{ + private OnCompletionListener onCompletionListener; + private OnPreparedListener onPreparedListener; + + public final void setOnCompletionListener(OnCompletionListener listener){ + onCompletionListener = listener; + } + public final void setOnPreparedListener(OnPreparedListener listener){ + onPreparedListener = listener; + } + + protected final void notifyOnCompletion(){ + if (onCompletionListener != null){ + onCompletionListener.onCompletion(); + } + } + protected final void notifyOnPrepared(){ + if (onPreparedListener != null){ + onPreparedListener.onPrepared(); + } + } +} diff --git a/app/src/main/java/com/example/bob/nativemessagequeue/api/IPlayer.java b/app/src/main/java/com/example/bob/nativemessagequeue/api/IPlayer.java new file mode 100644 index 0000000..52cd951 --- /dev/null +++ b/app/src/main/java/com/example/bob/nativemessagequeue/api/IPlayer.java @@ -0,0 +1,15 @@ +package com.example.bob.nativemessagequeue.api; + +/** + * Created by bob on 2018/4/8. + */ + +public interface IPlayer { + interface OnCompletionListener { + void onCompletion(); + } + + interface OnPreparedListener { + void onPrepared(); + } +} diff --git a/app/src/main/java/com/example/bob/nativemessagequeue/api/MessageClient.java b/app/src/main/java/com/example/bob/nativemessagequeue/api/MessageClient.java new file mode 100644 index 0000000..a069980 --- /dev/null +++ b/app/src/main/java/com/example/bob/nativemessagequeue/api/MessageClient.java @@ -0,0 +1,58 @@ +package com.example.bob.nativemessagequeue.api; + +import android.os.Handler; +import android.os.Message; +import android.util.Log; + +import java.lang.ref.WeakReference; + +/** + * Created by bob on 2018/4/8. + */ + +public class MessageClient extends AbstractMQ { + static String tag = "MessageQueue";//getClass().getSimpleName(); + static { + System.loadLibrary("native-lib"); + } + + private native boolean nativeInit(); + private native String getNativeString(); + public native boolean putMessage(int what); + public native boolean destroy(); + public MessageClient() { + Log.i(tag,"init "+nativeInit()); + } + + private final Handler handler = new Handler(new Handler.Callback() { + @Override + public boolean handleMessage(Message msg) { + Log.e(tag, "what="+msg.what+", arg1="+msg.arg1+",arg2="+msg.arg2+", obj="+msg.obj); + switch (msg.what) { + case 200: + notifyOnCompletion(); + break; + case 300: + notifyOnPrepared(); + break; + } + return false; + } + }); + + private static void postEventFromNative(Object weakThiz, int what, int arg1, int arg2, Object obj) { + if (weakThiz == null) { + Log.e(tag, "weak thiz is null"); + return; + } + WeakReference weakReference = new WeakReference<>((MessageClient) weakThiz); + if (weakReference.get() == null) { + Log.e(tag, "message queue is null"); + return; + } + if (weakReference.get().handler != null) { + Message m = weakReference.get().handler.obtainMessage(what, arg1, arg2, obj); + weakReference.get().handler.sendMessage(m); + } + } +} diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..c7bd21d --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..d5fccc5 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..7e26e11 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,19 @@ + + + +