2015-04-13 18:58:18 qq_24868901 阅读数 5971

              自定义属性的定义与获取,看了hyman老师的Android制作微信6.0界面,以及Android制作的QQ5.0侧滑菜单,深有感触,感觉学到了不少的东西,两个项目里面都运用到了android属性的定义与获取,所以想在CSDN博客中做下总结,方便自己以后不是很熟悉的时候来回顾一下!

以下是关于Android自定义属性的的使用步骤

1.首先定义自定义属性。

2.其次在组件中使用自定义属性,为属性赋值。

3.在java代码中获取自定义属性,并使用属性值。


以上是简单的过程,下面对过程进行详述:


1.1资源文件res中的values文件夹中自定义一个名为attrs.xml形式的文件,

      其中资源文件根节点为<resources></resource>里面拥有的子节点可以为<attr> ,<declare-styleable>,

  

    1.1.1   <attr>节点

     attr节点的属性有name,format.

     name就是给自定义属性命名的,最好能做到见明知意,

     关于format是给自定义的属性定义一个类型,有以下的类型

     -reference  引用类型 比如说自定义图片属性<attr name="icon" format="reference"></attr>

     -color         颜色类型  用来自定义颜色的类型

      -string       字符串类型  。。

      -demension 尺寸类型   用来定义尺寸类型

      -  

      -  

   1.1.2 <declare-styleable>节点

    declare-styleable节点也拥有子节点<attr>

    把<resource>节点内的子节点<attr>节点定义的自定义属性全部Copy到<declare-styleable>节点中,这个时候<declare-styleable>节点的子节点<attr>里面只需要name属性,自定义属性的format就可以去掉了(前提是你已经在resources节点下自定义属性节点中声明了format,当然你也可以在自定义属性的时候不声明fomat,在<declare-styleable>的子节点<attr>中来声明)。

   其中你需要给<declare-styleable>节点定义一个name属性,给name属性赋值,(自定义属性多用在自定义组件,所以这里最好使用自定义组件的名字)

   上面给name赋值,是为了使用自定义属性,是非常有意义的,而且是必须的。下面贴下代码加强理解

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <attr name="icon" format="reference"></attr>
    <attr name="color" format="color"></attr>
    <attr name="text" format="string"></attr>
    <attr name="text_size" format="dimension"></attr>

    <declare-styleable name="ChangeColorIconWithText">
        <attr name="icon"></attr>
        <attr name="color"></attr>
        <attr name="text"></attr>
        <attr name="text_size"></attr>
    </declare-styleable>

</resources>



2.1自定义属性完成后,接下来要使用自定义属性了

  首先我们来比较一下自定义属性与系统自带的属性,比如说

  

        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" >
   这些系统自带的属性 开头都是android: 他们的命名空间在

   

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

         这里我们要看到的是xmlns 意思是xml name space xml命名空间  在、、apk/res/android下面

而这里我们自己使用自己的自定义属性 就必须要加入自己的命名空间,还是与自定义搭上边

  自定义命名空间的方法:

xmlns:hyman="http://schemas.android.com/apk/res/com.imooc.weixin6_0"


这里的hyman名字是自己取的,当然要和你的项目有关,然后与系统的提供的属性集合一样,我们只需要把res/后面的内容换掉,把android换成自己项目的包名。一定是自己项目的包名,不是java文件所在的包名。


 命名空间完毕,使用自己的自定义属性,给属性值赋值,贴代码

其实我觉得使用自定义的属性的最重要的地方就是给自定义属性赋值,我们要利用的是值,自定义属性的都是容器,容器里面装的是值

    

<com.imooc.weixin6_0.ChangeColorIconWithText
            android:id="@+id/id_indicator_one"
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:padding="5dp"
            hyman:icon="@drawable/ic_menu_start_conversation"
            hyman:text="@string/app_name"
            hyman:text_size="12sp"
            hyman:color="#ff45c01a" />

   

        上面使用的自定义属性,使用提示的时候有时候、eclipse会给提示,有时候不会。


3.1最后一步了,是时候在java代码中使用自定义的属性了,并获取自定义属性的值去进行一系列操作

      java代码中获取自定义的属性值我们就不得不说到两个重要的类的了,第一个重要的类是TypedArray,第二个重要的类是TypedValue

    3.1.1    TypedValued

        最近接触到这个类比较多,而且主要是它的像素单位转换功能,网上像素单位转换有不少方法,我也转了博文,下面就不在赘述,我把博文的链接贴下面了


http://blog.csdn.net/qq_24868901/article/details/45022903


但是其中的dip(dp)转px的问题我还是要说一下

  怎么说呢,用个不是很精确的表达就是 在android中  比如下面以调语句

int viewWidth=50;  

如果上面的自被应用到显示方面的问题,它的基本单位是px(自己琢磨,如果有问题请评论哈,谢谢)


  但是应用px呢会造成不同的设备上面出现显示的不同效果,但是dp则不会,它会根据不同的设备来得到一个合适的像素值 (不然dp 也不会是device independent pixels)也就是设备独立像素。

为了达到一个app界面在不同的手机上显示合适的大小,所以要将dp转换为px ,不同的设备转换数字会不同

  在三星S4中   50dp=150px

  在MX3中    50dp=137px  (我拿鸟哥 和我的手机测出的!)


3.1.2  TypedArray

    TypedArray类这个类我也在转过博客,

   主要在这里说下怎么用TypedArray来获取自定义属性在java中

  首先

    

TypedArray a = context.obtainStyledAttributes(attrs,
				R.styleable.ChangeColorIconWithText);
通过上下文context对象调用obtainStyleAttributes()方法来得到TyedArray 对象  (这个时候我们给<declare-styleable>节点命名终于派上用场了)

    然后

int n = a.getIndexCount();

通过TypedArray对象获取一共使用了几个自定义的属性获取了自定义属性就可以调用for和switch语句了


for (int i = 0; i < n; i++)
		{
			int attr = a.getIndex(i);
			switch (attr)
			{
			case R.styleable.ChangeColorIconWithText_icon:
				BitmapDrawable drawable = (BitmapDrawable) a.getDrawable(attr);
				mIconBitmap = drawable.getBitmap();
				break;
			case R.styleable.ChangeColorIconWithText_color:
				mColor = a.getColor(attr, 0xFF45C01A);
				break;
			case R.styleable.ChangeColorIconWithText_text:
				mText = a.getString(attr);
				break;
			case R.styleable.ChangeColorIconWithText_text_size:
				mTextSize = (int) a.getDimension(attr, TypedValue
						.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12,
								getResources().getDisplayMetrics()));
				break;
			}

		}

用完TypedArray后别忘了
a.recycle();

最后一点要说明的是在自定义的组件中使用自定义属性的时候,系统会默认调用3个参数的构造方法,当未使用自定义属性的时候会默认的调用2个参数的构造方法,上面的代码一般是写在3个参数的构造方法中

(hyman老师在讲QQ5.0侧滑菜单的时候是这么说的,后来更正了这个错误,就算使用自定义属性也是调用两个参数的构造方法)


public ChangeColorIconWithText(Context context, AttributeSet attrs,
			int defStyleAttr)


其中利用TypedArray对象来获取索引,以上代码。。

这里贴一张更加精简的获取自定义属性的方法(朋友截图)这个方法更加的精简


通过TypedArray对象的getXxxx()方法来获取自定义属性值,getDrawable()和getColor()和getDimension()
方法都有一个默认值的参数,就是获取不到值的时候就使用默认值。 

最后一个getDimension默认值的意思是将12sp转换成?px;


  

到这里Android自定义属性和属性值得获取就结束了。

关于这个方法我想做一个总结

  • 首先Android的思想也就是把数据定义与代码的操作分开的思想就很让我觉得非常牛X了,自定义属性更加让Android代码整洁美观,方便维护与修改
  • 其次自定义属性可以可以避免一些麻烦,比如说上述的关于像素单位的问题,如果在java代码中定义一个dp单位像素值,必须要使用工具方法来进行转换成px,自定义属性可以避免这个麻烦(不过在getDimension()默认值参数中要转换,这是必须的。除非你指定的就是一个px,避免不掉的麻烦)。


2015-07-16 20:31:24 ted1452259777 阅读数 0

Android的Theme是由各种attr组合而成, 每个attr对应了这个属性的一个引用, 这个引用又可以是各种东西.

 

在某些情况下, 我们需要获取非自定义的主题下某个属性的内容 (比如拿到系统默认的配色colorAccent), 操作方式举例一则:

int defaultColor = 0xFF000000;
int[] attrsArray = { andorid.r.attr.colorAccent };
TypedArray typedArray = context.obtainStyledAttributes(attrsArray);
int accentColor = typedArray.getColor(0, defaultColor);

// don't forget the resource recycling
typedArray.recycle();

 

2017-05-26 14:37:12 FengShanChuiChui 阅读数 4881

obtainStyledAttributes是干什么的

有过自定义属性或者查看过系统View相关子类源码的人可能对这个方法都不会陌生。
该方法是Context类为我们提供的获取style中特定属性值的方法。通过这个方法,我们就可以获取在style中定义的各种属性值,然后根据获取到的不同的属性值实现差异化的效果。

一种典型的使用方式是:

        //TextView 构造方法片选代码

        //首先通过obtainStyledAttributes获取TypedArray
        TypedArray a = theme.obtainStyledAttributes(attrs,
                com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes); 
        //接着从TypedArray中提取相应的值,此处提取的是resourceId,对于的attr声明应为“reference”        
        //除此之外,TypedArray还有getInt,getDrawable等一系列方法用于提取其他类型的值。
        int ap = a.getResourceId(
                com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);

        //回收TypedArray
        a.recycle();

从以上代码片看到调用obtainStyledAttributes的变量是theme,其实是因为context中的该方法最终也是调用其持有的theme对象的该方法,因此是一致的。如下:

    //Context obtainStyledAttributes实现
    public final TypedArray obtainStyledAttributes(
            AttributeSet set, @StyleableRes int[] attrs, @AttrRes int defStyleAttr,
            @StyleRes int defStyleRes) {
        return getTheme().obtainStyledAttributes(
            set, attrs, defStyleAttr, defStyleRes);
    }

既然如此,我们就直接分析Theme中的obtainStyledAttributes方法。

obtainStyledAttributes API解析

这里写图片描述

以上是文档中对该方法的说明。我们先从参数部分看。该方法总共有四个参数:

参数名 说明
set 直接内嵌在View中的属性集合,如: <Button textColor=”#ff000000”>。
同时还包括通过style=“@style/xxxx”引入的属性,如:<Button style=”@style/xxxx”>
attrs 想要获取的属性集合,通常我们在声明属性时可以将之作为styleable的孩子,那么此时就可以直接使用如TextView中的方式引用那个styleable资源就行,如:R.styleable.TextViewAppearance
但有时由只想获取某一个或者几个属性的话,就应该手动构建int数组了,如:new int[]{R.attr.xxx, ...}
defStyleAttr 指定默认引用的属性,该属性应是一个指向某个style的引用。注意注释中的说明,该属性的值是从theme中提取的,如果该属性是包含在第一个参数“set”中的(使用内联方式指定或者style嵌入方式指定到元素标签内),那么会获取不到该值,因此只能够在theme中定义该属性的值
当theme中存在该属性的定义时,系统会将该属性指向的style内容引入进当前的内容作为属性集提供检索,而当不提供该参数时,系统是不会将Theme中那些指向style的属性内容展开的。
defStyleRes 当defStyleAttr=0或者Theme中未定义defStyleAttr属性时使用该处指向的style提供默认的属性集,比起defStyleAttr,该参数因为指定确定的style而缺乏灵活性,但其保证了组件中某些必须要获取到的属性值不会不存在。



总的来说,这个方法就在从一堆属性中提取自己需要的属性。这一堆属性的来源依据说明文档中的说法是有序的从以下地方提取的:

When determining the final value of a particular attribute, there are four inputs that come into play:

  1. Any attribute values in the given AttributeSet. ——参数set
  2. The style resource specified in the AttributeSet (named “style”). ——参数set
  3. The default style specified by defStyleAttr and defStyleRes. ——参数defStyleAttr和defStyleRes
  4. The base values in this theme. ——theme

而我们需要获取的属性就由int[]型的参数attrs指定。

这样,弄清了系统组件是如何获取属性的,我们就可以有针对性的修改属性来定义不同的效果了。比如修改默认的EditText获得焦点是底部线条的颜色。

修改默认的EditText获得焦点时底部线条的颜色

有时候我们只是想简单的修改获得焦点是EditText的颜色(比如使用红色提示输入格式不正确)而不想全部将EditText的background替换成自己写的selector(背景选择器),有没有简单的方法呢?当然有!

首先,我们查看系统默认的EditText的背景是个什么,我们才能有针对性的修改。
查看EditText源码发现其构造方法中指定了默认style为com.android.internal.R.attr.editTextStyle,即为android:editTextStyle

    //在inflate xml布局文件过程中,一般使用该构造方法
    public EditText(Context context, AttributeSet attrs) {
        this(context, attrs, com.android.internal.R.attr.editTextStyle);
    }

既然如此,那么我们就看看我们使用的Theme中定义的android:editTextStyle属性值是什么吧。

SDK/platforms/android-25/data/res/values/themes.xml中发现如下:

<item name=”editTextStyle”>@style/Widget.EditText</item>

再查看style/Widget.EditText,发现其中background属性使用的是?attr/editTextBackground

    <style name="Widget.EditText">
        <item name="focusable">true</item>
        <item name="focusableInTouchMode">true</item>
        <item name="clickable">true</item>
        <item name="background">?attr/editTextBackground</item>
        <item name="textAppearance">?attr/textAppearanceMediumInverse</item>
        <item name="textColor">?attr/editTextColor</item>
        <item name="gravity">center_vertical</item>
        <item name="breakStrategy">simple</item>
        <item name="hyphenationFrequency">normal</item>
    </style>

于是折回theme中查找editTextBackground,找到为:

<item name=”editTextBackground”>@drawable/edit_text_material</item>

那么drawable/edit_text又是什么呢,继续……

<!--SDK/platforms/android-25/data/res/drawable/edit_text_material.xml-->

<inset xmlns:android="http://schemas.android.com/apk/res/android"
       android:insetLeft="@dimen/edit_text_inset_horizontal_material"
       android:insetRight="@dimen/edit_text_inset_horizontal_material"
       android:insetTop="@dimen/edit_text_inset_top_material"
       android:insetBottom="@dimen/edit_text_inset_bottom_material">
    <selector>
        <item android:state_enabled="false">
            <nine-patch android:src="@drawable/textfield_default_mtrl_alpha"
                android:tint="?attr/colorControlNormal" />
        </item>
        <item android:state_pressed="false" android:state_focused="false">
            <nine-patch android:src="@drawable/textfield_default_mtrl_alpha"
                android:tint="?attr/colorControlNormal" />
        </item>
        <item>
            <nine-patch android:src="@drawable/textfield_activated_mtrl_alpha"
                android:tint="?attr/colorControlActivated" />
        </item>
    </selector>
</inset>

可以看到其中默认状态时(也是选择激活状态)的背景设置为一个nine-patch并设置了tint(染色)属性为?attr/colorControlActivated

于是乎,我们打可以使用android:colorControlActivated这个属性来设置选择时线条的颜色。因此在Theme中定义

<item name=”android:colorControlActivated”>#ff0000</item>

同理,可以设置未选中状态的颜色通过定义colorControlNormal

后记

注意!!!
以上edit_text_material中tint的使用方式为引用系统属性colorControlActivated,因此不能通过<EditText android:colorControlActivated="#ff0000">来实现,只能在Theme中指定该属性!

那这就引出另一个问题。我们在Theme中修改该系统属性值可能也在被其他组件使用。岂不是都会被影响到?确实是这样的,但是这是有解决办法的。可以使用ContextThemeWrapper来解决,使该属性的定义只影响某个或者某些元素。具体使用方式可以百度ContextThemeWrapper或参见本人博客Android ContextThemeWrapper应用

2013-05-27 15:23:31 hfgerr 阅读数 26

做Android布局是件很享受的事,这得益于他良好的xml方式。使用xml可以快速有效的为软件定义界面。可是有时候我们总感觉官方定义的一些基本组件不够用,自定义组件就不可避免了。那么如何才能做到像官方提供的那些组件一样用xml来定义他的属性呢?现在我们就来讨论一下他的用法。

一、在res/values文件下定义一个attrs.xml文件,代码如下:

<?xml version="1.0" encoding="utf-8"?> 
<resources> 
    <declare-styleable name="ToolBar"> 
        <attr name="buttonNum" format="integer"/> 
        <attr name="itemBackground" format="reference|color"/> 
    </declare-styleable> 
</resources>

二、在布局xml中如下使用该属性:

<?xml version="1.0" encoding="utf-8"?> 
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    xmlns:toolbar="http://schemas.android.com/apk/res/cn.zzm.toolbar" 
    androidrientation="vertical" 
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent" 
    > 
    <cn.zzm.toolbar.ToolBar android:id="@+id/gridview_toolbar" 
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content" 
        android:layout_alignParentBottom="true" 
        android:background="@drawable/control_bar" 
        android:gravity="center" 
        toolbar:buttonNum="5" 
        toolbar:itemBackground="@drawable/control_bar_item_bg"/> 
</RelativeLayout>

三、在自定义组件中,可以如下获得xml中定义的值:

TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.ToolBar); 
buttonNum = a.getInt(R.styleable.ToolBar_buttonNum, 5); 
itemBg = a.getResourceId(R.styleable.ToolBar_itemBackground, -1);

a.recycle();

就这么简单的三步,即可完成对自定义属性的使用。

*********************************************************************

好了,基本用法已经讲完了,现在来看看一些注意点和知识点吧。

首先来看看attrs.xml文件。

该文件是定义属性名和格式的地方,需要用<declare-styleable name="ToolBar"></declare-styleable>包围所有属性。其中name为该属性集的名字,主要用途是标识该属性集。那在什么地方会用到呢?主要是在第三步。看到没?在获取某属性标识时,用到"R.styleable.ToolBar_buttonNum",很显然,他在每个属性前面都加了"ToolBar_"。

在来看看各种属性都有些什么类型吧:string , integer , dimension , reference , color , enum.

前面几种的声明方式都是一致的,例如:<attr name="buttonNum" format="integer"/>。 
只有enum是不同的,用法举例:

<attr name="testEnum"> 
    <enum name="fill_parent" value="-1"/> 
    <enum name="wrap_content" value="-2"/> 
</attr>

如果该属性可同时传两种不同的属性,则可以用“|”分割开即可。



让我们再来看看布局xml中需要注意的事项。

首先得声明一下:xmlns:toolbar=http://schemas.android.com/apk/res/cn.zzm.toolbar 
注意,“toolbar”可以换成其他的任何名字,后面的url地址必须最后一部分必须用上自定义组件的包名。自定义属性了,在属性名前加上“toolbar”即可。



最后来看看java代码中的注意事项。

在自定义组件的构造函数中,用

TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.ToolBar);

来获得对属性集的引用,然后就可以用“a”的各种方法来获取相应的属性值了。这里需要注意的是,如果使用的方法和获取值的类型不对的话,则会返回默认值。因此,如果一个属性是带两个及以上不用类型的属性,需要做多次判断,知道读取完毕后才能判断应该赋予何值。当然,在取完值的时候别忘了回收资源哦!

 

转发自:http://www.cnblogs.com/ufocdy/archive/2011/05/27/2060221.html

2019-01-22 15:08:45 kc58236582 阅读数 4134

属性在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;
    }
}

我们先来看看处理ctl类型的属性,ctl.start ctl.stop ctl.restart.

void handle_control_message(const char *msg, const char *arg)
{
    if (!strcmp(msg,"start")) {
        msg_start(arg);
    } else if (!strcmp(msg,"stop")) {
        msg_stop(arg);
    } else if (!strcmp(msg,"restart")) {
        msg_restart(arg);
    } else {
        ERROR("unknown control msg '%s'\n", msg);
    }
}

最后调用了下面这三个函数来处理这些命令。总的是先看看有没有这个service,然后调用start stop 或者是restart相关函数。

 

static void msg_start(const char *name)
{
    struct service *svc = NULL;
    char *tmp = NULL;
    char *args = NULL;

    if (!strchr(name, ':'))
        svc = service_find_by_name(name);
    else {
        tmp = strdup(name);
        if (tmp) {
            args = strchr(tmp, ':');
            *args = '\0';
            args++;

            svc = service_find_by_name(tmp);
        }
    }

    if (svc) {
        service_start(svc, args);
    } else {
        ERROR("no such service '%s'\n", name);
    }
    if (tmp)
        free(tmp);
}

static void msg_stop(const char *name)
{
    struct service *svc = service_find_by_name(name);

    if (svc) {
        service_stop(svc);
    } else {
        ERROR("no such service '%s'\n", name);
    }
}

static void msg_restart(const char *name)
{
    struct service *svc = service_find_by_name(name);

    if (svc) {
        service_restart(svc);
    } else {
        ERROR("no such service '%s'\n", name);
    }
}

 

 

下面我们再来看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);
        }
    }
}

 
 

 

 

 

 

 







Android 自定义属性

阅读数 352

android 屏幕属性

阅读数 139

没有更多推荐了,返回首页