Android lesson 5: content provider

Posted by matthijs on Tue, 25 Jan 2022 02:05:02 +0100

I What is a content provider

Content provider is one of the four components, but we don't often use it in general development. The content provider is the ContentProvider, which provides a unified interface for data sharing between different software. It exposes data in a way similar to a data table. If you describe it with images, it's roughly like this

Therefore, for other software, ContentProvider is like a "database". Therefore, the operation of obtaining the data provided by the outside world is basically the same as that of obtaining the data from the database. You just need a URI to represent the "database" to access.

In other words, the content provider ContentProvider is mainly used to realize the data sharing function between different applications. It provides a complete mechanism to allow one program to access the data in another program and ensure the security of the accessed data. At present, using content providers is the standard way for Android to share data across programs. Different from the two global read-write operation modes in file storage and shared preference storage, content providers can choose to share only that part of the data, so as to ensure that the private data in our program will not be exposed.

Take one of our common content providers. In the Android system, many data, such as contact information, SMS information, picture library, audio library, etc., are often used in development. Google engineers have encapsulated these information for us. We can use the Uri given to me by Google to directly access these data.

II Purpose of content provider

Let's say something official

Content Provider is one of the four components of Android system. They encapsulate data and provide a mechanism to define data security. Content Provider is a standard interface for one process to connect data with another process. In the Android system, applications are independent of each other, running in their own processes, and there is no data exchange between them. If application sharing is required between applications, the method used is the Content Provider.

The Content Provider provides external data storage channels in the form of URI, which can be used in other applications. Use the ContentResolver object as the "client" to communicate with the provider. The ContentResolver object communicates with the provider object, and IPC and security are automatically handled between them. The provider object receives the data request from each client, performs the request action and returns the result.

III Leading knowledge

When using the Content Provider, we also use URI, ContentUris and other related contents, which will appear in the final case. In order to better understand the code, we need to know it in advance.

3.1 explanation of URI

In the above, we mentioned that if you want to access content providers of other software, you need URIs. What is URI?

A URI is a uniform resource identifier, which is a string used to identify a resource name. This identification allows users to interact with any resource (including local and Internet) through a specific protocol.

As far as the Android platform is concerned, URI is mainly divided into three parts: scheme, authority and path. Authority is divided into host and port.
The URI format is as follows:

scheme: //host: port/path

For example:

In our content provider, authority is used to identify its unique.

3.2 ContentUris

It is a tool class about content URI. The syntax of content URI is as follows:

content: //authonity/path/id
  • Content part: the value of this part is always content://
  • Authority part: this part is used to identify the entire content provider
  • path part: usually used to distinguish different tables.
  • ID part: used to identify a single row of data, usually corresponding to the ID column

common method

  • public static long parseld(Uri contentUri).
    Is to take out the id in the Uri. If no path returns - 1, an exception may be thrown.

IV Steps for using content providers

4.1 step 1: define a class to inherit ContentProvider

The ContentProvider encapsulates the data and provides the data to other app s through the ContentResolver. When a data request is executed through the ContentResolver, it will parse the authority in the URI, send the request to the ContentProvider of the Authority registered by the system, and then parse the rest of the URI (mainly the path part) in the ContentProvider. The flow is shown in the figure.

The ContentProvider class defines six abstract methods.

The main methods to be implemented are:

  • public boolean onCreate() is called when creating a ContentProvider
  • public Cursor query (Uri, String[], String, String[], String) is used to query the ContentProvider of the specified Uri and return a Cursor
  • public Uri insert (Uri, ContentValues) is used to add data to the ContentProvider of the specified Uri (external applications add data to the ContentProvider)
  • public int update (Uri, ContentValues, String, String []) is used to update the data in the ContentProvider of the specified Uri
  • public int delete (Uri, String, String []) is used to delete data from the ContentProvider of the specified Uri
  • public String getType (Uri) is used to return the MIME type of data in the specified Uri

Data access methods (such as insert(Uri, ContentValues) and update(Uri, ContentValues, Bundle)) can be called from multiple threads at the same time, and must be thread safe. Other methods, such as onCreate(), are called only from the main thread of the application, and lengthy operations must be avoided.

There is no code example here, but an example will be given at the end of the article to show the process used.

4.2 step 2: configure the content provider in the manifest file

Syntax:

<provider android:authorities="list"
          android:directBootAware=["true" | "false"]
          android:enabled=["true" | "false"]
          android:exported=["true" | "false"]
          android:grantUriPermissions=["true" | "false"]
          android:icon="drawable resource"
          android:initOrder="integer"
          android:label="string resource"
          android:multiprocess=["true" | "false"]
          android:name="string"
          android:permission="string"
          android:process="string"
          android:readPermission="string"
          android:syncable=["true" | "false"]
          android:writePermission="string" >
        . . .
</provider>

explain:
The content provider component needs to be registered in the manifest file (in fact, all four components of Android need to be registered). Writing content providers needs to inherit the ContentProvider class. All content providers in the application must be registered in the manifest file, otherwise the system will not know them and will not run them.
You can only register your own content providers in your application, not the content providers of other applications in your application.
Android will store the content provider reference according to the content provider URI you registered in the manifest configuration file (android:authorities="list" is this thing).

Properties:

  • android:authorities:
    This property has no default value, so it must be written. It is a list of one or more URI s that identify the data of the content provider. If you write more than one, separate them with semicolons. To avoid conflicts, naming should follow Java style naming conventions. Typically, the name of the content provider class is used. com.example.provider.cartoonproviderContentProvider
  • android:enabled
    Whether the system can instantiate the content provider. If yes, set it to "true"; If not, set to "false". The default value is "true".
  • android:exported
    Whether the content provider can be used by other applications.
    true: the content provider can be used by other applications. Any application can use the URI of the content provider to access it, but it needs to provide permission to access it.
    false: the provider is not available to other applications. Only your app access provider. Only applications with the same user ID (UID) as the content provider or applications temporarily granted access to the content provider through the android: grantUriPermissions attribute can be accessed.

4.3 step 3: add matching rules to static code blocks (use of UriMatcher)

The ContentProvider encapsulates the data and provides the data to other app s through the ContentResolver. When a data request is executed through the ContentResolver, it will parse the authority in the URI, send the request to the ContentProvider of the Authority registered by the system, and then parse the rest of the URI (mainly the path part) in the ContentProvider. The content provider parses the URI, and the rest of the work is done by the UriMatcher. If it is represented by images, it is roughly like this.

This class has a constant with a value of - 1

Public static final int NO_MATCH

Generally, we will use the above constants as the parameters of the construction method public UriMatcher (int code) to create the root node of the Uri tree. The parameter code is the code of the root node

Main usage:

  • public UriMatcher(int code): create a UriMatch object and create the root node of the Uri tree.
  • public void addURI (String authority,String path, int code): add a URI to the URI tree. The parameter code corresponds to the URI.
    The authority of the first parameter URI (used to identify the entire ContentProvider, which should be consistent with that registered in the manifest file)
    The path of the second parameter URI (usually used to distinguish different tables)
    The third parameter URI matches the returned value successfully
  • Public int match (URI): match the parameter URI with the URI added by the addURI() method.

4.4 step 4: expose the method you want to expose (CRUD)

First, let's talk about CURD. CRUD refers to the acronyms of create, retrieve, update and delete in calculation processing.

The way to expose yourself is to let other applications perform what operations on their own content providers. You only need to write the corresponding table in the corresponding methods of their own content providers. The following example is easy to understand.

Application a has two tables. Table a and table b want to be exposed to the outside world. Table a only allows external additions and queries. Table b allows external addition, deletion, modification and query.
First, we need to add two return values when writing the addURI. Because both tables a and b have query operations, the matching value judgment of a and b is written in the query method of the content provider. For example, the deletion method has only table b, and the deletion method of the content provider only writes the matching value judgment of table b. Roughly as follows

//query
query(){
	switch(match Method returns the value of matching success){
		case a Return value of successful matching:
			Execution pair a Table query operation;
			break;
		case b Return value of successful matching:
			Execution pair b Table query operation;
			break;
	}
}
//insert
insert(){
	switch(match Method returns the value of matching success){
		case a Return value of successful matching:
			Execution pair a Table lookup insert operation;
			break;
		case b Return value of successful matching:
			Execution pair b Table insert operation;
			break;
	}
}
//modify
update(){
	switch(match Method returns the value of matching success){
		case b Return value of successful matching:
			Execution pair b Table modification
			break;
	}
}
//delete
delete(){
	switch(match Method returns the value of matching success){
		case b Return value of successful matching:
			Execution pair b Table delete operation
			break;
	}
}

In this way, write the operation to be exposed into the corresponding method of the content provider, match the method, and then execute the operation.

4.5 step 5: other applications operate the database through the content provider

That is, you use the contentResolver to execute any method, then pass in the URI to judge what table, and then the match method performs the corresponding operation on the ContentProvider to match what table
Because my is just an example, and then I write the four methods of addition, deletion, modification and query into a tree, mainly to reflect the process of addURI and match.

V Actual combat drill

Application A

acticity_main.xml

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ListView
        android:id="@+id/lv_lv"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </ListView>

</androidx.constraintlayout.widget.ConstraintLayout>

item.xml

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_vertical"
    android:orientation="horizontal">
    <ImageView
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:layout_marginLeft="5dp"
        android:background="#445566"></ImageView>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:layout_marginLeft="20dp"
        android:gravity="center_vertical"
        android:orientation="vertical">
        <TextView
            android:id="@+id/tv_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="5dp"
            android:text="full name">
        </TextView>
        <TextView
            android:id="@+id/tv_phone"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="5dp"
            android:text="Telephone"></TextView>
    </LinearLayout>
</LinearLayout>

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private ListView listView;
    private List<Person> persons;
    private Handler handler=new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            switch(msg.what){
                case 100:
                    listView.setAdapter(new MyAdapter());
                    break;
            }
        }
    };

    private class MyAdapter extends BaseAdapter{

        @Override
        public int getCount() {
            return persons.size();
        }

        @Override
        public Object getItem(int i) {
            return persons.get(i);
        }

        @Override
        public long getItemId(int i) {
            return ((Person) persons.get(i)).getId();
        }

        @Override
        public View getView(int i, View view, ViewGroup viewGroup) {
            Person person=persons.get(i);
            View view1=View.inflate(MainActivity.this,R.layout.item,null);
            TextView tv_name= view1.findViewById(R.id.tv_name);
            TextView tv_phone=view1.findViewById(R.id.tv_phone);
            tv_name.setText("full name:"+person.getName());
            tv_phone.setText("Telephone:"+person.getNumber());
            return view1;
        }
    }

    public void addDate(){
        PersonDao dao=new PersonDao(this);
        long number=12300000000L;
        for(int i=0;i<10;i++){
            dao.add("Outstanding"+i,number+i+"");
        }
    }

    private void getPersons(){
        Uri uri =Uri.parse("content://cn.edu.daqing.mt.provider/query");
        ContentResolver contentResolver=getContentResolver();
        Cursor cursor=contentResolver.query(uri,null,null,null,null);
        persons=new ArrayList<>();
        if(cursor==null){
            return;
        }
        while (cursor.moveToNext()){
            Person person=new Person();
            person.setName(cursor.getString(cursor.getColumnIndex("name")));
            person.setNumber(cursor.getString(cursor.getColumnIndex("number")));
            person.setId(cursor.getColumnIndex("id"));
            persons.add(person);
        }
        cursor.close();
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        listView=findViewById(R.id.lv_lv);
        new Thread(){
            @Override
            public void run() {
                super.run();
                //addDate();
                getPersons();
                if(persons.size()>0){
                    handler.sendEmptyMessage(100);
                }
            }
        }.start();

    }
}

Person.java

public class Person {
    private int id;
    private String name;
    private String number;

    public Person(){

    }

    public Person(int id, String name, String number) {
        this.id = id;
        this.name = name;
        this.number = number;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", number='" + number + '\'' +
                '}';
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }
}

PersonDao.java

public class PersonDao {

    private PersonSQLiteOpenHelper helper;

    public PersonDao(Context context){
        this.helper=new PersonSQLiteOpenHelper(context);
    }

    public long add(String name ,String number){
        SQLiteDatabase db=helper.getWritableDatabase();
        ContentValues contentValues =new ContentValues();
        contentValues.put("name",name);
        contentValues.put("number",number);
        long id =db.insert("person",null,contentValues);
        db.close();
        return id;
    }
}

PersonDBProvider.java

public class PersonDBProvider extends ContentProvider {

    private PersonSQLiteOpenHelper helper;
    private static final int INSERT=1;
    private static final int DELETE=2;
    private static final int UPDATE=3;
    private static final int QUERY=4;
    private static final int QUERYONE=0;

    private static UriMatcher matcher=new UriMatcher(UriMatcher.NO_MATCH);

    static {
        matcher.addURI("cn.edu.daqing.mt.provider","insert",INSERT);
        matcher.addURI("cn.edu.daqing.mt.provider","delete",DELETE);
        matcher.addURI("cn.edu.daqing.mt.provider","update",UPDATE);
        matcher.addURI("cn.edu.daqing.mt.provider","query",QUERY);
        matcher.addURI("cn.edu.daqing.mt.provider","query/#",QUERYONE);
    }
    @Override
    public boolean onCreate() {
        helper= new PersonSQLiteOpenHelper(getContext());
        return false;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
        switch (matcher.match(uri)){
            case QUERY:
                SQLiteDatabase db= helper.getReadableDatabase();
                Cursor cursor=db.query("person",strings,s,strings1,null,null,s1);
                return cursor;
            case QUERYONE:
                db=helper.getReadableDatabase();
                long id= ContentUris.parseId(uri);
                cursor=db.query("person",strings,"id=?",new String[]{id+""},null,null,s1);
                return  cursor;
            default:
                try {
                    throw new IllegalAccessException("Path mismatch query not executed");
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }

        }
        return  null;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        switch (matcher.match(uri)){
            case QUERY:
                return "vnd.android.cusor.dir/person";
            case QUERYONE:
                return  "vnd.android.cusor.item/person";
            default:
               break;
        }
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
        if(matcher.match(uri)==INSERT){
            SQLiteDatabase db=helper.getWritableDatabase();
            db.insert("person",null,contentValues);
            db.close();
        }else {
            try {
                throw new IllegalAccessException("Path mismatch query not executed");
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return uri;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
        int no = 0;
        if(matcher.match(uri)==DELETE){
            SQLiteDatabase db=helper.getWritableDatabase();
            no = db.delete("person",s,strings);
            db.close();
        }else {
            try {
                throw new IllegalAccessException("Path mismatch query not executed");
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return no;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {
        int no = 0;
        if(matcher.match(uri)==UPDATE){
            SQLiteDatabase db=helper.getWritableDatabase();
            no = db.update("person",contentValues,s,strings);
            db.close();
        }else {
            try {
                throw new IllegalAccessException("Path mismatch query not executed");
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return no;
    }
}

PersonSQLiteOpenHelper.java

public class PersonSQLiteOpenHelper extends SQLiteOpenHelper {

    public PersonSQLiteOpenHelper(Context context){
        super(context,"person.db",null,1);
    }

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        sqLiteDatabase.execSQL("create table person (id integer primary key autoincrement,name varchar(20),number varchar(20))");
    }

    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {

    }
}

AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.contentprovidera">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <provider
            android:authorities="cn.edu.daqing.mt.provider"
            android:name=".PersonDBProvider"
            android:permission="cn.edu.daqing.mt.provider.WZZ"
            android:exported="true"/>
    </application>

    <permission android:name="cn.edu.daqing.mt.provider.WZZ"></permission>
</manifest>

Application B

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private ListView listView;
    private List<Person> persons;
    private Handler handler=new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            switch(msg.what){
                case 100:
                    listView.setAdapter(new MyAdapter());
                    break;
            }
        }
    };

    private class MyAdapter extends BaseAdapter{

        @Override
        public int getCount() {
            return persons.size();
        }

        @Override
        public Object getItem(int i) {
            return persons.get(i);
        }

        @Override
        public long getItemId(int i) {
            return ((Person) persons.get(i)).getId();
        }

        @Override
        public View getView(int i, View view, ViewGroup viewGroup) {
            Person person=persons.get(i);
            View view1=View.inflate(MainActivity.this,R.layout.item,null);
            TextView tv_name= view1.findViewById(R.id.tv_name);
            TextView tv_phone=view1.findViewById(R.id.tv_phone);
            tv_name.setText("full name:"+person.getName());
            tv_phone.setText("Telephone:"+person.getNumber());
            return view1;
        }
    }



    private void getPersons(){
        Uri uri =Uri.parse("content://cn.edu.daqing.mt.provider/query");
        ContentResolver contentResolver=getContentResolver();
        Cursor cursor=contentResolver.query(uri,null,null,null,null);
        persons=new ArrayList<>();
        if(cursor==null){
            return;
        }
        while (cursor.moveToNext()){
            Person person=new Person();
            person.setName(cursor.getString(cursor.getColumnIndex("name")));
            person.setNumber(cursor.getString(cursor.getColumnIndex("number")));
            person.setId(cursor.getColumnIndex("id"));
            persons.add(person);
        }
        cursor.close();
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        listView=findViewById(R.id.lv_lv);
        new Thread(){
            @Override
            public void run() {
                super.run();
                getPersons();
                if(persons.size()>0){
                    handler.sendEmptyMessage(100);
                }
            }
        }.start();

    }
}

Person.java

public class Person {
    private int id;
    private String name;
    private String number;

    public Person(){

    }

    public Person(int id, String name, String number) {
        this.id = id;
        this.name = name;
        this.number = number;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", number='" + number + '\'' +
                '}';
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }
}

AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.contentproviderb">

    <uses-permission android:name="cn.edu.daqing.mt.provider.WZZ"></uses-permission>
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

The display layout of application B is consistent with that of application A, and the activity in application A will be applied_ main. XML and item Just copy the XML.

Follow up articles are constantly updated....

Topics: Java Android Apache