[Android学习笔记一] ContentProvider组件开发详解

简介:

   Android四大组件中ContentProvider组件相对Activity,BroadcastReceiver, Service而言比较独立,而且多数使用的时候都是在用Android系统提供的关于邮件,媒体,短信,联系人等ContentProvider。通常开发的应用较少提供ContentProvider组件,一般会在同家公司的产品或则内容适配方面会用到ContentProvider组件。

 

    本文主要讲述开发和使用ContentProvider组件的通用方式。其中包含:代码模板,权限设置,ContentResolver 等。


1 . 开发ContentProvider基本思路


   1.1.ContentProvider组件需要对外开放的内容

       授权字符串(Authoritis),内容类型,数据字段名称, 访问权限说明等


   1.2. 编写ContentProvider组件类继承ContentProvider类,实现其中的CRUD操作方法


   1.3. AndroidMainifest.xml中配置<provider>元素,指定控制属性,其中包含是否对外部应用可用,读写权限,Authorities字符串等


   1.4. 应用本身或者外部应用使用ContentProvider组件


2. 开发一个基于SQLite数据库提供Note表信息的ContentProvider


  2.1  开发一个独立的ContentProvider组件代码框架


     

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
package  secondriver.xprovider;
 
import  android.content.*;
import  android.content.pm.ProviderInfo;
import  android.database.Cursor;
import  android.database.sqlite.SQLiteDatabase;
import  android.database.sqlite.SQLiteException;
import  android.database.sqlite.SQLiteOpenHelper;
import  android.database.sqlite.SQLiteQueryBuilder;
import  android.net.Uri;
import  android.provider.BaseColumns;
import  android.text.TextUtils;
import  android.util.Log;
 
import  java.text.SimpleDateFormat;
import  java.util.Date;
 
/**
  * Author : secondriver
  * Date :  2015/11/4
  */
public  class  XProvider  extends  ContentProvider {
 
     private  static  final  String TAG =  "XProvider" ;
 
     //授权字符串
     
     //授权Uri
     
 
     //Note公开信息
     public  static  final  class  Note  implements  BaseColumns {
 
         //Note表名
         
         //Note表的内容Uri
       
     //内容类型
  
         //Note表字段
       
         //Uri匹配码  
 
     }
 
     //Uri匹配器
     public  static  UriMatcher uriMatcher;
 
     static  {
         uriMatcher =  new  UriMatcher(UriMatcher.NO_MATCH);
         //添加Uri匹配内容
     }
 
     private  XSQLiteHelper helper;
 
     @Override
     public  boolean  onCreate() {
         //ContentProvider组件创建时做的工作
         return  true ;
     }
 
     @Override
     public  Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
         return  cursor;
     }
 
     @Override
     public  String getType(Uri uri) {
         return  null ;
     }
 
     @Override
     public  Uri insert(Uri uri, ContentValues values) {
         return  null ;
     }
 
     @Override
     public  int  delete(Uri uri, String selection, String[] selectionArgs) {
         return  effectRows;
     }
 
     @Override
     public  int  update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
         return  effectRows;
     }
 
     public  static  class  XSQLiteHelper  extends  SQLiteOpenHelper {
 
         public  static  final  String DATA_BASE_NAME =  "xprovider.sqlite" ;
         public  static  final  int  DATA_BASE_VERSION =  1 ;
 
         public  static  volatile  XSQLiteHelper xsqLiteHelper;
 
         public  static  XSQLiteHelper getXsqLiteHelper(Context context) {
             //实例化XSQLiteHelp对象
             return  xsqLiteHelper;
         }
 
         public  XSQLiteHelper(Context context, String name, SQLiteDatabase.CursorFactory factory,  int  version) {
             super (context, name, factory, version);
         }
 
         @Override
         public  void  onCreate(SQLiteDatabase db) {
             //数据库初始化
         }
 
         @Override
         public  void  onUpgrade(SQLiteDatabase db,  int  oldVersion,  int  newVersion) {
 
         }
     }
}


      上面代码提供了一个SQLiteOpenHelper的实现,实际开发中可个会更具具体应用来提供给ContentProvider组件。

     

      中文注释部分可能需要填充具体代码,比如权限可以更具实际情况来定义,表字段根据要提供的内容信息来公开字段名并且需要在文档中说明。


   2.1 准备Note表信息

     

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//Note公开信息
     public  static  final  class  Note  implements  BaseColumns {
 
         //Note表名
         public  static  final  String TABLE_NAME =  "note" ;
 
         //Note表的内容Uri
         public  static  final  Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI,  "note" );
 
         //建议使用格式如: type/subtype => vnd.android.cursor.dir/vnd.<name>.<type>  name=package name  type=table name
         public  static  final  String CONTENT_DIR_TYPE =  "vnd.android.cursor.dir/vnd.secondriver.xprovider.note" ;
         public  static  final  String CONTENT_ITEM_TYPE =  "vnd.android.cursor.item/vnd.secondriver.xprovider.note" ;
 
         //ID主键
         public  static  final  String ID_COLUMN = _ID;
         //内容列
         public  static  final  String CONTENT_COLUMN =  "CONTENT" ;
         //创建时间列表
         public  static  final  String CREATED_COLUMN =  "CREATED" ;
         //标识列
         public  static  final  String FLAG_COLUMN =  "FLAG" ;
         //状态列
         public  static  final  String STATUS_COLUMN =  "STATUS" ;
 
         //Uri匹配码
         private  static  final  int  NOTE_ITEM =  0x21 ;
         private  static  final  int  NOTE_DIR =  0x22 ;
 
     }


    说明:Uri匹配码声明为Note类的常量这样可以方便对照,一个ContentProvider组件中声明多个表公开类,比如User类,这样就比较容易区分操作的是那个表的内容。


 2.2  Note类外其它的具体代码


 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
package  secondriver.xprovider;
 
import  android.content.*;
import  android.content.pm.ProviderInfo;
import  android.database.Cursor;
import  android.database.sqlite.SQLiteDatabase;
import  android.database.sqlite.SQLiteException;
import  android.database.sqlite.SQLiteOpenHelper;
import  android.database.sqlite.SQLiteQueryBuilder;
import  android.net.Uri;
import  android.provider.BaseColumns;
import  android.text.TextUtils;
import  android.util.Log;
 
import  java.text.SimpleDateFormat;
import  java.util.Date;
 
/**
  * Author : secondriver
  * Date :  2015/11/4
  */
public  class  XProvider  extends  ContentProvider {
 
     private  static  final  String TAG =  "XProvider" ;
 
     //授权字符串
     public  static  final  String AUTHORITY =  "secondriver.xprovider.X_PROVIDER" ;
     //授权Uri
     public  static  final  Uri AUTHORITY_URI = Uri.parse( "content://"  + AUTHORITY);
 
 
     //Uri匹配器
     public  static  UriMatcher uriMatcher;
 
     static  {
         uriMatcher =  new  UriMatcher(UriMatcher.NO_MATCH);
         uriMatcher.addURI(AUTHORITY,  "note/#" , Note.NOTE_ITEM);
         uriMatcher.addURI(AUTHORITY,  "note" , Note.NOTE_DIR);
     }
 
     private  XSQLiteHelper helper;
 
     @Override
     public  boolean  onCreate() {
         helper = XSQLiteHelper.getXsqLiteHelper(getContext());
         return  true ;
     }
 
     @Override
     public  Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
         SQLiteQueryBuilder builder =  new  SQLiteQueryBuilder();
         switch  (uriMatcher.match(uri)) {
             case  Note.NOTE_DIR:
                 break ;
             case  Note.NOTE_ITEM:
                 builder.appendWhere(Note.ID_COLUMN +  "="  + uri.getPathSegments().get( 1 ));
                 break ;
             default :
                 throw  new  IllegalArgumentException( "Unsupported URI :"  + uri);
         }
         builder.setTables(Note.TABLE_NAME);
         Cursor cursor = builder.query(helper.getReadableDatabase(), projection, selection, selectionArgs,  null ,
                 null , sortOrder);
         return  cursor;
     }
 
     @Override
     public  String getType(Uri uri) {
         switch  (uriMatcher.match(uri)) {
             case  Note.NOTE_ITEM:
                 return  Note.CONTENT_ITEM_TYPE;
             case  Note.NOTE_DIR:
                 return  Note.CONTENT_DIR_TYPE;
             default :
                 throw  new  IllegalArgumentException( "Unsupported URI :"  + uri);
         }
     }
 
     @Override
     public  Uri insert(Uri uri, ContentValues values) {
         SQLiteDatabase db = helper.getWritableDatabase();
         switch  (uriMatcher.match(uri)) {
             case  Note.NOTE_DIR:
                 long  noteId = db.insert(Note.TABLE_NAME,  null , values);
                 if  (noteId >  0 ) {
                     //插入成功
                     Uri newUri = ContentUris.withAppendedId(uri, noteId);
                     getContext().getContentResolver().notifyChange(newUri,  null );
                     return  newUri;
                 else  {
                     //插入失败
                     return  null ;
                 }
             default :
                 throw  new  IllegalArgumentException( "Unsupported URI :"  + uri);
         }
     }
 
     @Override
     public  int  delete(Uri uri, String selection, String[] selectionArgs) {
         SQLiteDatabase db = helper.getWritableDatabase();
         int  effectRows =  0 ;
         switch  (uriMatcher.match(uri)) {
             case  Note.NOTE_DIR:
                 effectRows = db.delete(Note.TABLE_NAME, selection, selectionArgs);
                 break ;
             case  Note.NOTE_ITEM:
                 long  id = ContentUris.parseId(uri);
                 String whereClause = Note.ID_COLUMN +  "="  + String.valueOf(id);
                 if  (!TextUtils.isEmpty(selection)) {
                     whereClause = whereClause +  " AND "  + selection;
                 }
                 effectRows = db.delete(Note.TABLE_NAME, whereClause, selectionArgs);
                 break ;
             default :
                 throw  new  IllegalArgumentException( "Unsupported URI :"  + uri);
         }
         getContext().getContentResolver().notifyChange(uri,  null );
         return  effectRows;
     }
 
     @Override
     public  int  update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
         SQLiteDatabase db = helper.getWritableDatabase();
         int  effectRows =  0 ;
         switch  (uriMatcher.match(uri)) {
             case  Note.NOTE_DIR:
                 effectRows = db.update(Note.TABLE_NAME, values, selection, selectionArgs);
                 break ;
             case  Note.NOTE_ITEM:
                 long  nodeId = ContentUris.parseId(uri);
                 String whereClause = Note.ID_COLUMN +  "="  + String.valueOf(nodeId);
                 if  (!TextUtils.isEmpty(selection)) {
                     whereClause = whereClause +  " AND "  + selection;
                 }
                 effectRows = db.update(Note.TABLE_NAME, values, whereClause, selectionArgs);
                 break ;
             default :
                 throw  new  IllegalArgumentException( "Unsupported URI :"  + uri);
         }
         return  effectRows;
     }
 
     public  static  class  XSQLiteHelper  extends  SQLiteOpenHelper {
 
         public  static  final  String DATA_BASE_NAME =  "xprovider.sqlite" ;
         public  static  final  int  DATA_BASE_VERSION =  1 ;
 
         public  static  volatile  XSQLiteHelper xsqLiteHelper;
 
         public  static  XSQLiteHelper getXsqLiteHelper(Context context) {
             if  ( null  == xsqLiteHelper) {
                 synchronized  (XSQLiteHelper. class ) {
                     if  ( null  == xsqLiteHelper) {
                         xsqLiteHelper =  new  XSQLiteHelper(context, DATA_BASE_NAME,  null , DATA_BASE_VERSION);
                     }
                 }
             }
             return  xsqLiteHelper;
         }
 
         public  XSQLiteHelper(Context context, String name, SQLiteDatabase.CursorFactory factory,  int  version) {
             super (context, name, factory, version);
         }
 
         @Override
         public  void  onCreate(SQLiteDatabase db) {
             db.beginTransaction();
             try  {
                 db.execSQL(
                         new  StringBuilder( "create table if not exists " )
                                 .append(Note.TABLE_NAME)
                                 .append( "(" )
                                 .append(Note.ID_COLUMN)
                                 .append( " integer primary key autoincrement, " )
                                 .append(Note.CONTENT_COLUMN)
                                 .append( " varchar, " )
                                 .append(Note.CREATED_COLUMN)
                                 .append( " varchar," )
                                 .append(Note.FLAG_COLUMN)
                                 .append( " varchar," )
                                 .append(Note.STATUS_COLUMN)
                                 .append( " varchar" )
                                 .append( ")" )
                                 .toString()
                 );
                 SimpleDateFormat simpleDateFormat =  new  SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" );
                 for  ( int  i =  0 ; i <  50 ; i++) {
                     ContentValues cv =  new  ContentValues();
                     cv.put(Note.CONTENT_COLUMN,  "Note 内容 "  + i);
                     cv.put(Note.CREATED_COLUMN, simpleDateFormat.format( new  Date()));
                     cv.put(Note.FLAG_COLUMN, i);
                     cv.put(Note.STATUS_COLUMN, i);
                     db.insert(Note.TABLE_NAME,  null , cv);
                 }
                 db.setTransactionSuccessful();
             catch  (SQLiteException e) {
                 Log.e(TAG, e.getMessage());
             finally  {
                 db.endTransaction();
             }
         }
 
         @Override
         public  void  onUpgrade(SQLiteDatabase db,  int  oldVersion,  int  newVersion) {
 
         }
     }
}


    

    3. 在AndroidMainfest.xml清单文件中声明ContentProvider组件


     3.1  声明XProvider

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--
             按如下顺序验证权限
             android:grantUriPermssions: Temporary permission flag.
             android:permission: Single provider-wide read/write permission.
             android:readPermission: Provider-wide read permission.
             android:writePermission: Provider-wide write permission.
         -->
         < provider
                 android:permission = "secondriver.xprovider.permission.X_PROVIDER"
                 android:authorities = "secondriver.xprovider.X_PROVIDER"
                 android:name = ".XProvider"
                 android:exported = "true" >
             <!--
                 子元素:
                 grant-uri-permission :Uri临时访问授权
                 path-permission :Uri细粒度控制读写权限
             -->
         </ provider >


    3.2 定义权限

         清单文件中声明XProvider的时候使用到了内容读写权限

         “secondriver.xprovider.permission.X_PROVIDER” 因此该权限需要清单文件中定义。

        

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
< permission
             android:name = "secondriver.xprovider.permission.X_PROVIDER"
             android:label = "xProvider的读写权限"
             android:description = "@string/permission_x_provider_desc"
             android:protectionLevel = "normal" />
    <!--
     <permission
             android:name="secondriver.xprovider.permission.READ_X_PROVIDER"
             android:label="xProvider的读权限"
             android:description="@string/permission_read_x_provider_desc"
             android:protectionLevel="normal"/>
     <permission
             android:name="secondriver.xprovider.permission.WRITE_X_PROVIDER"
             android:label="xProvider的写权限"
             android:description="@string/permission_write_x_provider_desc"
             android:protectionLevel="normal"/> 
     -->


     权限定义时label属性的值通常为”XXX的权限“,description属性的值通常是”允许该应用干什么,授权有XXX的危害“。比如:label =发送持久广播权限 description=允许该应用发送持久广播,此类消息在广播结束后仍会保留。过多使用会占用手机过多内容,从而降低其速度或稳定性。 


        3.3 权限说明

        默认情况下Provider是没有权限控制的,因此一旦exported=true,那么外部其它应用都可以访问到Provider提供的内容,为了更加安全,有效,范围合适的控制需要添加权限控制。

        Provider权限分为: 

  •  读写的Provider层权限

  •  读写分开的Provider层权限

  •  Path层权限

  •  临时授权

       四种权限从上往下优先级越高。


      Path层权限:是对于Uri的更具细粒度的权限控制,provider元素的子元素中可以配置grant-ui-permission和path-permission 。

      

      临时授权:provider元素属性grantUriPermissions=true时系统将授予应用临时权限访问完整的Provider,覆盖掉Provider和Path层的权限;grantUriPermissions=false时需要在provider元素的子元素中配置一个或者多个grant-uri-permission元素来为指定的Uri的临时访问授权。

      另外应用在使用临时授权访问Provider时Provider应用会在返回的Intent中通过的setFlags方法指定FLAG_GRANT_READ_URI_PERMISSION和FLAG_GRANT_WRITE_URI_PERMISSION标识,携带一个具有临时授权的Uri供外部应用完成本次内容访问操作。


  4. 在本应用和其它应用中使用ContentProvider提供的内容


      这里提供在其它应用中使用ContentProvider。在使用外部提供的ContentProvider通常需要了解的内容便是文字1.1部分提到。


    4.1 下面是通过一个ListView来展示Note中的”CONTENT“和”CREATED“字段信息


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package  secondriver.oapp;
 
import  android.app.Activity;
import  android.app.LoaderManager;
import  android.content.CursorLoader;
import  android.content.Loader;
import  android.database.Cursor;
import  android.net.Uri;
import  android.os.Bundle;
import  android.view.View;
import  android.widget.AdapterView;
import  android.widget.ListView;
import  android.widget.SimpleAdapter;
import  android.widget.Toast;
 
import  java.util.ArrayList;
import  java.util.HashMap;
import  java.util.List;
import  java.util.Map;
 
/**
  * Author : secondriver
  * Date :  2015/11/5
  */
public  class  XResolverActivity  extends  Activity {
 
     private  SimpleAdapter mAdapter;
     private  ListView mListView;
     private  List<Map<String, String>> mData;
 
     private  final  int  xproviderLoad =  0x01 ;
     private  LoaderManager loaderManager;
     private  LoaderManager.LoaderCallbacks<Cursor> callbacks =  new  LoaderManager.LoaderCallbacks<Cursor>() {
         @Override
         public  Loader<Cursor> onCreateLoader( int  id, Bundle args) {
             CursorLoader cursorLoader =  new  CursorLoader(getApplicationContext(),
                     Uri.parse( "content://secondriver.xprovider.X_PROVIDER/note" ),
                     new  String[]{
                             "_id" ,
                             "CONTENT" ,
                             "CREATED"
                     },  null null null );
             return  cursorLoader;
         }
 
         @Override
         public  void  onLoadFinished(Loader<Cursor> loader, Cursor data) {
             int  idIndex = data.getColumnIndexOrThrow( "_id" );
             int  contentIndex = data.getColumnIndexOrThrow( "CONTENT" );
             int  createdIndex = data.getColumnIndexOrThrow( "CREATED" );
             mData.clear();
             while  (data.moveToNext()) {
                 HashMap<String, String> m =  new  HashMap<>();
                 m.put( "CONTENT" , data.getString(contentIndex));
                 m.put( "CREATED" , data.getString(createdIndex));
                 m.put( "_id" , data.getString(idIndex));
                 mData.add(m);
             }
             mAdapter.notifyDataSetChanged();
         }
 
         @Override
         public  void  onLoaderReset(Loader<Cursor> loader) {
 
         }
     };
 
 
     @Override
     protected  void  onCreate(Bundle savedInstanceState) {
         super .onCreate(savedInstanceState);
         setContentView(R.layout.activity_xresolver);
 
         mListView = (ListView) findViewById(android.R.id.list);
         mData =  new  ArrayList<>();
         mAdapter =  new  SimpleAdapter( this , mData, android.R.layout.simple_list_item_2,
                 new  String[]{
                         "CONTENT" ,
                         "CREATED"
                 },
                 new  int []{
                         android.R.id.text1,
                         android.R.id.text2
                 });
         mListView.setAdapter(mAdapter);
         mListView.setOnItemClickListener( new  AdapterView.OnItemClickListener() {
             @Override
             public  void  onItemClick(AdapterView<?> parent, View view,  int  position,  long  id) {
                 HashMap m = (HashMap) mData.get(position);
                 String noteId = (String) m.get( "_id" );
                 String noteContent = (String) m.get( "CONTENT" );
                 getContentResolver().delete(Uri.withAppendedPath(Uri.parse( "content://secondriver.xprovider.X_PROVIDER/note" ), noteId),  null null );
                 mData.remove(position);
                 mAdapter.notifyDataSetChanged();
                 Toast.makeText(XResolverActivity. this "Delete :"  + noteContent, Toast.LENGTH_LONG).show();
             }
         });
 
         loaderManager = getLoaderManager();
         loaderManager.initLoader(xproviderLoad,  new  Bundle(), callbacks);
     }
 
     @Override
     protected  void  onResume() {
         super .onResume();
         if  ( null  != loaderManager) {
             loaderManager.restartLoader(xproviderLoad,  new  Bundle(), callbacks);
         }
     }
 
     @Override
     protected  void  onDestroy() {
         super .onDestroy();
         if  ( null  != loaderManager) {
             loaderManager.destroyLoader(xproviderLoad);
         }
     }
}


     说明:需要额外注意的是代码中的硬编码字符串内容,这些内容正是XProvider类和Note类公开的信息,这些内容通常在ContentProvider组件的使用文档中公开说明的。如果是在应用内部使用XProvider的话,那么就可以直接使用变量名而避免硬编码。如下代码片段所示:

 

1
2
3
4
5
6
7
8
9
  mAdapter =  new  SimpleAdapter( this , mData, android.R.layout.simple_list_item_2,
                 new  String[]{
                         XProvider.Note.CONTENT_COLUMN,
                         XProvider.Note.CREATED_COLUMN
                 },
                 new  int []{
                         android.R.id.text1,
                         android.R.id.text2
                 });


   由于XProvider的访问需要读写权限,因此需要在清单文件中声明使用的权限。

   

1
< uses-permission  android:name = "secondriver.xprovider.permission.X_PROVIDER" />


  5. ContentProvider组件小结

     在开发ContentProvider时尽可能使其具备以下特点:

     提供恰当的内容访问范围;ContentProvider组件独立封装;详细的权限,Uri,提供内容的使用说明。



本文转自 secondriver 51CTO博客,原文链接:http://blog.51cto.com/aiilive/1710151,如需转载请自行联系原作者

相关文章
|
前端开发 Android开发 开发者
深入探究Android中的自定义View组件开发
【4月更文挑战第3天】 在现代Android应用程序的开发过程中,创建具有独特功能和高度定制化的用户界面是一个常见需求。为此,理解并掌握自定义View组件的开发成为了开发者必备的技能之一。本文将深入探讨如何在Android中创建自定义View,从基础的绘制原理到事件处理机制,再到性能优化技巧,旨在为读者提供一个全面的技术视角,并通过实例代码演示如何实现一个功能丰富、响应迅速的自定义View组件。
|
数据库 Android开发 开发者
Android Studio入门之内容共享ContentProvider讲解以及实现共享数据实战(附源码 超详细必看)
Android Studio入门之内容共享ContentProvider讲解以及实现共享数据实战(附源码 超详细必看)
512 0
|
SQL XML Java
Android 这 13 道 ContentProvider 面试题,你都会了吗?
Android 这 13 道 ContentProvider 面试题,你都会了吗?
|
安全 数据库 Android开发
45. 【Android教程】内容提供者 - Content Provider
45. 【Android教程】内容提供者 - Content Provider
180 2
|
架构师 网络协议 算法
Android高级架构师整理面试经历发现?(大厂面经+学习笔记(1)
Android高级架构师整理面试经历发现?(大厂面经+学习笔记(1)
|
消息中间件 缓存 架构师
2024年阿里Android高级面试题分享,附学习笔记+面试整理+进阶书籍
2024年阿里Android高级面试题分享,附学习笔记+面试整理+进阶书籍
|
存储 安全 Android开发
Android数据存储:请解释ContentProvider是什么,它的主要作用是什么?
ContentProvider是Android的四大组件之一,主要负责结构化数据的管理与共享。它封装数据并提供安全的访问接口,通过URI实现应用间数据的标准化共享。ContentResolver与ContentProvider协作,处理数据的CRUD操作,使得其他应用能方便地调用和操作数据。
107 0
|
XML 数据可视化 Android开发
深入探究Android中的自定义View组件开发
【4月更文挑战第12天】在安卓应用开发中,创建具有独特交互和视觉表现的自定义View组件是增强用户体验的重要手段。本文将详细阐述如何从头开始构建一个Android自定义View,包括理解View的工作原理、处理绘制流程、事件分发机制以及属性的自定义与管理。通过具体案例分析,我们将一步步实现一个可定制的动态进度条,不仅具备基础功能,还能根据业务需求进行扩展,体现高度的产品个性化。
|
数据库 Android开发 Kotlin
android开发,使用kotlin学习ContentProvider
android开发,使用kotlin学习ContentProvider
176 0
|
API 数据库 Android开发
Android ContentProvider内容提供者详解
Android ContentProvider内容提供者详解
202 2