android6.0 power显示(亮度等)深入分析(二)DisplayManagerService
2016-06-04 15:45
621 查看
上篇博客我们分析了,PowerManagerService和DisplayPowerController这两个类,我也提到了和android5.1的变化,把背光这块放到了DisplayManagerService中了,之前这块没有分析过,今天分析下DisplayManagerService和背光的关系。
看了DisplayManagerService的注释,发现现在所以的显示设备都放在DisplayManagerService管理,wifiDisplay,defaultDisplay(背光)。
我们再来看onStart函数,publish了一个BinderService和LocalService,还有发送了一个消息。
@Override
public void onStart() {
mHandler.sendEmptyMessage(MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER);
publishBinderService(Context.DISPLAY_SERVICE, new BinderService(),
true /*allowIsolated*/);
publishLocalService(DisplayManagerInternal.class, new LocalService());
}我们看消息处理,就是调用了registerDefaultDisplayAdapter函数:
registerDefaultDisplayAdapter函数
再来看看registerDisplayAdapterLocked
这里就是register了DefaultDisplay的适配层,就是和背光相关的。在新建LocalDisplayAdapter的时候我们把mDisplayAdapterListener传过去了。
上面又紧跟着调用了registerLocked函数
tryConnectDisplayLocked函数,先是看传入的builtInDisplayId是否支持,一个是main,一个是hdmi的。
然后再去查找这个LocalDisplayDevice,如果是找到了需要更新下configs,没找到需要新建一个LocalDisplayDevice。最后都调用了sendDisplayDeviceEventLocked函数。
我们再来看LocalDisplayDevice,如果传入的是BUILT_IN_DISPLAY_ID_MAIN就是背光的,我们获取背光的Light,保存在mBackLight变量。
然后上面函数调用了sendDisplayDeviceEventLocked函数,就是调用了传入的参数DisplayAdapterListener
如果是新建就调用了handleDisplayDeviceAdded函数,
我们先来看看handleDisplayDeviceAdded,最后将device保存在了mDisplayDevices中。
我们再来看看requestGlobalDisplayStateInternal函数:
再看看applyGlobalDisplayStateLocked函数,最后遍历device调用updateDisplayStateLocked函数
updateDisplayStateLocked函数调用device的requestDisplayStateLocked返回是Runnable,最后放在workQueue队列中
我们再来看看LocalDisplayDevice的requestDisplayStateLocked函数
上面函数返回一个Runnable放在workQueue,在Runnable 中会调用mBacklight.setBrightness设置背光。
之前是将Runnable接口都放在了mTempDisplayStateWorkQueue中,然后遍历调用了run函数。最后就调用到了LocalDisplayDevice的Runnable接口中设置背光了。
我们先来看看LightsService
setLightLocked中最后调用了setLight_native函数。
setLight_native函数如下,其主要也是依赖上面传下来的ptr,作为devices的指针。
而mNativePointer也是调用了init_native函数
这就到驱动了,最终到hardware目录下有个lights.c文件有下面函数
static int open_lights(const struct hw_module_t *module, char const *name,
struct hw_device_t **device)
{
int (*set_light)(struct light_device_t *dev,
struct light_state_t const *state);
if (0 == strcmp(LIGHT_ID_BACKLIGHT, name))
set_light = set_light_backlight;
else if (0 == strcmp(LIGHT_ID_NOTIFICATIONS, name))
set_light = set_light_leds_notifications;
else if (0 == strcmp(LIGHT_ID_ATTENTION, name))
set_light = set_light_leds_attention;
else if (0 == strcmp(LIGHT_ID_BATTERY,name))
set_light = set_light_leds_battery;
else if (0 == strcmp(LIGHT_ID_BUTTONS,name))
set_light = set_light_keyboard;
else
return -EINVAL;
pthread_once(&g_init, init_g_lock);
struct light_device_t *dev = malloc(sizeof(struct light_device_t));
memset(dev, 0, sizeof(*dev));
dev->common.tag = HARDWARE_DEVICE_TAG;
dev->common.version = 0;
dev->common.module = (struct hw_module_t *)module;
dev->common.close = (int (*)(struct hw_device_t *))close_lights;
dev->set_light = set_light;
*device = (struct hw_device_t *)dev;
return 0;
}下面我们再来看set_light_backlight函数:
其中
这样最后设置背光就是往这个节点里面写值,我们可以看下这个节点值。
这篇博客我们主要分析了,DisplayMangerService是如何设置背光节点的,以及一些hal层的代码。
看了DisplayManagerService的注释,发现现在所以的显示设备都放在DisplayManagerService管理,wifiDisplay,defaultDisplay(背光)。
一、DisplayManagerService注册localDisplay的适配层
我们先来看构造函数:public DisplayManagerService(Context context) { super(context); mContext = context; mHandler = new DisplayManagerHandler(DisplayThread.get().getLooper());//消息处理 mUiHandler = UiThread.getHandler(); mDisplayAdapterListener = new DisplayAdapterListener();//display适配层监视器 mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false); PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mGlobalDisplayBrightness = pm.getDefaultScreenBrightnessSetting();//成员变量屏幕亮度 }
我们再来看onStart函数,publish了一个BinderService和LocalService,还有发送了一个消息。
@Override
public void onStart() {
mHandler.sendEmptyMessage(MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER);
publishBinderService(Context.DISPLAY_SERVICE, new BinderService(),
true /*allowIsolated*/);
publishLocalService(DisplayManagerInternal.class, new LocalService());
}我们看消息处理,就是调用了registerDefaultDisplayAdapter函数:
@Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER: registerDefaultDisplayAdapter(); break;
registerDefaultDisplayAdapter函数
private void registerDefaultDisplayAdapter() { // Register default display adapter. synchronized (mSyncRoot) { registerDisplayAdapterLocked(new LocalDisplayAdapter( mSyncRoot, mContext, mHandler, mDisplayAdapterListener)); } }
再来看看registerDisplayAdapterLocked
private void registerDisplayAdapterLocked(DisplayAdapter adapter) { mDisplayAdapters.add(adapter); adapter.registerLocked(); }
这里就是register了DefaultDisplay的适配层,就是和背光相关的。在新建LocalDisplayAdapter的时候我们把mDisplayAdapterListener传过去了。
二、LocalDisplayAdapter & LocalDisplayDevice
LocalDisplayAdapter构造函数调用了父类的,而父类也就是保存了变量public LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context, Handler handler, Listener listener) { super(syncRoot, context, handler, listener, TAG); }
上面又紧跟着调用了registerLocked函数
public void registerLocked() { super.registerLocked(); mHotplugReceiver = new HotplugDisplayEventReceiver(getHandler().getLooper()); for (int builtInDisplayId : BUILT_IN_DISPLAY_IDS_TO_SCAN) { tryConnectDisplayLocked(builtInDisplayId); } }
tryConnectDisplayLocked函数,先是看传入的builtInDisplayId是否支持,一个是main,一个是hdmi的。
private void tryConnectDisplayLocked(int builtInDisplayId) { IBinder displayToken = SurfaceControl.getBuiltInDisplay(builtInDisplayId); if (displayToken != null) { SurfaceControl.PhysicalDisplayInfo[] configs = SurfaceControl.getDisplayConfigs(displayToken); if (configs == null) { // There are no valid configs for this device, so we can't use it Slog.w(TAG, "No valid configs found for display device " + builtInDisplayId); return; } int activeConfig = SurfaceControl.getActiveConfig(displayToken); if (activeConfig < 0) { // There is no active config, and for now we don't have the // policy to set one. Slog.w(TAG, "No active config found for display device " + builtInDisplayId); return; } LocalDisplayDevice device = mDevices.get(builtInDisplayId); if (device == null) { // Display was added. device = new LocalDisplayDevice(displayToken, builtInDisplayId, configs, activeConfig); mDevices.put(builtInDisplayId, device); sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED); } else if (device.updatePhysicalDisplayInfoLocked(configs, activeConfig)) { // Display properties changed. sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_CHANGED); } } else { // The display is no longer available. Ignore the attempt to add it. // If it was connected but has already been disconnected, we'll get a // disconnect event that will remove it from mDevices. } }
然后再去查找这个LocalDisplayDevice,如果是找到了需要更新下configs,没找到需要新建一个LocalDisplayDevice。最后都调用了sendDisplayDeviceEventLocked函数。
我们再来看LocalDisplayDevice,如果传入的是BUILT_IN_DISPLAY_ID_MAIN就是背光的,我们获取背光的Light,保存在mBackLight变量。
public LocalDisplayDevice(IBinder displayToken, int builtInDisplayId, SurfaceControl.PhysicalDisplayInfo[] physicalDisplayInfos, int activeDisplayInfo) { super(LocalDisplayAdapter.this, displayToken, UNIQUE_ID_PREFIX + builtInDisplayId); mBuiltInDisplayId = builtInDisplayId; updatePhysicalDisplayInfoLocked(physicalDisplayInfos, activeDisplayInfo); if (mBuiltInDisplayId == SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) { LightsManager lights = LocalServices.getService(LightsManager.class); mBacklight = lights.getLight(LightsManager.LIGHT_ID_BACKLIGHT); } else { mBacklight = null; } }
然后上面函数调用了sendDisplayDeviceEventLocked函数,就是调用了传入的参数DisplayAdapterListener
protected final void sendDisplayDeviceEventLocked( final DisplayDevice device, final int event) { mHandler.post(new Runnable() { @Override public void run() { mListener.onDisplayDeviceEvent(device, event); } }); }
如果是新建就调用了handleDisplayDeviceAdded函数,
private final class DisplayAdapterListener implements DisplayAdapter.Listener { @Override public void onDisplayDeviceEvent(DisplayDevice device, int event) { switch (event) { case DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED: handleDisplayDeviceAdded(device); break; case DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED: handleDisplayDeviceChanged(device); break; case DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED: handleDisplayDeviceRemoved(device); break; } } @Override public void onTraversalRequested() { synchronized (mSyncRoot) { scheduleTraversalLocked(false); } } }
我们先来看看handleDisplayDeviceAdded,最后将device保存在了mDisplayDevices中。
private void handleDisplayDeviceAdded(DisplayDevice device) { synchronized (mSyncRoot) { handleDisplayDeviceAddedLocked(device); } } private void handleDisplayDeviceAddedLocked(DisplayDevice device) { DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); if (mDisplayDevices.contains(device)) { Slog.w(TAG, "Attempted to add already added display device: " + info); return; } Slog.i(TAG, "Display device added: " + info); device.mDebugLastLoggedDeviceInfo = info; mDisplayDevices.add(device); addLogicalDisplayLocked(device); Runnable work = updateDisplayStateLocked(device); if (work != null) { work.run(); } scheduleTraversalLocked(false); }
三、设置背光
现在我们在上篇博客不是说背光的调制最后是在DisplayManagerService中,是在下面函数的requestGlobalDisplayStateInternal中调用的public void initPowerManagement(final DisplayPowerCallbacks callbacks, Handler handler, SensorManager sensorManager) { synchronized (mSyncRoot) { DisplayBlanker blanker = new DisplayBlanker() { @Override public void requestDisplayState(int state, int brightness) { // The order of operations is important for legacy reasons. if (state == Display.STATE_OFF) { requestGlobalDisplayStateInternal(state, brightness); } callbacks.onDisplayStateChange(state); if (state != Display.STATE_OFF) { requestGlobalDisplayStateInternal(state, brightness); } } }; mDisplayPowerController = new DisplayPowerController( mContext, callbacks, handler, sensorManager, blanker); } }
我们再来看看requestGlobalDisplayStateInternal函数:
private void requestGlobalDisplayStateInternal(int state, int brightness) { if (state == Display.STATE_UNKNOWN) { state = Display.STATE_ON; } if (state == Display.STATE_OFF) { brightness = PowerManager.BRIGHTNESS_OFF; } else if (brightness < 0) { brightness = PowerManager.BRIGHTNESS_DEFAULT; } else if (brightness > PowerManager.BRIGHTNESS_ON) { brightness = PowerManager.BRIGHTNESS_ON; } synchronized (mTempDisplayStateWorkQueue) { try { // Update the display state within the lock. // Note that we do not need to schedule traversals here although it // may happen as a side-effect of displays changing state. synchronized (mSyncRoot) { if (mGlobalDisplayState == state && mGlobalDisplayBrightness == brightness) { return; // no change } Trace.traceBegin(Trace.TRACE_TAG_POWER, "requestGlobalDisplayState(" + Display.stateToString(state) + ", brightness=" + brightness + ")"); mGlobalDisplayState = state; mGlobalDisplayBrightness = brightness; applyGlobalDisplayStateLocked(mTempDisplayStateWorkQueue); } // Setting the display power state can take hundreds of milliseconds // to complete so we defer the most expensive part of the work until // after we have exited the critical section to avoid blocking other // threads for a long time. for (int i = 0; i < mTempDisplayStateWorkQueue.size(); i++) { mTempDisplayStateWorkQueue.get(i).run(); } Trace.traceEnd(Trace.TRACE_TAG_POWER); } finally { mTempDisplayStateWorkQueue.clear(); } } }
再看看applyGlobalDisplayStateLocked函数,最后遍历device调用updateDisplayStateLocked函数
private void applyGlobalDisplayStateLocked(List<Runnable> workQueue) { final int count = mDisplayDevices.size(); for (int i = 0; i < count; i++) { DisplayDevice device = mDisplayDevices.get(i); Runnable runnable = updateDisplayStateLocked(device); if (runnable != null) { workQueue.add(runnable); } } }
updateDisplayStateLocked函数调用device的requestDisplayStateLocked返回是Runnable,最后放在workQueue队列中
private Runnable updateDisplayStateLocked(DisplayDevice device) { // Blank or unblank the display immediately to match the state requested // by the display power controller (if known). DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); if ((info.flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) { return device.requestDisplayStateLocked(mGlobalDisplayState, mGlobalDisplayBrightness); } return null; }
我们再来看看LocalDisplayDevice的requestDisplayStateLocked函数
public Runnable requestDisplayStateLocked(final int state, final int brightness) { // Assume that the brightness is off if the display is being turned off. assert state != Display.STATE_OFF || brightness == PowerManager.BRIGHTNESS_OFF; final boolean stateChanged = (mState != state); final boolean brightnessChanged = (mBrightness != brightness) && mBacklight != null; if (stateChanged || brightnessChanged) { final int displayId = mBuiltInDisplayId; final IBinder token = getDisplayTokenLocked(); final int oldState = mState; if (stateChanged) { mState = state;// 状态 updateDeviceInfoLocked(); } if (brightnessChanged) { mBrightness = brightness;//保存亮度 } // Defer actually setting the display state until after we have exited // the critical section since it can take hundreds of milliseconds // to complete. return new Runnable() { @Override public void run() { // Exit a suspended state before making any changes. int currentState = oldState; if (Display.isSuspendedState(oldState) || oldState == Display.STATE_UNKNOWN) { if (!Display.isSuspendedState(state)) { setDisplayState(state); currentState = state; } else if (state == Display.STATE_DOZE_SUSPEND || oldState == Display.STATE_DOZE_SUSPEND) { setDisplayState(Display.STATE_DOZE); currentState = Display.STATE_DOZE; } else { return; // old state and new state is off } } // Apply brightness changes given that we are in a non-suspended state. if (brightnessChanged) { setDisplayBrightness(brightness);//设置亮度 } // Enter the final desired state, possibly suspended. if (state != currentState) { setDisplayState(state); } } private void setDisplayState(int state) { if (DEBUG) { Slog.d(TAG, "setDisplayState(" + "id=" + displayId + ", state=" + Display.stateToString(state) + ")"); } Trace.traceBegin(Trace.TRACE_TAG_POWER, "setDisplayState(" + "id=" + displayId + ", state=" + Display.stateToString(state) + ")"); try { final int mode = getPowerModeForState(state); SurfaceControl.setDisplayPowerMode(token, mode); } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); } } private void setDisplayBrightness(int brightness) { if (DEBUG) { Slog.d(TAG, "setDisplayBrightness(" + "id=" + displayId + ", brightness=" + brightness + ")"); } Trace.traceBegin(Trace.TRACE_TAG_POWER, "setDisplayBrightness(" + "id=" + displayId + ", brightness=" + brightness + ")"); try { mBacklight.setBrightness(brightness);//真正的设置背光 } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); } } }; } return null; }
上面函数返回一个Runnable放在workQueue,在Runnable 中会调用mBacklight.setBrightness设置背光。
之前是将Runnable接口都放在了mTempDisplayStateWorkQueue中,然后遍历调用了run函数。最后就调用到了LocalDisplayDevice的Runnable接口中设置背光了。
synchronized (mSyncRoot) { if (mGlobalDisplayState == state && mGlobalDisplayBrightness == brightness) { return; // no change } Trace.traceBegin(Trace.TRACE_TAG_POWER, "requestGlobalDisplayState(" + Display.stateToString(state) + ", brightness=" + brightness + ")"); mGlobalDisplayState = state; mGlobalDisplayBrightness = brightness; applyGlobalDisplayStateLocked(mTempDisplayStateWorkQueue); } // Setting the display power state can take hundreds of milliseconds // to complete so we defer the most expensive part of the work until // after we have exited the critical section to avoid blocking other // threads for a long time. for (int i = 0; i < mTempDisplayStateWorkQueue.size(); i++) { mTempDisplayStateWorkQueue.get(i).run(); }
四、背光hal层
我们先来看看LightsServicepublic class LightsService extends SystemService { static final String TAG = "LightsService"; static final boolean DEBUG = false; final LightImpl mLights[] = new LightImpl[LightsManager.LIGHT_ID_COUNT]; private final class LightImpl extends Light { private LightImpl(int id) { mId = id; } @Override public void setBrightness(int brightness) { setBrightness(brightness, BRIGHTNESS_MODE_USER); } @Override public void setBrightness(int brightness, int brightnessMode) { synchronized (this) { int color = brightness & 0x000000ff; color = 0xff000000 | (color << 16) | (color << 8) | color; setLightLocked(color, LIGHT_FLASH_NONE, 0, 0, brightnessMode); } }
setLightLocked中最后调用了setLight_native函数。
private void setLightLocked(int color, int mode, int onMS, int offMS, int brightnessMode) { if (color != mColor || mode != mMode || onMS != mOnMS || offMS != mOffMS) { if (DEBUG) Slog.v(TAG, "setLight #" + mId + ": color=#" + Integer.toHexString(color)); mColor = color; mMode = mode; mOnMS = onMS; mOffMS = offMS; Trace.traceBegin(Trace.TRACE_TAG_POWER, "setLight(" + mId + ", 0x" + Integer.toHexString(color) + ")"); try { setLight_native(mNativePointer, mId, color, mode, onMS, offMS, brightnessMode); } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); } } }
setLight_native函数如下,其主要也是依赖上面传下来的ptr,作为devices的指针。
static void setLight_native(JNIEnv* /* env */, jobject /* clazz */, jlong ptr, jint light, jint colorARGB, jint flashMode, jint onMS, jint offMS, jint brightnessMode) { Devices* devices = (Devices*)ptr; light_state_t state; if (light < 0 || light >= LIGHT_COUNT || devices->lights[light] == NULL) { return ; } memset(&state, 0, sizeof(light_state_t)); state.color = colorARGB; state.flashMode = flashMode; state.flashOnMS = onMS; state.flashOffMS = offMS; state.brightnessMode = brightnessMode; { ALOGD_IF_SLOW(50, "Excessive delay setting light"); devices->lights[light]->set_light(devices->lights[light], &state); } }
而mNativePointer也是调用了init_native函数
static jlong init_native(JNIEnv* /* env */, jobject /* clazz */) { int err; hw_module_t* module; Devices* devices; devices = (Devices*)malloc(sizeof(Devices)); err = hw_get_module(LIGHTS_HARDWARE_MODULE_ID, (hw_module_t const**)&module); if (err == 0) { devices->lights[LIGHT_INDEX_BACKLIGHT] = get_device(module, LIGHT_ID_BACKLIGHT); devices->lights[LIGHT_INDEX_KEYBOARD] = get_device(module, LIGHT_ID_KEYBOARD); devices->lights[LIGHT_INDEX_BUTTONS] = get_device(module, LIGHT_ID_BUTTONS); devices->lights[LIGHT_INDEX_BATTERY] = get_device(module, LIGHT_ID_BATTERY); devices->lights[LIGHT_INDEX_NOTIFICATIONS] = get_device(module, LIGHT_ID_NOTIFICATIONS); devices->lights[LIGHT_INDEX_ATTENTION] = get_device(module, LIGHT_ID_ATTENTION); devices->lights[LIGHT_INDEX_BLUETOOTH] = get_device(module, LIGHT_ID_BLUETOOTH); devices->lights[LIGHT_INDEX_WIFI] = get_device(module, LIGHT_ID_WIFI); } else { memset(devices, 0, sizeof(Devices)); } return (jlong)devices; }
这就到驱动了,最终到hardware目录下有个lights.c文件有下面函数
static int open_lights(const struct hw_module_t *module, char const *name,
struct hw_device_t **device)
{
int (*set_light)(struct light_device_t *dev,
struct light_state_t const *state);
if (0 == strcmp(LIGHT_ID_BACKLIGHT, name))
set_light = set_light_backlight;
else if (0 == strcmp(LIGHT_ID_NOTIFICATIONS, name))
set_light = set_light_leds_notifications;
else if (0 == strcmp(LIGHT_ID_ATTENTION, name))
set_light = set_light_leds_attention;
else if (0 == strcmp(LIGHT_ID_BATTERY,name))
set_light = set_light_leds_battery;
else if (0 == strcmp(LIGHT_ID_BUTTONS,name))
set_light = set_light_keyboard;
else
return -EINVAL;
pthread_once(&g_init, init_g_lock);
struct light_device_t *dev = malloc(sizeof(struct light_device_t));
memset(dev, 0, sizeof(*dev));
dev->common.tag = HARDWARE_DEVICE_TAG;
dev->common.version = 0;
dev->common.module = (struct hw_module_t *)module;
dev->common.close = (int (*)(struct hw_device_t *))close_lights;
dev->set_light = set_light;
*device = (struct hw_device_t *)dev;
return 0;
}下面我们再来看set_light_backlight函数:
static int set_light_backlight(struct light_device_t* dev, struct light_state_t const* state) { int err = 0; int brightness = rgb_to_brightness(state); if(!dev) { return -1; } pthread_mutex_lock(&g_lock); err = write_int(LCD_FILE, brightness); pthread_mutex_unlock(&g_lock); return err; }
其中
char const*const LCD_FILE = "/sys/class/leds/lcd-backlight/brightness";
这样最后设置背光就是往这个节点里面写值,我们可以看下这个节点值。
root@lte26007:/sys/class/leds/lcd-backlight # cat brightness 102
五、总结
这篇博客我们主要分析了,DisplayMangerService是如何设置背光节点的,以及一些hal层的代码。
相关文章推荐
- Android6.0 wakelock深入分析
- android6.0 power按键深入分析
- android6.0 power显示(亮度等)深入分析(一)PowerManagerService & DisplayPowerController
- Android 关于Logcat的日志过滤
- 锁屏界面日期显示异常
- [Android]线性布局
- Android AccessibilityService使用注意
- com.android.dex.DexIndexOverflowException
- 获取手机分辨率(屏幕大小)
- SetupWizard界面长按Power键没有飞行模式选项
- AsyncTask串并行 源码分析
- 开机SetupWizard界面时区显示异常
- android-4集成高德地图的搜索和导航功能
- Android静态安全检测 -> 随机数使用不安全
- android 源码编译sdk
- android 自定义动态加载数据的折线图及相关问题解析
- Android开发笔记之广播,service实现音乐的播放暂停停止快进等功能
- Android 线程池
- Caused by: java.lang.SecurityException: Permission Denial: reading com.android.providers.media.MediaProvider uri content://...
- Android M原生BUG,手机使用过程中SystemServer空指针异常导致手机重启