虽然这篇文章写得很差,因为赶时间,所以就匆匆忙忙地写出来自己作一个笔记。但是我想对大家应该有一点帮助。

1、有关sensor在Java应用程序的编程(以注册多个传感器为例,这程序是我临时弄出来的,可能有错)

packagecom.sensors.acc;

importandroid.app.Activity;

importandroid.os.Bundle;

importandroid.util.Log;

importandroid.widget.TextView;

importandroid.hardware.SensorManager;

importandroid.hardware.Sensor;

importandroid.hardware.SensorEvent;

importandroid.hardware.SensorEventListener;

publicclassaccextendsActivity {

floatx,y,z;

SensorManagersensormanager=null;

SensoraccSensor=null;

SensorlightSensor=null;

SensorproximitySensor=null;

TextViewaccTextView=null;

/** Called when the activity is first created. */

@Override

publicvoidonCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

sensormanager= (SensorManager)getSystemService(SENSOR_SERVICE);

accSensor=sensormanager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);

lightSensor=sensormanager.getDefaultSensor(Sensor.TYPE_LIGHT);

proximitySensor=sensormanager.getDefaultSensor(Sensor.TYPE_PROXIMITY);

accTextView= (TextView)findViewById(R.id.textview_name);

}

SensorEventListenerlsn=newSensorEventListener() {

publicvoidonSensorChanged(SensorEvent e) {

if(e.sensor==accSensor) {

Log.d("sensor","found acc sensor");

x= e.values[SensorManager.DATA_X];

y= e.values[SensorManager.DATA_Y];

z= e.values[SensorManager.DATA_Z];

accTextView.setText("x = "+x+", \ny = "+y+", \nz = "+z);

}

elseif(e.sensor==lightSensor) {

Log.d("sensor","found light sensor");

accTextView.setText("data is "+ e.values[0]);

}

elseif(e.sensor==proximitySensor) {

Log.d("sensor","found proximity sensor");

accTextView.setText("distance is "+ e.values[0]);

}

//Log.d("sensor", "found acc sensor");

//Log.d("sensor", "x = " + x + ", y = " + y + ", z = " + z);

//accTextView.setText("x = " + x + ", \ny = " + y + ", \nz = " + z);

}

publicvoidonAccuracyChanged(Sensor s,intaccuracy) {

}

};

@Override

protectedvoidonResume() {

super.onResume();// register this class as a listener for the orientation and accelerometer sensors

sensormanager.registerListener(lsn,accSensor, SensorManager.SENSOR_DELAY_NORMAL);

sensormanager.registerListener(lsn,lightSensor, SensorManager.SENSOR_DELAY_NORMAL);

sensormanager.registerListener(lsn,proximitySensor, SensorManager.SENSOR_DELAY_NORMAL);

//sensormanager.unregisterListener(lsn);

}

@Override

protectedvoidonStop() {// unregister listener

sensormanager.unregisterListener(lsn, accSensor);

sensormanager.unregisterListener(lsn, lightSensor);

sensormanager.unregisterListener(lsn, proximitySensor);

super.onStop();

}

}

在onCreate()函数中,调用getSystemService(SENSOR_SERVICE)初始化一个SensorManager实例,为什么要用getSystemService函数,而不直接用new SensorManager呢?我们看此函数的实现,在ApplicationContext.java中,

if (SENSOR_SERVICE.equals(name)) {

return getSensorManager();

然后getSensorManager()的实现

private SensorManager getSensorManager() {

synchronized (mSync) {

if (mSensorManager == null) {

mSensorManager = new SensorManager(mMainThread.getHandler().getLooper());

}

}

return mSensorManager;

}

看到没有?初始化SensorManager的时候需要mMainThread.getHandler().getLooper()这个参数,之个应该是用来传递消息用的,在SensorManager类的构造函数中会把此参数传给类成员mMainLooper。如果用new SensorManager()就需要另外获取mainLooper参数传递进去。

2、在android中跟sensor有关的一些文件有SensorManager.java,位于frameworks\base\core\java\android\hardware目录下,SensorService.java,位于frameworks\base\services\java\com\android\server目录下,android_hardware_SensorManager.cpp,位于frameworks\base\core\jni\目录下,与SensorManager.java相对应,com_android_server_SensorService.cpp,在frameworks\base\services\jni\目录下,与SensorService.java相对应。还有SystemServer.java文件,Hardware\Libhardware\Include\Hardware目录下的Sensor.h头文件。另外我们需要根据Sensor.h实现自己的一个源文件,一般取名为sensors_xxx.c或者sensors_xxx.cpp。

3、SensorManager类分析

有几个函数比较重要,必须清晰理解它们的实现,才能了解整个传感器系统的实现。从而更好地去实现硬件抽象层的实现。几个比较重要的函数有构造函数SensorManager(), registerListener()和unregisterListener(),其中registerListener()和unregisterListener()有多个,标志为@Deprecated的是过时的,就不要看了。

(1)构造函数SensorManager(Looper mainLooper)

这个函数首先获取得传感器系统服务,并赋给类成员mSensorService,

mSensorService = ISensorService.Stub.asInterface(

ServiceManager.getService(Context.SENSOR_SERVICE));

这里我要说一句,就是关于这个传感器系统服务,很多书上都说用getSystemService()是获得传感器的系统服务,而它返回的是SensorManager类型,所以以为整个系统都是使用同一个SensorManager类的实例,以为我们在任何地方使用的SensorManager实例都是同一个,它们的公共成员是共享的。但是经过这两天的分析,这种说法是错误的。其实每次调用getSystemService()函数时都初始化一个新的SensorManager实例,而这个SensorManager实例会在构造函数里通过取得传感器系统服务SensorService来实现对下层传感器的一些控制。而这个SensorService才是系统的传感器服务,说服务,不如说它只是SensorService类的一个实例罢了。它只在系统初始化时初始化一次。Android中的系统服务机制应该跟传感器的都差不多一个样,都是由不同的Manager调用下层相同的Service。你可以列举其它的Manager。那它是什么时候初始化呢?它是系统初始化在SystemServer进程里创建的,SystemServer是一个管理很多系统服务的进程,我们转到SystemServer.的main函数里,可以看到一直到调用int2()函数,它会创建一个ServerThread,最终调用AdbSettingsObserver类的run()函数,在run()函数里有这么有一句

// Sensor Service is needed by Window Manager, so this goes first

Log.i(TAG, "Sensor Service");

ServiceManager.addService(Context.SENSOR_SERVICE, new SensorService(context));

这里就创建SensorService实例了。在创建这个实例时会在SensorService构造函数中调用jni函数public SensorService(Context context) {

if (localLOGV) Log.d(TAG, "SensorService startup");

_sensors_control_init();

}

我们看_sensors_control_init();对应的为

static jint

android_init(JNIEnv *env, jclass clazz)

{

sensors_module_t* module;

if (hw_get_module(SENSORS_HARDWARE_MODULE_ID, (const hw_module_t**)&module) == 0) {

if (sensors_control_open(&module->common, &sSensorDevice) == 0) {

const struct sensor_t* list;

int count = module->get_sensors_list(module, &list);

return count;

}

}

return 0;

}

它主要调用了sensor.h中的sensors_control_open()

static inline int sensors_control_open(const struct hw_module_t* module,

struct sensors_control_device_t** device) {

return module->methods->open(module,

SENSORS_HARDWARE_CONTROL, (struct hw_device_t**)device);

}

之后在系统任何地方使用的都是这个SensorService实例。最后run()函数调用Looper.loop();就进行消息循环等待了,这就是SystemServer进程的消息服务了。这才真正叫做系统服务嘛。

我们继续看SensorManager类的构造函数,取得SensorService后,

nativeClassInit();

这是一个jni函数,SensorManager类调用的jni函数都在com_android_server_SensorService.cpp里,我们看这函数

static void

nativeClassInit (JNIEnv *_env, jclass _this)

{

jclass sensorClass = _env->FindClass("android/hardware/Sensor");

SensorOffsets& sensorOffsets = gSensorOffsets;

sensorOffsets.name= _env->GetFieldID(sensorClass, "mName","Ljava/lang/String;");

sensorOffsets.vendor= _env->GetFieldID(sensorClass, "mVendor","Ljava/lang/String;");

sensorOffsets.version= _env->GetFieldID(sensorClass, "mVersion","I");

sensorOffsets.handle= _env->GetFieldID(sensorClass, "mHandle", "I");

sensorOffsets.type= _env->GetFieldID(sensorClass, "mType","I");

sensorOffsets.range= _env->GetFieldID(sensorClass, "mMaxRange","F");

sensorOffsets.resolution= _env->GetFieldID(sensorClass, "mResolution","F");

sensorOffsets.power= _env->GetFieldID(sensorClass, "mPower","F");

}

这个函数只是获取和设置一些信息吧,我们不关心。接着

sensors_module_init();

我们看这函数

static jint

sensors_module_init(JNIEnv *env, jclass clazz)

{

int err = 0;

sensors_module_t const* module;

err = hw_get_module(SENSORS_HARDWARE_MODULE_ID, (const hw_module_t **)&module);

if (err == 0)

sSensorModule = (sensors_module_t*)module;

return err;

}

它获取了sensor的模块信息,并把它赋给sSensorModule全局变量,之后传的modules参数都为这个。

接着在构造函数里

final ArrayList fullList = sFullSensorsList;

int i = 0;

do {

Sensor sensor = new Sensor();

i = sensors_module_get_next_sensor(sensor, i);

if (i>=0) {

Log.d(TAG, "found sensor: " + sensor.getName() +

", handle=" + sensor.getHandle());

sensor.setLegacyType(getLegacySensorType(sensor.getType()));

fullList.add(sensor);

sHandleToSensor.append(sensor.getHandle(), sensor);

}

} while (i>0);

这里主要是通过jni函数sensors_module_get_next_sensor(sensor, i);获取传感器列表,并把它加入自己的fullList列表中。我们看sensors_module_get_next_sensor()函数

static jint

sensors_module_get_next_sensor(JNIEnv *env, jobject clazz, jobject sensor, jint next)

{

if (sSensorModule == NULL)

return 0;

SensorOffsets& sensorOffsets = gSensorOffsets;

const struct sensor_t* list;

int count = sSensorModule->get_sensors_list(sSensorModule, &list);

if (next >= count)

return -1;

list += next;

jstring name = env->NewStringUTF(list->name);

jstring vendor = env->NewStringUTF(list->vendor);

env->SetObjectField(sensor, sensorOffsets.name,name);

env->SetObjectField(sensor, sensorOffsets.vendor,vendor);

env->SetIntField(sensor, sensorOffsets.version,list->version);

env->SetIntField(sensor, sensorOffsets.handle,list->handle);

env->SetIntField(sensor, sensorOffsets.type,list->type);

env->SetFloatField(sensor, sensorOffsets.range,list->maxRange);

env->SetFloatField(sensor, sensorOffsets.resolution, list->resolution);

env->SetFloatField(sensor, sensorOffsets.power,list->power);

next++;

return next

}

它主要是调用HAL层的get_sensors_list()函数取得传感器列表信息。

接着在sensorManger构造函数最后

sSensorThread = new SensorThread();

创建一个SensorThread()线程。但并未运行,但在SensorThread类的构造函数里会执行jni函数sensors_data_init();

我们看此函数static jint

sensors_data_init(JNIEnv *env, jclass clazz)

{

if (sSensorModule == NULL)

return -1;

int err = sensors_data_open(&sSensorModule->common, &sSensorDevice);

return err;

}

它调用了HAL层的sensors_data_open函数,而这个函数位于sensor.h中,它调用的是

static inline int sensors_data_open(const struct hw_module_t* module,

struct sensors_data_device_t** device) {

return module->methods->open(module,

SENSORS_HARDWARE_DATA, (struct hw_device_t**)device);

}

Modules->methods->open函数。而在SensorThread类的析构函数finalize()里会调用

sensors_data_uninit();

static jint

sensors_data_uninit(JNIEnv *env, jclass clazz)

{

int err = 0;

if (sSensorDevice) {

err = sensors_data_close(sSensorDevice);

if (err == 0)

sSensorDevice = 0;

}

return err;

}

在sensor.h里

static inline int sensors_data_close(struct sensors_data_device_t* device) {

return device->common.close(&device->common);

}

那什么时候sSensorThread线程会运行呢?我们在下面看registerListener()函数。

(2)public boolean registerListener(SensorEventListener listener, Sensor sensor, int rate) {

return registerListener(listener, sensor, rate, null);

}

它调用的是public boolean registerListener(SensorEventListener listener, Sensor sensor, int rate,

Handler handler)

在这函数中,先验证rate,然后检测注册的listener在不在本类的sListeners列表中。

for (ListenerDelegate i : sListeners) {

if (i.getListener() == listener) {

l = i;

break;

}

}

如果不在就申请一个listener,并把它加入全局列表sListener中,并调用mSensorService的enableSensor()函数使能传感器,这个enableSensor()函数最终会调用HAL层的active函数和set_delay()函数,使用后然后判断sListener列表是否为空,当然,第一次为空时加入一个新的listener就不为空了,此时就执行sSensorThread的startLocked运行sSensorThread线程了

l = new ListenerDelegate(listener, sensor, handler);

result = mSensorService.enableSensor(l, name, handle, delay);

if (result) {

sListeners.add(l);

sListeners.notify();

}

if (!sListeners.isEmpty()) {

sSensorThread.startLocked(mSensorService);

}

另一方面,如果注册的listener在sListeners列表中,则先调用mSensorService的enableSensor()函数使能传感器,然后把注册的传感器加入到已存在的listener中。

result = mSensorService.enableSensor(l, name, handle, delay);

if (result) {

l.addSensor(sensor);

}

接下来我们看看startLocked函数,它在SensorThread中,

void startLocked(ISensorService service) {

try {

if (mThread == null) {

Bundle dataChannel = service.getDataChannel();

mThread = new Thread(new SensorThreadRunnable(dataChannel),

SensorThread.class.getName());

mThread.start();

}

} catch (RemoteException e) {

Log.e(TAG, "RemoteException in startLocked: ", e);

}

}

第一次mThread为null,然后它调用了service.getDataChannel()函数,此函数在SensorService类中,主要调用了jni函数_sensors_control_open(),

public Bundle getDataChannel() throws RemoteException {

// synchronize so we do not require sensor HAL to be thread-safe.

synchronized(mListeners) {

return _sensors_control_open();

}

}

SensorService类中调用的jni函数主要都在com_android_server_SensorService.cpp文件中,我们看一下这个函数

static jobject

android_open(JNIEnv *env, jclass clazz)

{

native_handle_t* handle = sSensorDevice->open_data_source(sSensorDevice);

if (!handle) {

return NULL;

}

// new Bundle()

jobject bundle = env->NewObject(

gBundleOffsets.mClass,

gBundleOffsets.mConstructor);

if (handle->numFds > 0) {

jobjectArray fdArray = env->NewObjectArray(handle->numFds,

gParcelFileDescriptorOffsets.mClass, NULL);

for (int i = 0; i < handle->numFds; i++) {

// new FileDescriptor()

jobject fd = env->NewObject(gFileDescriptorOffsets.mClass,

gFileDescriptorOffsets.mConstructor);

env->SetIntField(fd, gFileDescriptorOffsets.mDescriptor, handle->data[i]);

// new ParcelFileDescriptor()

jobject pfd = env->NewObject(gParcelFileDescriptorOffsets.mClass,

gParcelFileDescriptorOffsets.mConstructor, fd);

env->SetObjectArrayElement(fdArray, i, pfd);

}

// bundle.putParcelableArray("fds", fdArray);

env->CallVoidMethod(bundle, gBundleOffsets.mPutParcelableArray,

env->NewStringUTF("fds"), fdArray);

}

if (handle->numInts > 0) {

jintArray intArray = env->NewIntArray(handle->numInts);

env->SetIntArrayRegion(intArray, 0, handle->numInts, &handle->data[handle->numInts]);

// bundle.putIntArray("ints", intArray);

env->CallVoidMethod(bundle, gBundleOffsets.mPutIntArray,

env->NewStringUTF("ints"), intArray);

}

// delete the file handle, but don't close any file descriptors

native_handle_delete(handle);

return bundle;

}

它主要调用了HAL层的open_data_source()函数。取得一些文件描述符等信息。

接下来SensorThread创建一个线程,调用start()就进入SensorThreadRunnable类的run()函数了,所以我们接着去看run()函数,它首先调用open()函数

if (!open()) {

return;

}

在open()函数中调用了jni函数sensors_data_open(fds, ints);

static jint

sensors_data_open(JNIEnv *env, jclass clazz, jobjectArray fdArray, jintArray intArray)

{

jclass FileDescriptor = env->FindClass("java/io/FileDescriptor");

jfieldID fieldOffset = env->GetFieldID(FileDescriptor, "descriptor", "I");

int numFds = (fdArray ? env->GetArrayLength(fdArray) : 0);

int numInts = (intArray ? env->GetArrayLength(intArray) : 0);

native_handle_t* handle = native_handle_create(numFds, numInts);

int offset = 0;

for (int i = 0; i < numFds; i++) {

jobject fdo = env->GetObjectArrayElement(fdArray, i);

if (fdo) {

handle->data[offset++] = env->GetIntField(fdo, fieldOffset);

} else {

handle->data[offset++] = -1;

}

}

if (numInts > 0) {

jint* ints = env->GetIntArrayElements(intArray, 0);

for (int i = 0; i < numInts; i++) {

handle->data[offset++] = ints[i];

}

env->ReleaseIntArrayElements(intArray, ints, 0);

}

// doesn't take ownership of the native handle

return sSensorDevice->data_open(sSensorDevice, handle);

}

这函数最终调用了HAL层的data_open(),之后run()函数就进入一个while循环了。

while (true) {

// wait for an event

final int sensor = sensors_data_poll(values, status, timestamp);

int accuracy = status[0];

synchronized (sListeners) {

if (sensor == -1 || sListeners.isEmpty()) {

if (sensor == -1) {

// we lost the connection to the event stream. this happens

// when the last listener is removed.

Log.d(TAG, "_sensors_data_poll() failed, we bail out.");

}

// we have no more listeners or polling failed, terminate the thread

sensors_data_close();

mThread = null;

break;

}

final Sensor sensorObject = sHandleToSensor.get(sensor);

if (sensorObject != null) {

// report the sensor event to all listeners that

// care about it.

final int size = sListeners.size();

for (int i=0 ; i

ListenerDelegate listener = sListeners.get(i);

if (listener.hasSensor(sensorObject)) {

// this is asynchronous (okay to call

// with sListeners lock held).

listener.onSensorChangedLocked(sensorObject,

values, timestamp, accuracy);

}

}

}

}

它调用了jni函数sensors_data_poll()一直读数据。

static jint

sensors_data_poll(JNIEnv *env, jclass clazz,

jfloatArray values, jintArray status, jlongArray timestamp)

{

sensors_data_t data;

int res = sSensorDevice->poll(sSensorDevice, &data);

if (res >= 0) {

jint accuracy = data.vector.status;

env->SetFloatArrayRegion(values, 0, 3, data.vector.v);

env->SetIntArrayRegion(status, 0, 1, &accuracy);

env->SetLongArrayRegion(timestamp, 0, 1, &data.time);

}

return res;

}

把传感器得到的值都放在value数组中,根据返回的传感器标志sensor,把它分派给在sListener列表中所有的listener,如果listener中有监听这个sensor,就把它分派给这个listener,此时就会引起onSensorChange()了。

好了,获取传感器数据主要是这样一个途径。最后我们去分析一下unregisterListener()函数。

private void unregisterListener(Object listener) {

if (listener == null) {

return;

}

try {

synchronized (sListeners) {

final int size = sListeners.size();

for (int i=0 ; i

ListenerDelegate l = sListeners.get(i);

if (l.getListener() == listener) {

// disable all sensors for this listener

for (Sensor sensor : l.getSensors()) {

String name = sensor.getName();

int handle = sensor.getHandle();

mSensorService.enableSensor(l, name, handle, SENSOR_DISABLE);

}

sListeners.remove(i);

break;

}

}

}

} catch (RemoteException e) {

Log.e(TAG, "RemoteException in unregisterListener: ", e);

}

}

不用想这个函数会做一些与registerListener相反的事情,至少差不多。它先在sListeners列表中找到这个listener,然后先调用enableSensor()函数禁止这个传感器。我们跟踪一下这函数,在SensorService类中。

synchronized(mListeners) {

if (enable!=SENSOR_DISABLE && !_sensors_control_activate(sensor, true)) {

Log.w(TAG, "could not enable sensor " + sensor);

return false;

}

Listener l = null;

int minDelay = enable;

for (Listener listener : mListeners) {

if (binder == listener.mToken) {

l = listener;

}

if (minDelay > listener.mDelay)

minDelay = listener.mDelay;

}

if (l == null && enable!=SENSOR_DISABLE) {

l = new Listener(binder);

binder.linkToDeath(l, 0);

mListeners.add(l);

mListeners.notify();

}

if (l == null) {

// by construction, this means we're disabling a listener we

// don't know about...

Log.w(TAG, "listener with binder " + binder +

", doesn't exist (sensor=" + name + ", id=" + sensor + ")");

return false;

}

if (minDelay >= 0) {

_sensors_control_set_delay(minDelay);

}

if (enable != SENSOR_DISABLE) {

l.addSensor(sensor, enable);

} else {

l.removeSensor(sensor);

deactivateIfUnusedLocked(sensor);

if (l.mSensors == 0) {

mListeners.remove(l);

binder.unlinkToDeath(l, 0);

mListeners.notify();

}

}

if (mListeners.size() == 0) {

_sensors_control_wake();

_sensors_control_close();

}

}

return true;

你们看到它的实现了吧。如果enable是true的话,就调用_sensors_control_activate(),如果是false的话,就调用deactivateIfUnusedLocked(),它们最终都会调用HAL层的active()函数。最后,如果是禁止传感器的话,如果mListeners为空了,它就会调用

_sensors_control_wake();

_sensors_control_close();

这两个jni函数,最终会调用HAL层的wake()和close_data_source()函数。当调用wake()函数时,会使SensorManager类线程的run()函数中的sensor_data_poll()函数立即返回,此时在run()函数中调用sensors_data_close();最终会调用HAL层的data_close()函数。至此,一个传感器从初始到结束的流程就分析完了。

所以在java使用一个传感器在HAL层具体调用的函数流程为:

首先,sensors_control_open(),只在系统初始化时调用一次。用来初始化control_device结构体。

以下的是每次使用传感器一般经过的流程,注意,是一般而已,有些并不执行

(1)sensors_data_open

(2)get_sensors_list

(3)activate

(4)set_delay

(5)open_data_source

(6)data_open

(7)poll

一直读数据。。。。。。。。。。。

退出时

(8)activate

(9)sensors_control_close

(10)data_close

当然其它的细节你们可以继续去研究。也可以在我博客里提出来大家交流和讨论一下!

android sensor源码,阅读android有关sensor的源码总结 - JerryMo06的专栏 - CSDN博客相关推荐

  1. CSDN博客文章阅读模式插件(附源码)

    插件地址:https://greasyfork.org/zh-CN/scripts/380667-csdn%E5%8D%9A%E5%AE%A2%E9%98%85%E8%AF%BB%E6%A8%A1%E ...

  2. Android应用开发-小巫CSDN博客客户端UI篇

    Android应用开发-小巫CSDN博客客户端UI篇 上一篇是给童鞋们介绍整个项目的概况,从这篇博文开始,后续也会详细介绍整个客户端的开发,但不会贴很多代码,我会贴核心代码然后提供实现思路,想看里面更 ...

  3. Android应用开发-小巫CSDN博客客户端Jsoup篇

    Android应用开发-小巫CSDN博客客户端Jsoup篇 距上一篇博客已经过去了两个星期,小巫也觉得非常抱歉,因为在忙着做另外一个项目,几乎抽不出空来,这不小巫会把剩下的博文全部在国庆补上.本篇博客 ...

  4. Android应用开发-小巫CSDN博客客户端之获取评论列表

    Android应用开发-小巫CSDN博客客户端之获取评论列表 上一篇博客介绍了博文详细内容的业务逻辑实现,本篇博客介绍小巫CSDN博客客户端的最后一项功能,获取评论列表,这个功能的实现跟前面获取文章列 ...

  5. Android应用开发-小巫CSDN博客客户端之显示博文详细内容

    Android应用开发-小巫CSDN博客客户端之显示博文详细内容 上篇博文给大家介绍的是如何嵌入有米广告并且获取收益,本篇博客打算讲讲关于如何在一个ListView里显示博文的详细信息,这个可能是童鞋 ...

  6. Android应用开发-小巫CSDN博客客户端开发开篇,玩转MySQL

    本篇博客是关于这款应用的开发的起始篇,主要简单介绍一下整个项目的概况,整体大纲如下: 1. 项目起因 2. 项目效果展示 3. 项目文档结构和依赖库说明 4. 项目功能简介 5. 系列博客分享后期计划 ...

  7. Android应用开发-小巫CSDN博客客户端之嵌入有米广告

    Android应用开发-小巫CSDN博客客户端之嵌入有米广告 上一篇博客给大家介绍如何集成友盟社会化组件,本篇继续带来干货,教大家如何嵌入广告到应用中去.小巫自称专业对接30年,熟悉各大渠道SDK的接 ...

  8. 自己动手编写CSDN博客备份工具-blogspider之源码分析(3)

    作者:gzshun. 原创作品,转载请标明出处! 来源:http://blog.csdn.net/gzshun 周星驰:剪头发不应该看别人怎么剪就发神经跟流行,要配合啊!你看你的发型,完全不配合你的脸 ...

  9. Android应用开发-小巫CSDN博客客户端UI篇,kotlin安卓开发教程视频

    ** ** (图5-博文评论列表) 以上给大家展示的是小巫CSDN博客客户端的主要界面效果,下面来讲解如何布局这样的界面: 启动界面布局 /BlogClient/res/layout/splash.x ...

最新文章

  1. 以 Kubernetes 为代表的容器技术,已成为云计算的新界面
  2. 8148和8127中的ezsdk和dvrsdk
  3. python万年历差农历程序_公历转农历的python实现
  4. ffplay分析 (暂停 / 播放处理)
  5. Canvas Clock
  6. 实践教程 | Pytorch 模型的保存与迁移
  7. 百度下载工具我用这两个(目前)
  8. sqlserver200864位下载_sql server 2008 r2中文版
  9. springboot GeoLite2-City.mmdb实现通过IP地址获取经纬度以及该IP的所属地区
  10. 十进制计算机算法,计算机知识--二进制,十进制,十六制算法
  11. Excel中文转拼音
  12. 争做新时代好少年主题团日活动PPT模板
  13. Grafana监控群晖NAS
  14. 新装修的房子多久能入住
  15. 夺命聘礼【三】- 原创中篇小说
  16. 选择排序-简单选择排序
  17. 图论:SPFA 算法详解( 算法竞赛入门到进阶) HDU 2544 链式前向星 【提供ACM模板+图解,不会都难!】
  18. 计算机毕业设计 SSM校园拼车系统 拼车出行管理系统 滴滴打车管理系统Java Vue MySQL数据库 远程调试 代码讲解
  19. [华为OD 004] 完美走位
  20. mysql中新建数据库create table的COLLATE是什么?

热门文章

  1. 【Elasticsearch】 Full text queries query_string 等 字符串查询
  2. 【SpringCloud】Ribbon 负载均衡
  3. 【Flink】Flink 时间之 timerService().registerEventTimeTimer 主要做了什么
  4. 【Antlr】Antlr 自动错误恢复机制
  5. 03-git上传大项目的时很慢
  6. 使用eclipse遇到的unable to install breakpoint的问题
  7. SQL入门-Navicat的基本使用
  8. mysql主码列允许null_mysql的一些基本操作
  9. 程序员学会精刷LeetCode后,会变得有多强...
  10. java 语法检查_java编译期间的语法检查