Android6.0 属性系统

属性在android中非常重要,我们基本的不多介绍了,主要说下其用法,原理等。

一、java层获取属性

在java层主要通过SystemProperties这个类来访问Android的系统属性,通过一系列的native函数。

public class SystemProperties
{
	......

    public static String get(String key) {
        if (key.length() > PROP_NAME_MAX) {
            throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
        }
        return native_get(key);
    }

    public static String get(String key, String def) {
        if (key.length() > PROP_NAME_MAX) {
            throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
        }
        return native_get(key, def);
    }

    public static int getInt(String key, int def) {
        if (key.length() > PROP_NAME_MAX) {
            throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
        }
        return native_get_int(key, def);
    }

    public static long getLong(String key, long def) {
        if (key.length() > PROP_NAME_MAX) {
            throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
        }
        return native_get_long(key, def);
    }

    public static boolean getBoolean(String key, boolean def) {
        if (key.length() > PROP_NAME_MAX) {
            throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
        }
        return native_get_boolean(key, def);
    }

    public static void set(String key, String val) {
        if (key.length() > PROP_NAME_MAX) {
            throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
        }
        if (val != null && val.length() > PROP_VALUE_MAX) {
            throw new IllegalArgumentException("val.length > " +
                PROP_VALUE_MAX);
        }
        native_set(key, val);
    }

我们再来看下android_os_SystemProperties.cpp中的这些native函数,注意都是静态的,因为在java层也是静态调用。

static jboolean SystemProperties_get_boolean(JNIEnv *env, jobject clazz,
                                      jstring keyJ, jboolean defJ)
{
    int len;
    const char* key;
    char buf[PROPERTY_VALUE_MAX];
    jboolean result = defJ;

    if (keyJ == NULL) {
        jniThrowNullPointerException(env, "key must not be null.");
        goto error;
    }

    key = env->GetStringUTFChars(keyJ, NULL);

    len = property_get(key, buf, "");
    if (len == 1) {
        char ch = buf[0];
        if (ch == '0' || ch == 'n')
            result = false;
        else if (ch == '1' || ch == 'y')
            result = true;
    } else if (len > 1) {
         if (!strcmp(buf, "no") || !strcmp(buf, "false") || !strcmp(buf, "off")) {
            result = false;
        } else if (!strcmp(buf, "yes") || !strcmp(buf, "true") || !strcmp(buf, "on")) {
            result = true;
        }
    }

    env->ReleaseStringUTFChars(keyJ, key);

error:
    return result;
}

static void SystemProperties_set(JNIEnv *env, jobject clazz,
                                      jstring keyJ, jstring valJ)
{
    int err;
    const char* key;
    const char* val;

    if (keyJ == NULL) {
        jniThrowNullPointerException(env, "key must not be null.");
        return ;
    }
    key = env->GetStringUTFChars(keyJ, NULL);

    if (valJ == NULL) {
        val = "";       /* NULL pointer not allowed here */
    } else {
        val = env->GetStringUTFChars(valJ, NULL);
    }

    err = property_set(key, val);

    env->ReleaseStringUTFChars(keyJ, key);

    if (valJ != NULL) {
        env->ReleaseStringUTFChars(valJ, val);
    }

    if (err < 0) {
        jniThrowException(env, "java/lang/RuntimeException",
                          "failed to set system property");
    }
}

最后是调用了system/core/libcutils/properties.c文件中的下面函数

int property_set(const char *key, const char *value)
{
    return __system_property_set(key, value);
}

int property_get(const char *key, char *value, const char *default_value)
{
    int len;

    len = __system_property_get(key, value);
    if(len > 0) {
        return len;
    }
    if(default_value) {
        len = strlen(default_value);
        if (len >= PROPERTY_VALUE_MAX) {
            len = PROPERTY_VALUE_MAX - 1;
        }
        memcpy(value, default_value, len);
        value[len] = '\0';
    }
    return len;
}

最后在bionic/libc/bionic/system_properties.cpp中调用如下函数write都是通过socket来往init写属性的,然后在init中调用__system_property_update和__system_property_add来往共享内存中写属性,但是获取属性应该是通过共享内存读取的。

int __system_property_get(const char *name, char *value)
{
    const prop_info *pi = __system_property_find(name);//从共享内存上获取相应的属性内存

    if (pi != 0) {
        return __system_property_read(pi, 0, value);//从属性内存中读取属性内容
    } else {
        value[0] = 0;
        return 0;
    }
}

int __system_property_set(const char *key, const char *value)
{
    if (key == 0) return -1;
    if (value == 0) value = "";
    if (strlen(key) >= PROP_NAME_MAX) return -1;
    if (strlen(value) >= PROP_VALUE_MAX) return -1;

    prop_msg msg;
    memset(&msg, 0, sizeof msg);
    msg.cmd = PROP_MSG_SETPROP;
    strlcpy(msg.name, key, sizeof msg.name);
    strlcpy(msg.value, value, sizeof msg.value);

    const int err = send_prop_msg(&msg);
    if (err < 0) {
        return err;
    }

    return 0;
}

有一点需要注意java中还有一个函数System.getProperty获取的不是系统属性,不要混淆了。

二、c层获取属性

c层获取属性我们就是通过上面的property_set和property_get方法

int property_set(const char *key, const char *value)
{
    return __system_property_set(key, value);
}

int property_get(const char *key, char *value, const char *default_value)
{
    int len;

    len = __system_property_get(key, value);
    if(len > 0) {
        return len;
    }
    if(default_value) {
        len = strlen(default_value);
        if (len >= PROPERTY_VALUE_MAX) {
            len = PROPERTY_VALUE_MAX - 1;
        }
        memcpy(value, default_value, len);
        value[len] = '\0';
    }
    return len;
}

三、init进程处理属性

系统中的每个进程都可以调用这些函数来读取和修改属性。读取属性值对任何进程都是没有限制的,直接由本进程从共享区中读取;但是修改属性值则必须通过init进程完成,同时进程还需要检查请求的进程是否有权限修改该属性值。

属性值成功修改后,init进程会检查init.rc中是否定义了该属性值的触发器。如果有定义,就执行该触发器下的命令。看下面:

on property:sys.lc.amtmode=0
    class_start core
    class_start main
    class_start late_start
    start lc-oms-sa

3.1 属性分类

我们看下属性的一些分类:

1.ro前缀的,"ro."这样的属性是只读属性,一旦设置,属性值不能再改变了。

2.persist前缀的,"persist."这样的属性改变会写入目录data/property下与属性名相同的文件中。再次开机时这些值会被init进程读取出来,因此关机再启动也是生效的。

3.net前缀的,"net."这样的属性当它改变时,属性"net.change"将会被自动设置为最后修改的属性名

4.属性"ctl.start" "ctl.stop"和 "ctl.restart"属性控制类属性,用于启动和停止服务的。使用ctl.start启动服务时,系统将会启动结果放在名为"init.svc.服务名”属性中。

3.2 创建属性系统共享空间

property_init 主要是在__system_property_area_init函数中创建了共享内存

void property_init() {
    if (property_area_initialized) {
        return;
    }

    property_area_initialized = true;

    if (__system_property_area_init()) {
        return;
    }

    pa_workspace.size = 0;
    pa_workspace.fd = open(PROP_FILENAME, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
    if (pa_workspace.fd == -1) {
        ERROR("Failed to open %s: %s\n", PROP_FILENAME, strerror(errno));
        return;
    }
}

我们来看__system_property_area_init函数,最后是在map_prop_area_rw函数中调用了mmap创建了共享内存

int __system_property_area_init()
{
    return map_prop_area_rw();
}
static int map_prop_area_rw()
{
    /* dev is a tmpfs that we can use to carve a shared workspace
     * out of, so let's do that...
     */
    const int fd = open(property_filename,//文件/dev/__properties__,应该是匿名映射,没有实际文件
                        O_RDWR | O_CREAT | O_NOFOLLOW | O_CLOEXEC | O_EXCL, 0444);

    if (fd < 0) {
        if (errno == EACCES) {
            /* for consistency with the case where the process has already
             * mapped the page in and segfaults when trying to write to it
             */
            abort();
        }
        return -1;
    }

    if (ftruncate(fd, PA_SIZE) < 0) {
        close(fd);
        return -1;
    }

    pa_size = PA_SIZE;
    pa_data_size = pa_size - sizeof(prop_area);
    compat_mode = false;

    void *const memory_area = mmap(NULL, pa_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);//内存映射
    if (memory_area == MAP_FAILED) {
        close(fd);
        return -1;
    }

    prop_area *pa = new(memory_area) prop_area(PROP_AREA_MAGIC, PROP_AREA_VERSION);

    /* plug into the lib property services */
    __system_property_area__ = pa;

    close(fd);
    return 0;
}

共享内存使用名称为如下的设备文件创建。

#define PROP_FILENAME "/dev/__properties__"

3.3 init进程处理属性

在init进程中的主函数中:在解析init.rc之前,先调用了start_property_service函数

    property_load_amt_defaults(amt_mode);

    property_load_boot_defaults();
    start_property_service();

    init_parse_config_file("/init.rc");

start_property_service函数创建了socket,然后监听,并且调用register_epoll_handler函数把socket的fd放入了epoll中

void start_property_service() {
    property_set_fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
                                    0666, 0, 0, NULL);
    if (property_set_fd == -1) {
        ERROR("start_property_service socket creation failed: %s\n", strerror(errno));
        exit(1);
    }

    listen(property_set_fd, 8);

    register_epoll_handler(property_set_fd, handle_property_set_fd);
}

register_epoll_handler函数就是把fd放入epoll中

void register_epoll_handler(int fd, void (*fn)()) {
    epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.ptr = reinterpret_cast<void*>(fn);
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {
        ERROR("epoll_ctl failed: %s\n", strerror(errno));
    }
}

先我们就来解析下handle_property_set_fd函数

static void handle_property_set_fd()
{
    prop_msg msg;
    int s;
    int r;
    struct ucred cr;
    struct sockaddr_un addr;
    socklen_t addr_size = sizeof(addr);
    socklen_t cr_size = sizeof(cr);
    char * source_ctx = NULL;
    struct pollfd ufds[1];
    const int timeout_ms = 2 * 1000;  /* Default 2 sec timeout for caller to send property. */
    int nr;

    if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) {//获取对端socket的fd
        return;
    }

    /* Check socket options here */
    if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
        close(s);
        ERROR("Unable to receive socket options\n");
        return;
    }

    ufds[0].fd = s;
    ufds[0].events = POLLIN;
    ufds[0].revents = 0;
    nr = TEMP_FAILURE_RETRY(poll(ufds, 1, timeout_ms));
    if (nr == 0) {
        ERROR("sys_prop: timeout waiting for uid=%d to send property message.\n", cr.uid);
        close(s);
        return;
    } else if (nr < 0) {
        ERROR("sys_prop: error waiting for uid=%d to send property message: %s\n", cr.uid, strerror(errno));
        close(s);
        return;
    }

    r = TEMP_FAILURE_RETRY(recv(s, &msg, sizeof(msg), MSG_DONTWAIT));//获取socket数据
    if(r != sizeof(prop_msg)) {
        ERROR("sys_prop: mis-match msg size received: %d expected: %zu: %s\n",
              r, sizeof(prop_msg), strerror(errno));
        close(s);
        return;
    }

    switch(msg.cmd) {
    case PROP_MSG_SETPROP:
        msg.name[PROP_NAME_MAX-1] = 0;
        msg.value[PROP_VALUE_MAX-1] = 0;

        if (!is_legal_property_name(msg.name, strlen(msg.name))) {
            ERROR("sys_prop: illegal property name. Got: \"%s\"\n", msg.name);
            close(s);
            return;
        }

        getpeercon(s, &source_ctx);

        if(memcmp(msg.name,"ctl.",4) == 0) {//ctl类型
            // Keep the old close-socket-early behavior when handling
            // ctl.* properties.
            close(s);
            if (check_control_mac_perms(msg.value, source_ctx)) {
                handle_control_message((char*) msg.name + 4, (char*) msg.value);
            } else {
                ERROR("sys_prop: Unable to %s service ctl [%s] uid:%d gid:%d pid:%d\n",
                        msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid);
            }
        } else {
            if (check_perms(msg.name, source_ctx)) {//检查权限
                property_set((char*) msg.name, (char*) msg.value);//设置属性
            } else {
                ERROR("sys_prop: permission denied uid:%d  name:%s\n",
                      cr.uid, msg.name);
            }

            // Note: bionic's property client code assumes that the
            // property server will not close the socket until *AFTER*
            // the property is written to memory.
            close(s);
        }
        freecon(source_ctx);
        break;

    default:
        close(s);
        break;
    }
}

我们来看property_set函数调用了property_set_impl函数来设置属性

int property_set(const char* name, const char* value) {
    int rc = property_set_impl(name, value);
    if (rc == -1) {
        ERROR("property_set(\"%s\", \"%s\") failed\n", name, value);
    }
    return rc;
}

property_set_impl函数主要讲属性值写入,或者更新到共享内存中,然后当属性是net类型的,把net类型的属性名写入net.change属性,persist属性写入文件,最后调用property_changed函数来处理,属性改变后的触发器事件。

static int property_set_impl(const char* name, const char* value) {
    size_t namelen = strlen(name);
    size_t valuelen = strlen(value);

    if (!is_legal_property_name(name, namelen)) return -1;
    if (valuelen >= PROP_VALUE_MAX) return -1;

    if (strcmp("selinux.reload_policy", name) == 0 && strcmp("1", value) == 0) {
        if (selinux_reload_policy() != 0) {
            ERROR("Failed to reload policy\n");
        }
    } else if (strcmp("selinux.restorecon_recursive", name) == 0 && valuelen > 0) {
        if (restorecon_recursive(value) != 0) {
            ERROR("Failed to restorecon_recursive %s\n", value);
        }
    }

    prop_info* pi = (prop_info*) __system_property_find(name);

    if(pi != 0) {
        /* ro.* properties may NEVER be modified once set */
        if(!strncmp(name, "ro.", 3)) return -1;//ro文件,直接退出

        __system_property_update(pi, value, valuelen);//更新属性数据到共享内存
    } else {
        int rc = __system_property_add(name, namelen, value, valuelen);//增加属性
        if (rc < 0) {
            return rc;
        }
    }
    /* If name starts with "net." treat as a DNS property. */
    if (strncmp("net.", name, strlen("net.")) == 0)  {
        if (strcmp("net.change", name) == 0) {
            return 0;
        }
       /*
        * The 'net.change' property is a special property used track when any
        * 'net.*' property name is updated. It is _ONLY_ updated here. Its value
        * contains the last updated 'net.*' property.
        */
        property_set("net.change", name);//net类型的属性,改变后需要写属性到net.change
    } else if (persistent_properties_loaded &&
            strncmp("persist.", name, strlen("persist.")) == 0) {
        /*
         * Don't write properties to disk until after we have read all default properties
         * to prevent them from being overwritten by default values.
         */
        write_persistent_property(name, value);//persist类型的属性写入到data/property目录下以属性名命名的文件
    }
    property_changed(name, value);
    return 0;
}

我们先看下write_persistent_property函数,将属性在data/property目录下创建以属性名命名的文件,然后写入属性值。写入方式是先做了一个临时文件,成功后改名。

static void write_persistent_property(const char *name, const char *value)
{
    char tempPath[PATH_MAX];
    char path[PATH_MAX];
    int fd;

    snprintf(tempPath, sizeof(tempPath), "%s/.temp.XXXXXX", PERSISTENT_PROPERTY_DIR);
    fd = mkstemp(tempPath);//做临时文件
    if (fd < 0) {
        ERROR("Unable to write persistent property to temp file %s: %s\n", tempPath, strerror(errno));
        return;
    }
    write(fd, value, strlen(value));//写入数据
    fsync(fd);
    close(fd);

    snprintf(path, sizeof(path), "%s/%s", PERSISTENT_PROPERTY_DIR, name);
    if (rename(tempPath, path)) {//改名
        unlink(tempPath);
        ERROR("Unable to rename persistent property file %s to %s\n", tempPath, path);
    }
}

property_changed函数就是看有哪些满足属性的触发器,然后放入执行队列中。最后在init的循环中,执行触发器相应的命令

void property_changed(const char *name, const char *value)
{
    if (property_triggers_enabled)
        queue_property_triggers(name, value);
}
void queue_property_triggers(const char *name, const char *value)
{
    struct listnode *node, *node2;
    struct action *act;
    struct trigger *cur_trigger;
    bool match;
    int name_length;

    list_for_each(node, &action_list) {
        act = node_to_item(node, struct action, alist);
        match = !name;
        list_for_each(node2, &act->triggers) {
            cur_trigger = node_to_item(node2, struct trigger, nlist);
            if (!strncmp(cur_trigger->name, "property:", strlen("property:"))) {
                const char *test = cur_trigger->name + strlen("property:");
                if (!match) {
                    name_length = strlen(name);
                    if (!strncmp(name, test, name_length) &&
                        test[name_length] == '=' &&
                        (!strcmp(test + name_length + 1, value) ||
                        !strcmp(test + name_length + 1, "*"))) {
                        match = true;
                        continue;
                    }
                }
                const char* equals = strchr(test, '=');
                if (equals) {
                    char prop_name[PROP_NAME_MAX + 1];
                    char value[PROP_VALUE_MAX];
                    int length = equals - test;
                    if (length <= PROP_NAME_MAX) {
                        int ret;
                        memcpy(prop_name, test, length);
                        prop_name[length] = 0;

                        /* does the property exist, and match the trigger value? */
                        ret = property_get(prop_name, value);
                        if (ret > 0 && (!strcmp(equals + 1, value) ||
                                        !strcmp(equals + 1, "*"))) {
                            continue;
                        }
                    }
                }
            }
            match = false;
            break;
        }
        if (match) {
            action_add_queue_tail(act);//最后将满足的触发器加入执行队列中
        }
    }
}

3.3 读取文件中属性

我们先来看init.rc中的下面触发器

3.3.1 /system/build.prop

on load_system_props_action
    load_system_props

而load_system_props_action是在late-init中触发的

on late-init
    trigger early-fs
    trigger fs
    trigger post-fs

    # Load properties from /system/ + /factory after fs mount. Place
    # this in another action so that the load will be scheduled after the prior
    # issued fs triggers have completed.
    trigger load_system_props_action

我们再来看load_system_props的处理,其中PROP_PATH_SYSTEM_BUILD就是/system/build.prop文件,load_properties_from_file函数最后会调用property_set函数设置属性

void load_system_props() {
    load_properties_from_file(PROP_PATH_SYSTEM_BUILD, NULL);
    load_properties_from_file(PROP_PATH_VENDOR_BUILD, NULL);
    load_properties_from_file(PROP_PATH_FACTORY, "ro.*");
    load_recovery_id_prop();
}

3.3.2 /data/local.prop /data/property

同样persist类型的属性如下:

on load_persist_props_action
    load_persist_props
    start logd
    start logd-reinit

也是在late-init触发,最后调用load_persist_props

on late-init
    trigger early-fs
    trigger fs
    trigger post-fs

    # Load properties from /system/ + /factory after fs mount. Place
    # this in another action so that the load will be scheduled after the prior
    # issued fs triggers have completed.
    trigger load_system_props_action

    # Now we can mount /data. File encryption requires keymaster to decrypt
    # /data, which in turn can only be loaded when system properties are present
    trigger post-fs-data
    trigger load_persist_props_action

load_persist_props函数调用了load_override_properties load_persistent_properties来去读属性值

void load_persist_props(void) {
    load_override_properties();
    /* Read persistent properties after all default values have been loaded. */
    load_persistent_properties();//读取data/property/下面persist类型的属性
}

load_override_properties函数,如果ro.debuggable为1.从文件/data/local.prop来读取属性。/data/local.prop文件时为了覆盖系统缺省的属性值。build.prop文件放在system目录下,修改不是很方便,如果希望测试某个属性,可以在/data/local.prop文件中修改,可以覆盖build.prop中的定义。

static void load_override_properties() {
    if (ALLOW_LOCAL_PROP_OVERRIDE) {
        char debuggable[PROP_VALUE_MAX];
        int ret = property_get("ro.debuggable", debuggable);
        if (ret && (strcmp(debuggable, "1") == 0)) {
            load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE, NULL);
        }
    }
}







时间: 2024-07-28 17:38:42

Android6.0 属性系统的相关文章

Android6.0权限系统

Android6.0权限系统 Android权限系统是一个非常重要的安全问题,因为它只有在安装时会询问一次.一旦软件本安装之后,应用程序可以在用户毫不知情的情况下使用这些权限来获取所有的内容. 很多坏蛋会通过这个安全缺陷来收集用户的个人信息并使用它们来做坏事的情况就不足为奇了. Android团队也意识到了这个问题.在经过了7年后,权限系统终于被重新设置了.从Anroid 6.0(API Level 23)开始,应用程序在安装时不会被授予任何权限,取而代之的是在运行时应用回去请求用户授予对应的权

Android6.0状态栏(系统下拉状态栏的定制)

■目标 不允许用户从上到下下拉状态栏 ■案1 截获手势,不处理该手势. SystemGesturesPointerEventListener public void onPointerEvent(MotionEvent event) { if (swipe == SWIPE_FROM_TOP) { 不调用手势处理} ■案1效果 无效果. adb log发现代码确实走到了,说明这里只是一个通知机制. ■案2 在状态栏上进行中断手势或者touch命令 StatusBarWindowView的onIn

Android6.0以上系统动态获取权限

动态权限的申请方法: 1.首先,需要在AndroidManifest.xml静态申请权限,否则无法动态申请权限: <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="an

Android6.0指纹识别开发

近期在做android指纹相关的功能,谷歌在android6.0及以上版本号对指纹识别进行了官方支持.当时在FingerprintManager和FingerprintManagerCompat这两个之间纠结.当中使用FingerprintManager要引入com.android.support:appcompat-v7包.考虑到包的大小,决定使用v4兼容包FingerprintManagerCompat来实现. 主要实现的工具类FingerprintUtil:验证手机是否支持指纹识别方法ca

Android6.0 ViewGroup/View 事件分发机制详解

之前自认为对于Android的事件分发机制还算比较了解,直到前一阵偶然跟人探讨该问题,才发现自己以前的理解有误,惭愧之余遂决定研习源码,彻底弄明白Android的事件分发机制,好了废话少说,直接开干. 首先,我们对Android中的touch事件做一下总结,主要分为以下几类: 1.Action_Down  用户手指触碰到屏幕的那一刻,会触发该事件: 2.Action_Move   在触碰到屏幕之后,手指开始在屏幕上滑动,会触发Action_Move事件: 3.Action_Up       在用

Android6.0系统权限那些事

Android6.0带来了新的权限管理方式,本文主要来源于官方文档,加入了自己的理解,目的是想总结Android6.0权限管理的新方式,其他部分可能主要是总结式的带过,后续再详细分析. 一.Security Architecture(安全体系结构) Android安全体系结构的核心是: 默认情况下没有任何应用有权限去执行对其他应用.操作系统.用户有不利影响的操作.这是一个核心的设计理念.记住这句话对后面的权限管理可以很好的理解. 正式由于这样的设计理念,默认情况下应用不能去读写用户的私有数据(比

Android6.0机型上调用系统相机拍照返回的resultCode值始终等于0的问题

版权声明:本文为博主原创文章,未经博主允许不得转载. 正常情况下调用系统相机拍照: 如果拍照后点击的是“确定”图标,返回的resultCode = -1(Activity.RESULT_OK): 如果点击的是底部的“返回”键,返回的resultCode = 0(Activity.RESULT_CANCELED). 简单的调用系统相机的写法: //调用系统拍照 Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); String p

Android6.0系统添加那些新特性

??? 北京时间9月30日凌晨在美国旧金山举行2015年秋季新品公布会.在公布会上代号为"Marshmallow(棉花糖)"的安卓6.0系统正式推出.新系统的总体设计风格依旧保持扁平化的MeterialDesign风格. Android6.0在对软件体验与执行性能上进行了大幅度的优化.安卓权限系统被又一次设计了. ??? 全新的Android M相比眼下的Android Lollipop(5.0)有二十项重大的改进: ? ? 原文博客请參考:点击打开链接 ??? 一:App Permi

Android6.0 WMS(六) WMS动画管理

Android的应用启动时,或者切换Activity时都会以动画的方式显示前后两屏的切换过程.动画的原理很简单,把一帧一帧的图像按一定时间间隔显示出来就完成了. 动画的绘制需要定时驱动,通常的做法是启动一个定时消息,每隔一定时间发一个消息,收到消息后输出一帧画面.Android支持VSync信号后,动画的驱动就有VSync信号承担了. 窗口动画的基本元素是窗口Surface中保存的图像,通过对窗口的Surface设置不同的变换矩阵和透明度,然后强制Surface刷新,就能在屏幕上显示出窗口的变化