近学习了如何在Android 6.0上添加一个系统服务,APP如何通过新增的系统服务访问底层驱动。
在这学习过程中,收获颇多,并结合学习了《Embeded Android》--Karim Yaghmour 一书中的
Appendix B. Adding Support For New Hardware章节,受益匪浅,讲述了如何添加一个完整的系统
服务(app->framework->kernel)。尽管描述的非常详细,但是基于Android 2.3.7描述的。现在把
书中的opersys例子移植到Android 6.0上,虽然说不上复杂,但由于版本差异,难免会出现许多奇奇
怪怪的问题,甚至版本差异造成了bug出现。特以此移植记录分享学习过程。

主要围绕以下几个步骤添加一个完整的系统服务:
(A) 添加circular-char驱动
(B) 添加opersyshw_qemu HAL
(C) 添加com_android_server_opersys_OpersysService JNI
(D) 添加IOpersysService接口
(E) 添加OpersysService
(F) 添加OpersysManager
(G) 添加系统服务
(H) 注册服务
(I) 更新API
(J) 设置权限
(K) 测试服务
(L) 添加测试APP

(A) 添加circular-char驱动

circular-char是一个简单的字符设备驱动,其实现的功能就是一个简单的FIFO,APP可以通过
read、write来进行读写操作实验,即写数据到FIFO,可以从FIFO读出写入的数据。

kernel/drivers/circular-driver/circular-char.c

 #include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <asm/uaccess.h> #define BUF_SIZE 200 static char buf[BUF_SIZE];
static char *read_ptr;
static char *write_ptr; static int device_open(struct inode *inode, struct file *file)
{
printk("device_open called \n"); return ;
} static int device_release(struct inode *inode, struct file *file)
{
printk("device_release called \n"); return ;
} static ssize_t device_read(struct file *filp, /* see include/linux/fs.h */
char *buffer, /* buffer to fill with data */
size_t length, /* length of the buffer */
loff_t * offset)
{
int chars_read = ; printk("device_read called \n"); while(length && *read_ptr && (read_ptr != write_ptr)) {
put_user(*(read_ptr++), buffer++); printk("Reading %c \n", *read_ptr); if(read_ptr >= buf + BUF_SIZE)
read_ptr = buf; chars_read++;
length--;
} return chars_read;
} static ssize_t
device_write(struct file *filp, const char *buff, size_t len, loff_t * off)
{
int i; printk("device_write called \n"); for(i = ; i < len; i++) {
get_user(*write_ptr, buff++);
printk("Writing %c \n", *write_ptr);
write_ptr++;
if (write_ptr >= buf + BUF_SIZE)
write_ptr = buf;
} return len;
} static struct file_operations fops = {
.open = device_open,
.release = device_release,
.read = device_read,
.write = device_write,
}; static struct miscdevice circ_char_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "circchar",
.fops = &fops,
}; int circ_char_enter(void)
{
int retval; retval = misc_register(&circ_char_misc);
printk("CIRC Driver got retval %d\n", retval);
printk("mmap is %08X\n", (int) fops.mmap); read_ptr = buf;
write_ptr = buf; return ;
} void circ_char_exit(void)
{
misc_deregister(&circ_char_misc);
} module_init(circ_char_enter);
module_exit(circ_char_exit);

kernel/drivers/circular-driver/Kconfig

 menuconfig DRIVER_FOR_TEST
bool "Drivers for test"
help
Drivers for test.
If unsure, say no. if DRIVER_FOR_TEST config CIRCULAR_CHAR
tristate "circular-char"
help
circular-char driver. endif

kernel/drivers/circular-driver/Makefile

 obj-$(CONFIG_CIRCULAR_CHAR)     += circular-char.o

kernel/drivers/Kconfig

 ......
source "drivers/circular-driver/Kconfig"
......

kernel/drivers/Makefile

 ......
obj-$(CONFIG_DRIVER_FOR_TEST) += circular-driver/
......

kernel/arch/arm/configs/xxx_defconfig

......
CONFIG_DRIVER_FOR_TEST=y
CONFIG_CIRCULAR_CHAR=y
......

驱动已添加到内核,编译烧录到目标板看是否加载成功:

    # ls dev/circchar
ls dev/circchar
dev/circchar
#echo hello > dev/circchar
echo hello > dev/circchar
#cat dev/circchar
dev/circchar
hello

如果执行以上命令,输出对应得信息,则说明驱动加载成功。

(B) 添加opersyshw_qemu HAL

这里添加一个opersys的HAL层,使应用和驱动分离,hal主要向应用提供open、read、write等几个
接口。

hardware/libhardware/tests/opersyshw/opersyshw_qemu.c

 #define  LOG_TAG  "opersyshw_qemu"
#include <cutils/log.h>
#include <cutils/sockets.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <hardware/opersyshw.h>
#include <malloc.h> #define OPERSYSHW_DEBUG 1 #if OPERSYSHW_DEBUG
# define D(...) ALOGD(__VA_ARGS__)
#else
# define D(...) ((void))
#endif static int fd = ; static int opersyshw__read(char* buffer, int length)
{
int retval; D("OPERSYS HW - read()for %d bytes called", length); retval = read(fd, buffer, length);
D("read data from driver: %s", buffer); return retval;
} static int opersyshw__write(char* buffer, int length)
{
int retval; D("OPERSYS HW - write()for %d bytes called", length); retval = write(fd, buffer, length);
D("write data to driver: %s", buffer); return retval;
} static int opersyshw__close(void)
{
if (fd != -) {
if (!close(fd)) {
return ;
}
} return -;
} static int opersyshw__test(int value)
{
return value;
} static int open_opersyshw(const struct hw_module_t* module, char const* name,
struct hw_device_t** device)
{
struct opersyshw_device_t *dev = malloc(sizeof(struct opersyshw_device_t));
if (!dev) {
D("OPERSYS HW failed to malloc memory !!!");
return -;
} memset(dev, , sizeof(*dev)); dev->common.tag = HARDWARE_DEVICE_TAG;
dev->common.version = ;
dev->common.module = (struct hw_module_t*)module;
dev->read = opersyshw__read;
dev->write = opersyshw__write;
dev->close = opersyshw__close;
dev->test = opersyshw__test; *device = (struct hw_device_t*) dev; fd = open("/dev/circchar", O_RDWR);
if (fd < ) {
D("failed to open /dev/circchar!");
return ;
} D("OPERSYS HW has been initialized"); return ;
} static struct hw_module_methods_t opersyshw_module_methods = {
.open = open_opersyshw,
}; struct hw_module_t HAL_MODULE_INFO_SYM = {
.tag = HARDWARE_MODULE_TAG,
.version_major = ,
.version_minor = ,
.id = OPERSYSHW_HARDWARE_MODULE_ID,
.name = "Opersys HW Module",
.author = "Opersys inc.",
.methods = &opersyshw_module_methods,
};

hardware/libhardware/include/hardware/opersyshw.h

 #ifndef ANDROID_OPERSYSHW_INTERFACE_H
#define ANDROID_OPERSYSHW_INTERFACE_H #include <stdint.h>
#include <sys/cdefs.h>
#include <sys/types.h> #include <hardware/hardware.h> __BEGIN_DECLS #define OPERSYSHW_HARDWARE_MODULE_ID "opersyshw" struct opersyshw_device_t {
struct hw_device_t common; int (*read)(char* buffer, int length);
int (*write)(char* buffer, int length);
int (*close)(void);
int (*test)(int value);
}; __END_DECLS #endif // ANDROID_OPERSYSHW_INTERFACE_H

hardware/libhardware/tests/opersyshw/Android.mk

 LOCAL_PATH := $(call my-dir)

 # HAL module implemenation, not prelinked and stored in
# hw/<GPS_HARDWARE_MODULE_ID>.<ro.hardware>.so
include $(CLEAR_VARS)
LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
LOCAL_CFLAGS += $(common_flags)
LOCAL_LDLIBS += -llog
LOCAL_C_INCLUDES := hardware/libhardware
LOCAL_SHARED_LIBRARIES := liblog libcutils libhardware
LOCAL_SRC_FILES := opersyshw_qemu.c
LOCAL_MODULE := opersyshw.$(TARGET_BOARD_PLATFORM)
LOCAL_MODULE_TAGS := optional
include $(BUILD_SHARED_LIBRARY)

编译之后看看是否错误,是否生成.so文件,在源码根目录下:

    # find ./out/ -name 'opersyshw.*.so'
......
./out/target/product/<project>/system/lib/hw/opersyshw.sc8830.so
......

注意Android.mk中的$(TARGET_BOARD_PLATFORM),这里是sc8830,不同的平台会有差异

(C) 添加com_android_server_opersys_OpersysService JNI

JNI接口主要是为了Java(app)调用C/C++。

frameworks/base/services/core/jni/com_android_server_opersys_OpersysService.cpp

 #define LOG_TAG "OpersysServiceJNI"

 #include "jni.h"
#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h" #include <utils/misc.h>
#include <utils/Log.h>
#include <hardware/hardware.h>
#include <hardware/opersyshw.h> #include <stdio.h> namespace android
{ opersyshw_device_t* opersyshw_dev; static jint init_native(JNIEnv *env, jobject /* clazz */)
{
int err;
hw_module_t* module;
opersyshw_device_t* dev = NULL; //ALOGI("init_native()"); err = hw_get_module(OPERSYSHW_HARDWARE_MODULE_ID, (hw_module_t const**)&module);
if (err == ) {
if (module->methods->open(module, "", ((hw_device_t**) &dev)) != ) {
ALOGE("Can't open opersys module!!!");
return ;
}
} else {
ALOGE("Can't get opersys module!!!");
return ;
} return (jint)dev;
} static void finalize_native(JNIEnv *env, jobject /* clazz */, int ptr)
{
opersyshw_device_t* dev = (opersyshw_device_t*)ptr; //ALOGI("finalize_native()"); if (dev == NULL) {
return;
} dev->close(); free(dev);
} static int read_native(JNIEnv *env, jobject /* clazz */, int ptr, jbyteArray buffer)
{
opersyshw_device_t* dev = (opersyshw_device_t*)ptr;
jbyte* real_byte_array;
int length; //ALOGI("read_native()"); real_byte_array = env->GetByteArrayElements(buffer, NULL); if (dev == NULL) {
return ;
} length = dev->read((char*) real_byte_array, env->GetArrayLength(buffer)); ALOGI("read data from hal: %s", (char *)real_byte_array); env->ReleaseByteArrayElements(buffer, real_byte_array, ); return length;
} static int write_native(JNIEnv *env, jobject /* clazz */, int ptr, jbyteArray buffer)
{
opersyshw_device_t* dev = (opersyshw_device_t*)ptr;
jbyte* real_byte_array;
int length; //ALOGI("write_native()"); real_byte_array = env->GetByteArrayElements(buffer, NULL); if (dev == NULL) {
return ;
} length = dev->write((char*) real_byte_array, env->GetArrayLength(buffer)); ALOGI("write data to hal: %s", (char *)real_byte_array); env->ReleaseByteArrayElements(buffer, real_byte_array, ); return length;
} static int test_native(JNIEnv *env, jobject /* clazz */, int ptr, int value)
{
opersyshw_device_t* dev = (opersyshw_device_t*)ptr; if (dev == NULL) {
return ;
} ALOGI("test_native()"); return dev->test(value);
} static JNINativeMethod method_table[] = {
{ "init_native", "()I", (void*)init_native },
{ "finalize_native", "(I)V", (void*)finalize_native },
{ "read_native", "(I[B)I", (void*)read_native },
{ "write_native", "(I[B)I", (void*)write_native },
{ "test_native", "(II)I", (void*)test_native}
}; int register_android_server_opersys_OpersysService(JNIEnv *env)
{
return jniRegisterNativeMethods(env, "com/android/server/opersys/OpersysService",
method_table, NELEM(method_table)); };

frameworks/base/services/core/jni/onload.cpp

 ......
namespace android {
......
int register_android_server_opersys_OpersysService(JNIEnv* env);
......
}; .... extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
......
register_android_server_opersys_OpersysService(env);
......
}

frameworks/base/services/core/jni/Android.mk

 ......
LOCAL_SRC_FILES += \
......
$(LOCAL_REL_DIR)/com_android_server_opersys_OpersysService.cpp \
......

(D) 添加IOpersysService接口

IOpersysService主要用于实现一个进程间通信的接口,其内部机制就是通过Binder实现进程间通信的,
即客户端与服务端(OpersysService)分别处于不同的进程中,客户端和服务端之间不能够直接相互访问,
之间必须通过Binder传递。

frameworks/base/core/java/android/opersys/IOpersysService.aidl

 interface IOpersysService {
/**
* {@hide}
*/
String read(int maxLength);
int write(String mString);
}

frameworks/base/Android.mk

 ......
LOCAL_SRC_FILES += \
......
core/java/android/opersys/IOpersysService.aidl \
......

其中,aidl文件主要用于生成同名的.java文件IOpersysService.java,IOpersysService.java主要实现
了一些Binder相关的设置和相关接口。
    编译后,会在out目录下生成:

out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/opersys/IOpersysService.java

(E) 添加OpersysService

OpersysService主要充当一个服务端(server),直接调用native如:

private static native int init_native();
private static native void finalize_native(int ptr);
private static native int read_native(int ptr, byte[] buffer);
private static native int write_native(int ptr, byte[] buffer);
private static native int test_native(int ptr, int value);

这些方法对应的是frameworks/base/services/core/jni/com_android_server_opersys_OpersysService.cpp
对应的同名函数,视觉上就像Java直接调用了C/C++一样。

frameworks/base/services/core/java/com/android/server/opersys/OpersysService.java

 package com.android.server.opersys;

 import android.content.Context;
import android.os.Handler;
import android.opersys.IOpersysService;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.util.Slog;
import android.os.RemoteException; public class OpersysService extends IOpersysService.Stub {
private static final String TAG = "OpersysService";
private Context mContext;
private int mNativePointer; public OpersysService(Context context) {
super();
mContext = context;
Slog.i(TAG, "Opersys Service started"); mNativePointer = init_native(); Slog.i(TAG, "test() returns " + test_native(mNativePointer, ));
} protected void finalize() throws Throwable {
finalize_native(mNativePointer);
super.finalize();
} public String read(int maxLength) throws RemoteException
{
int length;
byte[] buffer = new byte[maxLength]; length = read_native(mNativePointer, buffer); try {
return new String(buffer, , length, "UTF-8");
} catch (Exception e) {
Slog.e(TAG, "read buffer error!");
return null;
}
} public int write(String mString) throws RemoteException
{
byte[] buffer = mString.getBytes(); return write_native(mNativePointer, buffer);
} private static native int init_native();
private static native void finalize_native(int ptr);
private static native int read_native(int ptr, byte[] buffer);
private static native int write_native(int ptr, byte[] buffer);
private static native int test_native(int ptr, int value);
}

(F) 添加OpersysManager

OpersysManager主要用于管理OpersysService,实例化了IOpersysService,在注册服务的
时候就是实例化了一个OpersysManager,APP(客户端)获取服务getService时也是获得这个对象,通过这个对象,APP就

可以调用该服务的相关接口(API)了

frameworks/base/core/java/android/opersys/OpersysManager.java

 package android.opersys;

 import android.content.Context;
import android.os.RemoteException;
import android.opersys.IOpersysService;
import android.util.Slog; public class OpersysManager
{
private static final String TAG = "OpersysManager"; public String read(int maxLength) {
try {
return mService.read(maxLength);
} catch (RemoteException e) {
Slog.e(TAG, "read error!");
return null;
}
} public int write(String mString) {
try {
return mService.write(mString);
} catch (RemoteException e) {
Slog.e(TAG, "write error!");
return ;
}
} public OpersysManager(Context context, IOpersysService service) {
mService = service;
} IOpersysService mService;
}

(G) 添加系统服务addService

实现了OpersysService的各种接口之后,需要向SystemServer添加服务

frameworks/base/services/java/com/android/server/SystemServer.java

 ......
import com.android.server.opersys.OpersysService;
......
private void startOtherServices() {
......
OpersysService opersys = null;
......
if (mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
......
try {
Slog.i(TAG, "Opersys Service");
opersys = new OpersysService(context);
Slog.i(TAG, "Add Opersys Service");
ServiceManager.addService(Context.OPERSYS_SERVICE, opersys);
Slog.i(TAG, "Opersys Service Succeed!");
} catch (Throwable e) {
Slog.e(TAG, "Failure starting OpersysService Service", e);
}
......
}
}

frameworks/base/core/java/android/content/Context.java

 ......
@StringDef({
......
OPERSYS_SERVICE,
......
})
......
/**
* Use with {@link #getSystemService} to retrieve a
* {@link android.opersys.OpersysManager} for using Opersys Service.
*
* @see #getSystemService
*/
public static final String OPERSYS_SERVICE = "opersys";
......

(H) 注册服务

frameworks/base/core/java/android/app/SystemServiceRegistry.java

 ......
import android.opersys.OpersysManager;
import android.opersys.IOpersysService;
......
final class SystemServiceRegistry {
......
static {
......
registerService(Context.OPERSYS_SERVICE, OpersysManager.class,
new CachedServiceFetcher<OpersysManager>() {
@Override
public OpersysManager createService(ContextImpl ctx) {
IBinder b = ServiceManager.getService(Context.OPERSYS_SERVICE);
IOpersysService service = IOpersysService.Stub.asInterface(b);
if (service == null) {
return null;
}
return new OpersysManager(ctx, service);
}});
......
}
......
}
......

到这里,主要工作都要完成了,貌似可以直接可以编译进行测试了。但是实际上还有些东西需要
设置,如编译设置,SeLinux安全设置等。

(I) 更新API

在前面添加和注册了服务之后,需要更新一下API才能正常编译。

frameworks/data-binding/compiler/src/main/resources/api-versions.xml

 ......
<field name="OPERSYS_SERVICE" />
......

在源码目录下:

 # make update-api -j16

编译通过后,会自动更新以下两个文件
frameworks/base/api/current.txt

......
field public static final java.lang.String OPERSYS_SERVICE = "opersys";
......
package android.opersys { public abstract interface IOpersysService implements android.os.IInterface {
method public abstract int write(java.lang.String) throws android.os.RemoteException;
} public static abstract class IOpersysService.Stub extends android.os.Binder implements android.opersys.IOpersysService {
ctor public IOpersysService.Stub();
method public android.os.IBinder asBinder();
method public static android.opersys.IOpersysService asInterface(android.os.IBinder);
method public boolean onTransact(int, android.os.Parcel, android.os.Parcel, int) throws android.os.RemoteException;
} public class OpersysManager {
ctor public OpersysManager(android.content.Context, android.opersys.IOpersysService);
method public java.lang.String read(int);
method public int write(java.lang.String);
} }
......

frameworks/base/api/system-current.txt更新同上

(J) 设置权限

external/sepolicy/service.te

 ......
type opersys_service, app_api_service, system_server_service, service_manager_type;
......

external/sepolicy/service_contexts

 ......
opersys u:object_r:opersys_service:s0
......

device/<manufacturer>/<chip>/common/sepolicy/device.te

 ......
type circchar_device, dev_type;
......

device/<manufacturer>/<chip>/common/sepolicy/file_contexts

 ......
/dev/circchar u:object_r:circchar_device:s0
......

device/<manufacturer>/<chip>/common/sepolicy/system_server.te  

 ......
allow system_server circchar_device:chr_file { open read write ioctl };
......

system/core/rootdir/ueventd.rc

 ......
/dev/circchar system system
......

(K) 测试服务

完成以上步骤后,全编译一次,烧录后,可先通过命令测试一下服务是否正常启动:

    # service check opersys
service check opersys
Service opersys:found
# service list
service list
......
opersys: [android.opersys.IOpersysService]
......
# service call opersys s16 "Hello, World!"
service call opersys s16 "Hello, World!"
Result: Parcel( 0000000d '........')
# service call opersys i32
service call opersys i32
Result: Parcel(
0x00000000: 0000000d 006c006c '........H.e.l.l.'
0x00000010: 002c006f 0072006f 0064006c 'o.,. .W.o.r.l.d.'
0x00000020: '!... ')

执行以上命令得到对应的结果后,说明服务启动正常。接下来就可以在APP上使用了。

(L) 添加测试APP

这个APP的功能是,当用户打开APP时,APP会获得OpersysService服务,然后向这个服务发送
“Hello Opersys”,然后从这个服务读回。

packages/apps/HelloOpersysInternal/src/com/opersys/hellointernal/HelloOpersysInternalActivity.java

 package com.opersys.hellointernal;                                                                                 

 import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.os.ServiceManager; // Will only work in AOSP
//import android.opersys.IOpersysService; // Interface "hidden" in SDK
import android.opersys.OpersysManager;
import android.content.Context; public class HelloOpersysInternalActivity extends Activity {
private static final String DTAG = "HelloOpersysInternal"; /** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main); //IOpersysService om = IOpersysService.Stub.asInterface(ServiceManager.getService("opersys")); OpersysManager om = (OpersysManager)getSystemService(Context.OPERSYS_SERVICE);
try {
Log.d(DTAG, "Going to write to the \"opersys\" service");
om.write("Hello Opersys");
Log.d(DTAG, "Service returned: " + om.read());
}
catch (Exception e) {
Log.d(DTAG, "FAILED to call service");
e.printStackTrace();
}
}
}

packages/apps/HelloOpersysInternal/res/layout/main.xml

 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" > <TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello" /> </LinearLayout>

packages/apps/HelloOpersysInternal/res/values/strings.xml

 <?xml version="1.0" encoding="utf-8"?>
<resources> <string name="hello">Hello World, HelloOpersysInternalActivity!</string>
<string name="app_name">HelloOpersysInternal</string> </resources>

packages/apps/HelloOpersysInternal/AndroidManifest.xml

 <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.opersys.hellointernal"
android:versionCode=""
android:versionName="1.0" > <uses-sdk android:minSdkVersion="" /> <application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:label="@string/app_name"
android:name=".HelloOpersysInternalActivity" >
<intent-filter >
<action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application> </manifest>

packages/apps/HelloOpersysInternal/Android.mk

 LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS) LOCAL_MODULE_TAGS := optional LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_PACKAGE_NAME := HelloOpersysInternal include $(BUILD_PACKAGE)

vendor/sprd/open-source/common_packages.mk

 PRODUCT_PACKAGES += \
......
HelloOpersysInternal
......

到这里,已经完成了所有步骤,全编译烧录之后,打开HelloOpersysInternalAPP,再打开
logcat,可以看到以下信息:

- ::56.137     D HelloOpersysInternal: Going to write to the "opersys" service

- ::56.140     D opersyshw_qemu: OPERSYS HW - write()for  bytes called

- ::56.140     D opersyshw_qemu: write data to driver: Hello Opersys

- ::56.140     I OpersysServiceJNI: write data to hal: Hello Opersys

- ::56.142     D opersyshw_qemu: OPERSYS HW - read()for  bytes called

- ::56.142     D opersyshw_qemu: read data from driver: Hello Opersys

- ::56.142     I OpersysServiceJNI: read data from hal: Hello Opersys

- ::56.143     D HelloOpersysInternal: Service returned: Hello Opersys

Android 6.0 如何添加完整的系统服务(app-framework-kernel)的更多相关文章

  1. Android通用框架设计与完整电商APP开发系列文章

    作者|傅猿猿 责编|Javen205 有福利 有福利 有福利 鸣谢 感谢@傅猿猿 邀请写此系列文章 Android通用框架设计与完整电商APP开发 课程介绍 [导学视频] [课程详细介绍] 以下是部分 ...

  2. Android 6.0动态添加权限

    Android 6.0加入了动态权限,权限有普通权限和危险权限两种,其中危险权限在6.0以上的手机是需要动态添加权限的,举例:拨打10086//-----------------布局文件------- ...

  3. android 9.0 Launcher3 去掉抽屉式,显示所有 app

    效果图 修改思路 1.增加全局控制变量 sys.launcher3.is_full_app,用来动态切换 2.增加两套布局,对应有抽屉和无抽屉 3.去除 allAppsButton 4.将 AllAp ...

  4. Android Studio3.0 Error:Execution failed for task ':app:javaPreCompileDebug' 错误

    Error:Execution failed for task ':app:javaPreCompileDebug'. > Annotation processors must be expli ...

  5. Android 6.0一个完整的native service

     上一篇博客<Android 6.0 如何添加完整的系统服务(app-framework-kernel)>http://www.cnblogs.com/hackfun/p/7418902. ...

  6. Android 7.0 新增功能和api

    Android 7.0 Nougat 为用户和开发者引入多种新功能.本文重点介绍面向开发者的新功能. 请务必查阅 Android 7.0 行为变更以了解平台变更可能影响您的应用的领域. 要详细了解 A ...

  7. Google Android 6.0 权限完全解析

    注:本文只针对Google原生Android系统有效, 小米魅族等手机有自己的权限机制, 可能不适用 一.运行时权限的变化及特点 新的权限机制更好的保护了用户的隐私,Google将权限分为两类,一类是 ...

  8. Android 6.0权限问题

    Android 6.0 open failed: EACCES (Permission denied) 对于6.0+权限问题,报错如上: 解决方案: Android 6.0 (Marshmallow) ...

  9. ionic3 更新打开apk android 8.0报错

    项目中安卓强制更新,当文件下载完.在android 8.0中不能打开apk包. 引入插件报一下错误 import { FileOpener } from '@ionic-native/file-ope ...

随机推荐

  1. vue.js编程式路由导航 --- 由浅入深

    编程式路由导航 实例中定义一个方法,这个方法绑定在标签上 然后就设置路由跳转 语法 this.$router.history.push('要跳转路由的地址') <!DOCTYPE html> ...

  2. 疫情控制(codevs 1218)

    题目描述 Description H 国有 n 个城市,这 n 个城市用 n-1 条双向道路相互连通构成一棵树,1 号城市是首都, 也是树中的根节点. H 国的首都爆发了一种危害性极高的传染病.当局为 ...

  3. codeforces gym 100357 I (费用流)

    题目大意 给出一个或与表达式,每个正变量和反变量最多出现一次,询问是否存在一种方案使得每个或式中有且仅有一个变量的值为1. 解题分析 将每个变量拆成三个点x,y,z. y表示对应的正变量,z表示对应的 ...

  4. Quartz原理解密

    Quartz原理解密 Author: Dorae Date:2018年7月17日15:55:02 转载请注明出处 一.quartz概述 quartz是一个用java实现的开源任务调度框架,可以用来创建 ...

  5. BZOJ(5) 1083: [SCOI2005]繁忙的都市

    1083: [SCOI2005]繁忙的都市 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 4105  Solved: 2595[Submit][Sta ...

  6. 怎样编译和安装memcached

     怎样编译和安装memcached 编译和安装步骤: $ apt-get install git $ git clone https://github.com/memcached/memcache ...

  7. iOS8開始默认语言有变化

    [问题] 測试组发现APP在iOS8及以上系统设备上,语言设置为我们不支持的小语种时.APP没有使用默认的英文,而是选择了上一次设置的语言. [分析] 经过研究发现,在iOS8系统開始,在设备语言设置 ...

  8. Wincc操作数据库SQLSERVER

     WINCC写数据库 在WINCC的图形编辑器中,在输入域的属性中.选择事件.再选择键盘的释放动作,在动作代码框中写下例如以下代码: Dim conn,rs Dim strsql Dim a a=HM ...

  9. SQL SERVER学习笔记:临时表与表变量

    本文主要摘自徐海蔚的<Microsoft SQL SERVER企业级平台管理实践> 表变量可以作为存储过程的返回参数,而临时表不行.(存疑?表值参数只在SQL SERVER2008才开始支 ...

  10. Expressions-->Member lookup

    7.4 Member lookup 在word文档的第140页 A member lookup is the process whereby凭借:通过…:借以:与…一致 the meaning of ...