使用ContentProvider
转载请注明出处: http://blog.csdn.net/a992036795/article/details/51610936
一、简介:
ContentProvider 在android中的作用是对外共享数据,也就是说可以通过ContentProvider把应用中的数据共享给其他应用访问,其他应用可以通过ContentProvider对应用中的数据进行增、删、该、查。使用ContentProvider的好处是,统一了数据的访问方式。
ContentProvider的体层实现实际是Binder。
二、使用:
一般ContentProvider的实现都是对SqliteOpenHelp的进一步包装,通过Uri映射来判断选择需要操作数据库中的那个表,并且进行增、删、改、查处理。
我们先来看以一下ContentProvider的定义:
** * Content providers are one of the primary building blocks of Android applications, providing * content to applications. They encapsulate data and provide it to applications through the single * {@link ContentResolver} interface. A content provider is only required if you need to share * data between multiple applications. For example, the contacts data is used by multiple * applications and must be stored in a content provider. If you don't need to share data amongst * multiple applications you can use a database directly via * {@link android.database.sqlite.SQLiteDatabase}. * * When a request is made via * a {@link ContentResolver} the system inspects the authority of the given URI and passes the * request to the content provider registered with the authority. The content provider can interpret * the rest of the URI however it wants. The {@link UriMatcher} class is helpful for parsing * URIs.
* * The primary methods that need to be implemented are: *
* - {@link #onCreate} which is called to initialize the provider
* - {@link #query} which returns data to the caller
* - {@link #insert} which inserts new data into the content provider
* - {@link #update} which updates existing data in the content provider
* - {@link #delete} which deletes data from the content provider
* - {@link #getType} which returns the MIME type of data in the content provider
*
* * class="caution">Data access methods (such as {@link #insert} and * {@link #update}) may be called from many threads at once, and must be thread-safe. * Other methods (such as {@link #onCreate}) are only called from the application * main thread, and must avoid performing lengthy operations. See the method * descriptions for their expected thread behavior.
* * Requests to {@link ContentResolver} are automatically forwarded to the appropriate * ContentProvider instance, so subclasses don't have to worry about the details of * cross-process calls.
* * <div class="special reference"> * Developer Guides
* For more information about using content providers, read the * "{@docRoot}guide/topics/providers/content-providers.html">Content Providers * developer guide.
*/public abstract class ContentProvider implements ComponentCallbacks2 { private static final String TAG = "ContentProvider";
通过注释我们大概可以了解到:
我们自定义一个ContentProvider,需要实现它的onCreate、query、insert、update、delete、getType方法。
因为它底层实现是Binder、其实对源码进行分析的话、可以看到如果是夸进程调用的话,OnCreate将发生在主线程、而其他方法将发生在Binder线程池中。
这一点,我们可以通过在方法打印进程名会得到证实。
06-08 01:46:21.903 3016-3016/com.blueberry.process1 I/SimpleContentProvider: onCreate: current thread: main06-08 01:46:21.920 3016-3032/com.blueberry.process1 I/SimpleContentProvider: insert: current thread: Binder_206-08 01:46:22.968 3016-3032/com.blueberry.process1 I/SimpleContentProvider: query: current thread: Binder_2
这是我写的一个例子的测试结果。下面我们将自己写一个ContentProvider。
首先我们先定义个SQLiteOpenHelp,来创建1个数据库,其中存在2张表。
下面给出类的定义:
/** * Created by blueberry on 2016/6/7. */public class UserInfoDbHelper extends SQLiteOpenHelper { private static final String TAG = "UserInfoDbHelper"; private static final String DB_NAME = "userinfo.db"; /*数据库名*/ private static final int DB_VERSION = 1;/*版本号*/ public static final String TABLE_USER_INFO = "userinfo";/*用户信息表*/ public static final String TABLE_COMPANY = "company";/*公司表*/ public static final String TEL_COLUMN = "tel_num";/*电话号码*/ public static final String DESC_COLUMN = "desc";/*描述*/ public static final String COMP_ID_COLUMN = "comp_id";/*公司id*/ public static final String ID_COLUMN = "id";/*公司的id*/ public static final String BUSINESS_COLUMN = "business";/*公司的业务*/ public static final String ADDR_COLUMN = "addr";/*公司位置*/ // 表 userinfo // | 字段名 | 类型 |意义 | // | Tel_num | TEXT |电话号码 | // | Desc | TEXT |描述 | // | comp_id | INTEGER | 公司id | // // 表 company // |字段名 |类型 |意义 | // |Id |INTEGER |公司的id | // |Business | TEXT |公司的业务 | // | Addr | TEXT | 公司位置 | private static final String POSTCODE_TABLE_SQL ="CREATE TABLE IF NOT EXISTS "+TABLE_USER_INFO +" (" +TEL_COLUMN+" TEXT ," +COMP_ID_COLUMN+" INTEGER ," +DESC_COLUMN+" TEXT" +")" ; private static final String COMPANY_TABLE_SQL ="CREATE TABLE IF NOT EXISTS "+TABLE_COMPANY+" (" +ID_COLUMN +" INTEGER PRIMEARY KEY ," +BUSINESS_COLUMN+" TEXT ," +ADDR_COLUMN+" TEXT" +")" ; public UserInfoDbHelper(Context context) { super(context, DB_NAME , null, DB_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(POSTCODE_TABLE_SQL); db.execSQL(COMPANY_TABLE_SQL); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { }}
可以看到创建了2张表,一张为userinfo 一张为 company。字段名已在注释列出。
下面继续给出自定一ContentProvider的定义:
/** * Created by blueberry on 2016/6/7. */public class SimpleContentProvider extends ContentProvider { private static final String TAG = "SimpleContentProvider"; public static final String AUTHORITY = "com.blueberry.test08.provider"; /*该ContentProvider返回的数据类型定义,数据集合*/ private static final String CONTENT_TYPE ="vnd.android.cursor.dir/vnd."+AUTHORITY; /*单项数据*/ private static final String CONTNET_TYPE_ITEM ="vnd.android.cursor.item/vnd."+AUTHORITY ; public static final Uri USERINFO_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + UserInfoDbHelper.TABLE_USER_INFO); public static final Uri COMPANY_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + UserInfoDbHelper.TABLE_COMPANY); public static final int USERINFO_CODE = 0; public static final int USERINFO_ITEM_CODE = 1; public static final int COMPANY_CODE = 2; public static final int COMPANY_ITEM_CODE = 3; private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); static { /** * 这里使用2种通配符 "*"表示匹配任意长度的任意字符,"#"表示匹配任意长度的数字 * 因此,content://com.blueberry.test08.provider/company 表示查询company表中的所有数据 * 而 conent://com.blueberry.test08.provider/company/# 表示根据一个数字id 查询一条信息 */ sUriMatcher.addURI(AUTHORITY, "userinfo", USERINFO_CODE); sUriMatcher.addURI(AUTHORITY, "userinfo/*", USERINFO_ITEM_CODE); sUriMatcher.addURI(AUTHORITY, "company", COMPANY_CODE); sUriMatcher.addURI(AUTHORITY, "company/#", COMPANY_ITEM_CODE); } private SQLiteDatabase mDatabase; /** * 夸进程时发生在主线程 * * @return */ @Override public boolean onCreate() { Log.i(TAG, "onCreate: current thread: " + Thread.currentThread().getName()); /*返回一个可读写的数据库*/ mDatabase = new UserInfoDbHelper(getContext()).getWritableDatabase(); return true; } /** * 夸进程时发生在工作线程 * * @param uri * @param projection * @param selection * @param selectionArgs * @param sortOrder * @return */ @Nullable @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { Log.i(TAG, "query: current thread: " + Thread.currentThread().getName()); Cursor cursor = null; switch (sUriMatcher.match(uri)) { case USERINFO_CODE: cursor = mDatabase.query(UserInfoDbHelper.TABLE_USER_INFO, projection, selection, selectionArgs, null, null, sortOrder); break; case USERINFO_ITEM_CODE: String tel = uri.getPathSegments().get(1); cursor = mDatabase.query(UserInfoDbHelper.TABLE_USER_INFO, projection, "tel_num = ?", new String[]{tel}, null, null, sortOrder); break; case COMPANY_CODE: cursor = mDatabase.query(UserInfoDbHelper.TABLE_COMPANY, projection, selection, selectionArgs, null, null, sortOrder); break; case COMPANY_ITEM_CODE: String cid = uri.getPathSegments().get(1); cursor = mDatabase.query(UserInfoDbHelper.TABLE_COMPANY, projection, "id = ?", new String[]{cid}, null, null, sortOrder); break; } return cursor; } /** * 夸进程时发生在工作线程 * * @param uri * @param values * @return */ @Nullable @Override public Uri insert(Uri uri, ContentValues values) { Log.i(TAG, "insert: current thread: " + Thread.currentThread().getName()); long newId = 0; Uri newUri = null; switch (sUriMatcher.match(uri)) { case USERINFO_CODE: newId = mDatabase.insert(UserInfoDbHelper.TABLE_USER_INFO, null, values); newUri = Uri.parse("content://" + AUTHORITY + "/" + UserInfoDbHelper.TABLE_USER_INFO + "/" + newId); break; case COMPANY_CODE: newId = mDatabase.insert(UserInfoDbHelper.TABLE_COMPANY, null, values); newUri = Uri.parse("content://" + AUTHORITY + "/" + UserInfoDbHelper.TABLE_COMPANY + "/" + newId); break; } if (newId > 0) return newUri; throw new IllegalArgumentException("Failed to insert row info" + uri); } /** * 夸进程时发生在工作线程 * * @param uri * @return */ @Nullable @Override public String getType(Uri uri) { Log.i(TAG, "getType: current thread: " + Thread.currentThread().getName()); switch (sUriMatcher.match(uri)){ case USERINFO_CODE: case COMPANY_CODE: return CONTENT_TYPE; case USERINFO_ITEM_CODE: case COMPANY_ITEM_CODE: return CONTNET_TYPE_ITEM; default: throw new RuntimeException("错误的 uri"); } } @Nullable @Override public Bundle call(String method, String arg, Bundle extras) { return super.call(method, arg, extras); } /** * 夸进程时发生在工作线程 * * @param uri * @param selection * @param selectionArgs * @return */ @Override public int delete(Uri uri, String selection, String[] selectionArgs) { Log.i(TAG, "delete: current thread: " + Thread.currentThread().getName()); return 0; } /** * 夸进程时发生在工作线程 * * @param uri * @param values * @param selection * @param selectionArgs * @return */ @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { Log.i(TAG, "update: current thread: " + Thread.currentThread().getName()); return 0; }}
我这里只是测试,所以只实现了query和inser方法。这里主要用了一个 UriMatcher类。
这里对上面的程序简要分析一下:
我们先来简要看一下Uri,Uri代表了要操作的数据表的绝对路径,Uri主要包含两部分信息,一是需要操作的ContentProvider,二是对ContentProvider中的哪个表进行操作。一个Uri的组成为:
ContentProvider的Schema已经由Android固定设置为content://, Authority用于唯一标示这个ContentProvider,外部调用者可以根据这个标示来找到它,这里的path就是要查询的数据表,最后的id是可选字段,例如,我们操作特定的数据项时就会指定一个查询条件,如果所有联系人的uri为 content://contacts/people,某个联系人的Uri:content://contacts/people/5,这个5 就是联系人的id,也就对应了查询的关键字。
这里我们的Authority为 com.blueberry.test08.provider。
接下来我们看以UriMatcher
我们来看一下这个类的定义:
/**Utility class to aid in matching URIs in content providers.To use this class, build up a tree of UriMatcher
objects.For example:
private static final int PEOPLE = 1; private static final int PEOPLE_ID = 2; private static final int PEOPLE_PHONES = 3; private static final int PEOPLE_PHONES_ID = 4; private static final int PEOPLE_CONTACTMETHODS = 7; private static final int PEOPLE_CONTACTMETHODS_ID = 8; private static final int DELETED_PEOPLE = 20; private static final int PHONES = 9; private static final int PHONES_ID = 10; private static final int PHONES_FILTER = 14; private static final int CONTACTMETHODS = 18; private static final int CONTACTMETHODS_ID = 19; private static final int CALLS = 11; private static final int CALLS_ID = 12; private static final int CALLS_FILTER = 15; private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH); static { sURIMatcher.addURI("contacts", "people", PEOPLE); sURIMatcher.addURI("contacts", "people/#", PEOPLE_ID); sURIMatcher.addURI("contacts", "people/#/phones", PEOPLE_PHONES); sURIMatcher.addURI("contacts", "people/#/phones/#", PEOPLE_PHONES_ID); sURIMatcher.addURI("contacts", "people/#/contact_methods", PEOPLE_CONTACTMETHODS); sURIMatcher.addURI("contacts", "people/#/contact_methods/#", PEOPLE_CONTACTMETHODS_ID); sURIMatcher.addURI("contacts", "deleted_people", DELETED_PEOPLE); sURIMatcher.addURI("contacts", "phones", PHONES); sURIMatcher.addURI("contacts", "phones/filter/*", PHONES_FILTER); sURIMatcher.addURI("contacts", "phones/#", PHONES_ID); sURIMatcher.addURI("contacts", "contact_methods", CONTACTMETHODS); sURIMatcher.addURI("contacts", "contact_methods/#", CONTACTMETHODS_ID); sURIMatcher.addURI("call_log", "calls", CALLS); sURIMatcher.addURI("call_log", "calls/filter/*", CALLS_FILTER); sURIMatcher.addURI("call_log", "calls/#", CALLS_ID); }
Starting from API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, paths can start with a leading slash. For example:
sURIMatcher.addURI("contacts", "/people", PEOPLE);
Then when you need to match against a URI, call {@link #match}, providingthe URL that you have been given. You can use the result to build a query,return a type, insert or delete a row, or whatever you need, without duplicatingall of the if-else logic that you would otherwise need. For example:
public String getType(Uri url) { int match = sURIMatcher.match(url); switch (match) { case PEOPLE: return "vnd.android.cursor.dir/person"; case PEOPLE_ID: return "vnd.android.cursor.item/person";... snip ... return "vnd.android.cursor.dir/snail-mail"; case PEOPLE_ADDRESS_ID: return "vnd.android.cursor.item/snail-mail"; default: return null; } }
instead of: public String getType(Uri url) { List pathSegments = url.getPathSegments(); if (pathSegments.size() >= 2) { if ("people".equals(pathSegments.get(1))) { if (pathSegments.size() == 2) { return "vnd.android.cursor.dir/person"; } else if (pathSegments.size() == 3) { return "vnd.android.cursor.item/person";... snip ... return "vnd.android.cursor.dir/snail-mail"; } else if (pathSegments.size() == 3) { return "vnd.android.cursor.item/snail-mail"; } } } return null; }
*/public class UriMatcher{
可以看到,注释中已经把他的用法说的查不到了。
UriMather 会根据uri来匹配出对应的 code来使得我们判断该查询那张表,
这里我们定义了四个 code. 分别对用与 用户信息查询、单个用户信息查询、公司查询、单个公司查询。
public static final int USERINFO_CODE = 0; public static final int USERINFO_ITEM_CODE = 1; public static final int COMPANY_CODE = 2; public static final int COMPANY_ITEM_CODE = 3;
我们通过如下方法将 对应信息添加进去:
static { /** * 这里使用2种通配符 "*"表示匹配任意长度的任意字符,"#"表示匹配任意长度的数字 * 因此,content://com.blueberry.test08.provider/company 表示查询company表中的所有数据 * 而 conent://com.blueberry.test08.provider/company/# 表示根据一个数字id 查询一条信息 */ sUriMatcher.addURI(AUTHORITY, "userinfo", USERINFO_CODE); sUriMatcher.addURI(AUTHORITY, "userinfo/*", USERINFO_ITEM_CODE); sUriMatcher.addURI(AUTHORITY, "company", COMPANY_CODE); sUriMatcher.addURI(AUTHORITY, "company/#", COMPANY_ITEM_CODE); }
我们接着来看一下getType方法,根据UriMather推荐的方法,如果查询的是数据集合, 我们返回的类型应该是 vnd.android.cursor.dir/….. 如果是查询单个数据我们返回的类型应该是 vnd.android.cursor.item/….
/*该ContentProvider返回的数据类型定义,数据集合*/ private static final String CONTENT_TYPE ="vnd.android.cursor.dir/vnd."+AUTHORITY; /*单项数据*/ private static final String CONTNET_TYPE_ITEM ="vnd.android.cursor.item/vnd."+AUTHORITY ; /** * 夸进程时发生在工作线程 * * @param uri * @return */ @Nullable @Override public String getType(Uri uri) { Log.i(TAG, "getType: current thread: " + Thread.currentThread().getName()); switch (sUriMatcher.match(uri)){ case USERINFO_CODE: case COMPANY_CODE: return CONTENT_TYPE; case USERINFO_ITEM_CODE: case COMPANY_ITEM_CODE: return CONTNET_TYPE_ITEM; default: throw new RuntimeException("错误的 uri"); } }
接着来看 他的query 和 insert方法:
/** * 夸进程时发生在工作线程 * * @param uri * @param projection * @param selection * @param selectionArgs * @param sortOrder * @return */ @Nullable @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { Log.i(TAG, "query: current thread: " + Thread.currentThread().getName()); Cursor cursor = null; switch (sUriMatcher.match(uri)) { case USERINFO_CODE: cursor = mDatabase.query(UserInfoDbHelper.TABLE_USER_INFO, projection, selection, selectionArgs, null, null, sortOrder); break; case USERINFO_ITEM_CODE: String tel = uri.getPathSegments().get(1); cursor = mDatabase.query(UserInfoDbHelper.TABLE_USER_INFO, projection, "tel_num = ?", new String[]{tel}, null, null, sortOrder); break; case COMPANY_CODE: cursor = mDatabase.query(UserInfoDbHelper.TABLE_COMPANY, projection, selection, selectionArgs, null, null, sortOrder); break; case COMPANY_ITEM_CODE: String cid = uri.getPathSegments().get(1); cursor = mDatabase.query(UserInfoDbHelper.TABLE_COMPANY, projection, "id = ?", new String[]{cid}, null, null, sortOrder); break; } return cursor; } /** * 夸进程时发生在工作线程 * * @param uri * @param values * @return */ @Nullable @Override public Uri insert(Uri uri, ContentValues values) { Log.i(TAG, "insert: current thread: " + Thread.currentThread().getName()); long newId = 0; Uri newUri = null; switch (sUriMatcher.match(uri)) { case USERINFO_CODE: newId = mDatabase.insert(UserInfoDbHelper.TABLE_USER_INFO, null, values); newUri = Uri.parse("content://" + AUTHORITY + "/" + UserInfoDbHelper.TABLE_USER_INFO + "/" + newId); break; case COMPANY_CODE: newId = mDatabase.insert(UserInfoDbHelper.TABLE_COMPANY, null, values); newUri = Uri.parse("content://" + AUTHORITY + "/" + UserInfoDbHelper.TABLE_COMPANY + "/" + newId); break; } if (newId > 0) return newUri; throw new IllegalArgumentException("Failed to insert row info" + uri); }
可以看到,利用UriMatcher得到要怎样查询,然后代用了 database的方法。
最后说一个方法:
@Nullable @Override public Bundle call(String method, String arg, Bundle extras) { return super.call(method, arg, extras); }
可以利用这个方法,扩展出一些查询方式。
最后我们来看以下在清单文件中的注册:
<provider android:authorities="com.blueberry.test08.provider" android:name=".SimpleContentProvider" android:permission="com.blueberry.permission.provider" android:process="com.blueberry.process1" android:multiprocess="false" > provider>
这里主要的字段为 authorities ,他是ContentProvider的唯一标识。
permission,它分为 读权限,和写权限 对应为 android:readpermission 和
android:writepermission 。 multiprocess如果设置为true标识每个调用进程都拥有一个示例,负责只有所有进程只有一个示例。process字段使得它运行在一个名为com.blueberry.process1的进程中。
最后我们来看一下调用程序:
public class MainActivity extends AppCompatActivity { private EditText etUserDesc,etUserPhoneNumber,etUserCompanyId; private EditText etCompanyId,etCompanyBussiness,etCompanyAddress ; private Button btnSubmitUserInfo,btnSubmitCompany; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); etUserDesc = (EditText) findViewById(R.id.et_user_desc); etUserPhoneNumber = (EditText) findViewById(R.id.et_user_phone_number); etUserCompanyId = (EditText) findViewById(R.id.et_user_company_id); etCompanyId = (EditText) findViewById(R.id.et_company_id); etCompanyBussiness = (EditText) findViewById(R.id.et_company_bussiness); etCompanyAddress = (EditText) findViewById(R.id.et_company_address); btnSubmitUserInfo = (Button) findViewById(R.id.btn_save_userinfo); btnSubmitCompany = (Button) findViewById(R.id.btn_save_company); btnSubmitUserInfo.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //存数用户信息数据 saveUserInfoRecord(); btnSubmitUserInfo.postDelayed(new Runnable() { @Override public void run() { queryUserInfo(); } },1000); } }); btnSubmitCompany.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { saveCompanyInfo(); btnSubmitCompany.postDelayed(new Runnable() { @Override public void run() { queryCompanyInfo(); } },1000) ; } }); } /** * 通过电话号码查询相关信息 */ private void queryUserInfo() { Uri queryUri = Uri.parse("content://com.blueberry.test08.provider/userinfo/123456") ; Cursor cursor = getContentResolver().query(queryUri, new String[]{UserInfoDbHelper.DESC_COLUMN,UserInfoDbHelper.COMP_ID_COLUMN,UserInfoDbHelper.TEL_COLUMN} ,null,null,null); if(cursor.moveToFirst()){ Toast.makeText(this, " 描述信息"+cursor.getString(0) +" 公司id"+cursor.getString(1) +" 电话来自"+cursor.getString(2),Toast.LENGTH_SHORT).show(); } cursor.close(); } /** * 存储用户信息到ContentProvider */ private void saveUserInfoRecord() { ContentValues newRecord = new ContentValues(); newRecord.put(UserInfoDbHelper.DESC_COLUMN,etUserDesc.getText().toString()); newRecord.put(UserInfoDbHelper.COMP_ID_COLUMN,etUserCompanyId.getText().toString()); newRecord.put(UserInfoDbHelper.TEL_COLUMN,etUserPhoneNumber.getText().toString()); getContentResolver().insert(SimpleContentProvider.USERINFO_CONTENT_URI,newRecord) ; } private void saveCompanyInfo() { ContentValues newRecord = new ContentValues(); newRecord.put(UserInfoDbHelper.ID_COLUMN,etCompanyId.getText().toString()); newRecord.put(UserInfoDbHelper.BUSINESS_COLUMN,etCompanyBussiness.getText().toString()); newRecord.put(UserInfoDbHelper.ADDR_COLUMN,etCompanyAddress.getText().toString()); getContentResolver().insert(SimpleContentProvider.COMPANY_CONTENT_URI,newRecord); } private void queryCompanyInfo(){ Cursor cursor = getContentResolver().query(SimpleContentProvider.COMPANY_CONTENT_URI, new String[]{UserInfoDbHelper.ID_COLUMN,UserInfoDbHelper.BUSINESS_COLUMN,UserInfoDbHelper.ADDR_COLUMN} ,null,null,null); StringBuffer sb = new StringBuffer(); while (cursor.moveToNext()){ sb.append("id: "+cursor.getString(0)+" business: "+cursor.getString(1)+" address: "+cursor.getString(2)); sb.append("\n"); } Toast.makeText(this,sb.toString(),Toast.LENGTH_LONG).show(); }}
布局:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.blueberry.test08.MainActivity"> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/et_user_desc" android:hint="请输入用户描述信息" android:contentDescription="用户描述信息" /> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/et_user_phone_number" android:hint="输入用户电话号码" android:contentDescription="用户电话号码" /> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/et_user_company_id" android:hint="用户公司id" android:contentDescription="用户公司id" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/btn_save_userinfo" android:text="存储用户信息" /> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/et_company_id" android:hint="公司id" /> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/et_company_bussiness" android:hint="请输入公司业务" android:contentDescription="公司业务" /> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/et_company_address" android:hint="请输入公司地址" android:contentDescription="请输入公司地址" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/btn_save_company" android:text="保存公司信息" />LinearLayout>
更多相关文章
- 说说在 Android(安卓)中如何发送 HTTP 请求
- 自定义Android,toast,以及多线程toast
- android 线程更新view及数据传送
- Android(安卓)Tthread 建立线程使用方法
- Android——线程创建以及handler
- android调试与内存泄漏
- Android(安卓)IPC机制及Binder原理
- Android(安卓)adb setuid提权漏洞的分析
- Android启动流程以及分类