Android进程间通信之ContentProvider
UriMatcher:
当我们在使用ContentProvide进行进程间通信时,那我们就必然会用到UriMatcher这个类,这个类的功能可以这样去理解,就是一段字符串与一个数值对应起来,只不过这段字符串是分段存储的,比如"path/****",那么在存的时候会先“/”对这个字符串进行分割然后在存起来,先来一段代码:
private static final String AUTHORITY = "com.zzq.uri"; matcher = new UriMatcher(UriMatcher.NO_MATCH); matcher.addURI(AUTHORITY,"path1/zzq1",1); matcher.addURI(AUTHORITY,"path1/zzq2",2); matcher.addURI(AUTHORITY,"path2/zzq3/*",3); matcher.addURI(AUTHORITY,"path2/zzq4/#",4); matcher.match(Uri.parse("content://com.zzq.uri/path1/zzq2"));
它的存储图形:
我们先来说它是如何匹配的,如果是我们自己定义的字符串匹配,那么这个协议头content://是可以随意取的,实际在这个匹配过程中,这个协议头实际是没有用到的,接下来就是看“com.zzq.uri/path1/zzq2”,内部匹配的时候实际也是讲这个字符串分成了三段,com.zzq.uri、path1、zzq2,分成三段后,先是第一段com.zzq.uri去匹配,匹配到的就只有一个,接下来在这个路径去匹配第二段,匹配到path1,再在path1路径下去匹配第三段,匹配到的结果就是zzq2,zzq2里面有记录添加时对应的数字,到这返回就是2了,对于添加时含有符号是#或*,那么在匹配时,#号对应的位置只要是数字就都可以匹配,而*号对应的位置只要是字符串就都可以匹配。这个类的作用有点像map集合,键是字符串,值是数字。如果还是不太清楚的可以去看源码。
UriMatcher的源码比较短。这里就直接贴出来了:
public class UriMatcher { public static final int NO_MATCH = -1; /** * Creates the root node of the URI tree. * * @param code the code to match for the root URI */ public UriMatcher(int code) { mCode = code; mWhich = -1; mChildren = new ArrayList<UriMatcher>(); mText = null; } private UriMatcher() { mCode = NO_MATCH; mWhich = -1; mChildren = new ArrayList<UriMatcher>(); mText = null; } /** * Add a URI to match, and the code to return when this URI is * matched. URI nodes may be exact match string, the token "*" * that matches any text, or the token "#" that matches only * numbers. * <p> * Starting from API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, * this method will accept a leading slash in the path. * * @param authority the authority to match * @param path the path to match. * may be used as a wild card for * any text, and # may be used as a wild card for numbers. * @param code the code that is returned when a URI is matched * against the given components. Must be positive. */ public void addURI(String authority, String path, int code) { if (code < 0) { throw new IllegalArgumentException("code " + code + " is invalid: it must be positive"); } String[] tokens = null; if (path != null) { String newPath = path; // Strip leading slash if present. if (path.length() > 0 && path.charAt(0) == '/') { newPath = path.substring(1); } tokens = newPath.split("/"); } int numTokens = tokens != null ? tokens.length : 0; UriMatcher node = this; for (int i = -1; i < numTokens; i++) { String token = i < 0 ? authority : tokens[i]; ArrayList<UriMatcher> children = node.mChildren; int numChildren = children.size(); UriMatcher child; int j; for (j = 0; j < numChildren; j++) { child = children.get(j); if (token.equals(child.mText)) { node = child; break; } } if (j == numChildren) { // Child not found, create it child = new UriMatcher(); if (token.equals("#")) { child.mWhich = NUMBER; } else if (token.equals("*")) { child.mWhich = TEXT; } else { child.mWhich = EXACT; } child.mText = token; node.mChildren.add(child); node = child; } } node.mCode = code; } /** * Try to match against the path in a url. * * @param uri The url whose path we will match against. * * @return The code for the matched node (added using addURI), * or -1 if there is no matched node. */ public int match(Uri uri) { final List<String> pathSegments = uri.getPathSegments(); final int li = pathSegments.size(); UriMatcher node = this; if (li == 0 && uri.getAuthority() == null) { return this.mCode; } for (int i=-1; i<li; i++) { String u = i < 0 ? uri.getAuthority() : pathSegments.get(i); ArrayList<UriMatcher> list = node.mChildren; if (list == null) { break; } node = null; int lj = list.size(); for (int j=0; j<lj; j++) { UriMatcher n = list.get(j); which_switch: switch (n.mWhich) { case EXACT: if (n.mText.equals(u)) { node = n; } break; case NUMBER: int lk = u.length(); for (int k=0; k<lk; k++) { char c = u.charAt(k); if (c < '0' || c > '9') { break which_switch; } } node = n; break; case TEXT: node = n; break; } if (node != null) { break; } } if (node == null) { return NO_MATCH; } } return node.mCode; } private static final int EXACT = 0; private static final int NUMBER = 1; private static final int TEXT = 2; private int mCode; private int mWhich; private String mText; private ArrayList<UriMatcher> mChildren; }
ContentProvide:
<provider android:authorities="com.example.zzq" android:name=".MyCOntentProvide" android:enabled="true" android:exported="true"/>对于注册的这个ContentProvide,别的应用如何找到呢?那这个就要看authorities这个属性了,我们访问ContentProvide都会用到getContentResolver()这个 方法,调用这个方法后返回的是ContentResolver对象,拿到这个对象后去访问ContentProvider需要我们传一个Uri进去,这里访问我们MyCOntentProvider的Uri是Content://com.example.zzq,这里用到的Uri就是authorities属性,到这里,虽说我们可以访问到ContentProvider,但是这个Uri并没指明他到ContentProvider去做什么,这个时候我们就可以在这个uri后面再添加路径,表明你这个uri是干嘛的,就好比https://baidu.com,这是百度的首页,如果我们要访问android百科,那么就是https://baike.baidu.com/item/Android/60243?fr=aladdin,在https://baidu.com后面加其他的路径,这样我们就可以访问到,ContentProvider用到的其实也是这个思路,只不过这后面的路径是需要我们自己去解析的,根据解析到的结果去做对应的处理,这里处理路径用到的就是UriMatcher。
public class MyCOntentProvide extends ContentProvider { private static final String TAG = "MyCOntentProvide"; private static final String authority = "com.example.zzq"; private static UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH); private static final String path1 = "path1"; private static final String path2 = "path2"; private static final String path3 = "path3"; private static final String path4 = "path4"; private static final String path5 = "path5"; private static final int code1 = 1; private static final int code2 = 2; private static final int code3 = 3; private static final int code4 = 4; private static final int code5 = 5; static{ matcher.addURI(authority,path1,code1); matcher.addURI(authority,path2,code2); matcher.addURI(authority,path3,code3); matcher.addURI(authority,path4,code4); matcher.addURI(authority,path5,code5); } @Override public boolean onCreate() { return false; } @Nullable @Override public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) { int match = matcher.match(uri); switch (match){ case code1: //你的处理 break; case code2: //你的处理 break; case code3: //你的处理 break; case code4: //你的处理 break; case code5: //你的处理 break; } MatrixCursor cursor = new MatrixCursor(new String[]{"name","age"}); cursor.addRow(new String[]{"zzq","12"}); return cursor; } @Nullable @Override public String getType(@NonNull Uri uri) { return "根据需要返回,也可以不做处理"; } @Nullable @Override public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { int match = matcher.match(uri); switch (match){ case code1: //你的处理 break; case code2: //你的处理 break; case code3: //你的处理 break; case code4: //你的处理 break; case code5: //你的处理 break; } return uri; } @Override public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) { return 1; } @Override public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) { return 2; } }当我们需要访问这个ContentProvider的时候,只需要调用getContentResolver()返回ContentResolver对象,然后调用ContentProvider的方法中对应的方就可以访问到,方法中需要我们传一个uri,比如上面这个事例,我们的uri中只要是content://com.example.zzq/...都是可以访问到这个ContentProvider的,那后面携带什么路径以及做什么对应的处理,那就是我们自己做定义了。比如我们访问的是一个uri = content://com.example.zzq/path2,
getContentResolver().query(uri,null,null,null,null);后面需要什么参数就传什么参数,通过上面的调用就可以访问到这里注册的ContentProvide,那么在调用match.match(uri)后,返回的就是2,和上面我们在UriMatcher中添加path2的数字对应上,如果还需要别的参数,可以自己定义,只要两边协商好就可以了。
对于ContentProvider的使用,这里可以分为两个步骤,一是ContentProvider的注册,二是uri解析:
1、对于ContentProvider的注册,其主要目的是对外提供访问这个ContentProvider的路径,也就是它的基路径,一般都是content://authorities的值,这个authorities就是注册这个ContentProvider的时候里面的一个属性。
2、有了基路径content://authoritie后,后面可以在加一些其他的路径content://authorities/...,这样访问到的也是你定义的这个ContentProvider,后面加的参数是什么参数,要做什么处理,那就是你自己定义了,Android为我们提供了对这个路径的处理,那就是UriMatcher。