ContentProvider组件

  1. ContentPrsovider的概念
  2. ContentProvider的基本使用
  3. ContentResolver内容观察者
  4. ContentProvider的常见应用
    1. 操作系统短信 (直接操作表)
    2. 操作系统联系人 (直接操作表)
    3. 联系人信息的获得 (操作系统给我们提供好的URL)
    4. 来电日志的删除 (内容观察者)
    5. 监听短信 (内容观察者)

01-ContentPrsovider的概念

  1. 目的:就是把自己私有的数据库内容通过内容提供者给暴露出来
  2. 内容提供者是以Uri进行传递的,其他组件都是都通Intent来传递信息的 URI的命名规则:Content://主机名/path/id

02-ContentProvider的使用

1. 内容提供者APP - 定义一个继承SQLiteOpenHelper的类来创建一个数据库- 定义一个类继承内容提供者在类中重写了CRUD的方法- 需要在清单文件中配置2. 内容解析者APP- 获得内容解析者对数据库进行URCD的操作

//定义一个继承SQLiteOpenHelper的类来创建一个数据库
public class UserSQLiteOpenHelper extends SQLiteOpenHelper {private static final String DB_NAME = "user.db";private static final int VERSION = 1;private UserSQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) {super(context, name, factory, version);}public UserSQLiteOpenHelper(Context context) {this(context, DB_NAME, null, VERSION);}@Overridepublic void onCreate(SQLiteDatabase db) {// 初始化两张表// t_womanString sql = "create table t_woman(_id integer primary key,c_name varchar(20),c_age integer,c_phone varchar(12))";db.execSQL(sql);// t_manString sql2 = "create table t_man(_id integer primary key,c_name varchar(20),c_age integer,c_phone varchar(12))";db.execSQL(sql2);}@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
}

//定义一个继承SQLiteOpenHelper的类来创建一个数据库
public class UserContentProvider extends ContentProvider {
//该定义其实也算是单例的一种private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);private static final int MATCH_WOMAN = 1;private static final int MATCH_MAN = 2;static{//给UriMatcher初始化,添加其可以支持的uri//定义了两条路径1.content://com.itheima.provider/t_woman  //            2.content://com.itheima.provider/t_man ,其他的URI的访问将通不过sUriMatcher.addURI("com.itheima.userProvider", "t_woman", MATCH_WOMAN);sUriMatcher.addURI("com.itheima.userProvider", "t_man", MATCH_MAN);}private UserSQLiteOpenHelper userSQLiteOpenHelper;/** 调用的时机:at application launch time 在主线程中被调用*/@Overridepublic boolean onCreate() {//在内容提供者中获取Context对象Context context = getContext();//初始化SQLiteOpenHelperuserSQLiteOpenHelper = new UserSQLiteOpenHelper(context);//返回值:如果初始化成功了,就返回true.return true;}/** 参数1:内容解析者传递过来的uri* 参数2:内容解析者访问内容提供者时传递的参数*/@Overridepublic Uri insert(Uri uri, ContentValues values) {SQLiteDatabase database = userSQLiteOpenHelper.getWritableDatabase();String tableName = getTableName(uri);if (TextUtils.isEmpty(tableName)) {return null;}long insert = database.insert(tableName, null, values);Log.d("tag", "insert:"+insert);//当进行该项操作时会给内容观察者发一条消息.getContext().getContentResolver().notifyChange(uri, null);//需要返回插入成功后的id//但是这里只能返回一个uri类型的数据,不能直接返回id//解决方法:uri+id Uri withAppendedId = ContentUris.withAppendedId(uri, insert);return withAppendedId;}//自定义的方法private String getTableName(Uri uri) {String tableName = "";int match = sUriMatcher.match(uri);switch (match) {case MATCH_WOMAN:tableName = "t_woman";break;case MATCH_MAN:tableName = "t_man";break;case UriMatcher.NO_MATCH:Log.d("tag", "错误的uri地址:"+uri);return null;default:break;}return tableName;}/** 参数2:内容解析者提供的where表达式* 参数3:where表达式中的?号的真实值*/@Overridepublic int delete(Uri uri, String selection, String[] selectionArgs) {SQLiteDatabase database = userSQLiteOpenHelper.getReadableDatabase();String tableName = getTableName(uri);//删除成功的行数int delete = database.delete(tableName, selection, selectionArgs);return delete;}@Overridepublic int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {SQLiteDatabase database = userSQLiteOpenHelper.getWritableDatabase();String tableName = getTableName(uri);/** 参数1:表名* 参数2:要更新的字段的key和value*/int update = database.update(tableName, values, selection, selectionArgs);//将影响的行数返回给内容解析者return update;}/** 参数2:要查询哪些字段*/@Overridepublic Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {SQLiteDatabase database = userSQLiteOpenHelper.getReadableDatabase();String tableName = getTableName(uri);/** 参数2:要查询哪些字段*/Cursor cursor = database.query(tableName, projection, selection, selectionArgs, null, null, sortOrder);//给内容解析者返回游标return cursor;}@Overridepublic String getType(Uri uri) {return null;}
}

//注册文件
<provider android:name="com.example.contentProvider.UserContentProvider"//主机名android:authorities="com.itheima.userProvider"//此步需要设为true,不设置也是可以的,建议设置android:exported="true" >
</provider>

//内容解析者APP,获得内容解析者对数据库进行URCD的操作
public class MainActivity extends Activity {private ListView lv_users;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);lv_users = (ListView) findViewById(R.id.lv_users);  }public void insertWoman(View view){/** 1. 获取内容解析者*/ContentResolver contentResolver = getContentResolver();ContentValues values = new ContentValues();values.put("c_name", "lucy"+new Random().nextInt(100));values.put("c_age", 20+new Random().nextInt(30));values.put("c_phone", "110");/** 2. 调用内容解析者的insert方法*/Uri insert = contentResolver.insert(Uri.parse("content://com.itheima.userProvider/t_woman"), values);/** 3. 将返回的uri中的id再解析出来*/long parseId = ContentUris.parseId(insert);Toast.makeText(this, "插入成功后的id是:"+parseId, Toast.LENGTH_SHORT).show();}public void insertMan(View view){/** 1. 获取内容解析者*/ContentResolver contentResolver = getContentResolver();ContentValues values = new ContentValues();values.put("c_name", "高斯雷"+new Random().nextInt(100));values.put("c_age", 30+new Random().nextInt(30));values.put("c_phone", "120");/** 2. 调用内容解析者的insert方法*/Uri insert = contentResolver.insert(Uri.parse("content://com.itheima.userProvider/t_man"), values);/** 3. 将返回的uri中的id再解析出来*/long parseId = ContentUris.parseId(insert);Toast.makeText(this, "插入成功后的id是:"+parseId, Toast.LENGTH_SHORT).show();}public void deleteMan(View view){/** 获取内容解析者,然后调用delete方法* 需求:将年龄大于54的干掉*/int delete = getContentResolver().delete(Uri.parse("content://com.itheima.userProvider/t_man"), "c_age>?", new String[]{"54"});Toast.makeText(this, "成功删除了:"+delete, Toast.LENGTH_SHORT).show();}public void updateMan(View view){ContentValues values = new  ContentValues();values.put("c_name", "雨泽2");/** 需求:将年龄小于40的记录的c_name改为宇泽* * update t_user set c_name='雨泽' where c_age<40;* */int update = getContentResolver().update(Uri.parse("content://com.itheima.userProvider/t_man"), values, "c_age<?", new String[]{"40"});Toast.makeText(this, "修改了:"+update, Toast.LENGTH_SHORT).show();}public void queryMan(View view){List<String> users = new ArrayList<>();/** 最后一个参数:排序表达式,不能包好order by本身 */Cursor cursor = getContentResolver().query(Uri.parse("content://com.itheima.userProvider/t_man"), new String[]{"c_name","c_age","c_phone"}, null, null, "c_age desc");/** 遍历cursor*/while(cursor.moveToNext()){String name = cursor.getString(0);int age = cursor.getInt(1);String phone = cursor.getString(2);users.add("name="+name+"\nage="+age+"\nphone="+phone);}cursor.close();//将数据显示到ListView上lv_users.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, users));}
}

03. ContentResolver内容观察者

  1. 需要先在内容提供者中发一条消息:
    getContext().getContentResolver().notifyChange(uri, null);

  2. 内容观察者

    1. ContentObserver 对对数据的变化进行监听
    2. 需要定义一个类继承内容观察者,复写他的onChange()方法
    3. 然后注册内容观察者,反注册可有可无,可以实现验证码,自动填充.
      getContentResolver().registerContentObserver(uri, true, new MyContentObserver(new Handler()));

public class MainActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//[1]注册内容观察者 //第二个参数的含义是,是否派生到uri的子类,true表示,该Uri下派生的其他URI的变化都可以接受到,Uri uri = Uri.parse("content://com.itheima.provider");getContentResolver().registerContentObserver(uri, true, new MyContentObserver(new Handler())); }//定义一个内容观察者 private class MyContentObserver extends ContentObserver{public MyContentObserver(Handler handler) {super(handler);} //当内容发送改变的时候调用@Overridepublic void onChange(boolean selfChange) {System.out.println("哈哈哈 数据库的内容发送了改变 ");super.onChange(selfChange);} }
}

04. ContentProvider的常见应用

  1. 操作系统短信 (直接操作表)
  2. 操作系统联系人 (直接操作表)
  3. 联系人信息的获得 (操作系统给我们提供好的URL)
  4. 来电日志的删除 (内容观察者)
  5. 监听短信 (内容观察者)

01-操作系统短信(短信的备份与还原)


//1. 操作系统短信 ,使用场景:短信的备份与还原
public class MainActivity extends Activity {private ListView lv_sms;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);lv_sms = (ListView) findViewById(R.id.lv_sms);}public void readSms(View view){Cursor cursor = getContentResolver().query(Uri.parse("content://sms"), new String[]{"address","date","type","body","_id"}, null, null, null);/** 参数3:是否重查,如果true,那么当短信内容改变的时候,CursorAdapter会自动更新ListView* 注意:cursorAdapter所使用的Cursor中必须用_id字段*/CursorAdapter cursorAdapter = new CursorAdapter(this,cursor,true) {@Overridepublic View newView(Context context, Cursor cursor, ViewGroup parent){TextView textView = new TextView(context);textView.setTextSize(26);textView.setTextColor(Color.BLUE);return textView;}@Overridepublic void bindView(View view, Context context, Cursor cursor) {TextView textView = (TextView)view;Sms sms = new Sms();sms.address =  cursor.getString(0);sms.date = cursor.getLong(1);sms.type = cursor.getInt(2);sms.body = cursor.getString(3);textView.setText(sms.toString());}};lv_sms.setAdapter(cursorAdapter);}public void insertSms(View view){ContentValues values = new ContentValues();values.put("address", "95555");values.put("type", 1);values.put("date", new Date().getTime());values.put("body", "您尾号5553的招行卡,入账61020.25元,备注:7月份工资.详情请访问:http://www.cmbchina.com/");Uri insert = getContentResolver().insert(Uri.parse("content://sms"), values);long parseId = ContentUris.parseId(insert);Toast.makeText(this, "插入成功:"+parseId, Toast.LENGTH_SHORT).show();   }
}
//权限
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.WRITE_SMS"/>

02-操作系统联系人(直接操作表)


//操作系统联系人,使用场景:微信,QQ读取联系人
//注:在手机上删除联系人后,仅仅是把raw_contects表中的一个标记字段改为了1,默认是0.在data表并没有删除//数据库的名字是/data/data/com.android.providers.contacts/databases/包下的contact2.db 数据库。
1. 系统联系人表结构1. data表  contact_id2. mimetypes 记录通讯录中支持的所有的数据类型.叫数据字典表,只能查询,不会增加.3. raw_contacts表:记录用户的id,一个用户一个id  2. 查询步骤1. 首先查询raw_contacts表,cursor,遍历cursor,contact_id = 1;2. 再带着contact_id=1,去查询data表3. 遍历data表中查到的字段,然后根据字段中的mimeType区分当前遍历到的字段到底是啥数据3. 插入系统联系人步骤1. 首先读取raw_contacts表中最大的contact_id,然后contact_id+1作为我们新的id,1. select contact_id from raw_contacts order by contact_id desc limit 1;2. getCount()的方式也是可以的2. 将contact_id+1插入到raw_contacts表中3. 有几个字段就让data表中插入几行记录(raw_contact_id,mimeytype,data1)
4.系统读取短信的流程是先去读raw_contacts,只有当该表的标记字段是0的时候才会能够显示在手机的联系人中.
public class MainActivity extends Activity {private ListView lv_contact;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);lv_contact = (ListView) findViewById(R.id.lv_contact);}public void readContact(View view){List<Contact> contactList = new LinkedList<>();ContentResolver contentResolver = getContentResolver();/** 1. 首先查询raw_contacts表,cursor,遍历cursor,contact_id = 1;*/Cursor cursor = contentResolver.query(Uri.parse("content://com.android.contacts/raw_contacts"), new String[]{"contact_id"}, "deleted=?", new String[]{"0"}, null);/** 2. 遍历cursor,再带着contact_id=1,去查询data表*/while(cursor.moveToNext()){//取出联系人的idint contact_id = cursor.getInt(0);//1 2//创建一个JavaBean,一个id就代表一个JavaBeanContact contact = new Contact();//去data表中查询,记得改表名/** 注意:data表中看着是mimetype_id,其实真正查询的时候会被转换为mimetype字段,并且返回的是字符串* 注意:data表中国看着是raw_contact_id,其实真正查询的时候会被转换为contact_id,其实两者都是可以的*/Cursor cursor2 = contentResolver.query(Uri.parse("content://com.android.contacts/data"), new String[]{"data1","mimetype"}, "contact_id=?", new String[]{contact_id+""}, null);while(cursor2.moveToNext()){String data1 = cursor2.getString(0);String mimetype = cursor2.getString(1);//根据mimetype区分当前的数据类型,然后封装到JavaBean上if ("vnd.android.cursor.item/name".equals(mimetype)) {contact.name = data1;}else if("vnd.android.cursor.item/phone_v2".equals(mimetype)){contact.phone = data1;}else if ("vnd.android.cursor.item/email_v2".equals(mimetype)) {contact.email = data1;}else if ("vnd.android.cursor.item/postal-address_v2".equals(mimetype)) {contact.address = data1;}}cursor2.close();//将当前Contact添加到集合中contactList.add(contact);}cursor.close();lv_contact.setAdapter(new ArrayAdapter<Contact>(this, android.R.layout.simple_list_item_1, contactList));}public void insertContact(View view){Contact contact = new Contact();ContentResolver contentResolver = getContentResolver();contact.address = "深圳福田区福中三路";contact.phone = "(0755)82100000";contact.email = "518000";contact.name = "许勤"+new Random().nextInt(1000);/** 1. 首先读取raw_contacts表中最大的contact_id,然后contact_id+1作为我们新的id*/int new_contact_id = 1;Cursor cursor = contentResolver.query(Uri.parse("content://com.android.contacts/raw_contacts"), new String[]{"contact_id"}, null, null, "contact_id desc limit 1");if(cursor!=null&&cursor.moveToNext()){int contact_id = cursor.getInt(0);new_contact_id = contact_id+1;}cursor.close();ContentValues values2 = new ContentValues();/** 2. 将contact_id+1插入到raw_contacts表中*/values2.put("contact_id", new_contact_id);contentResolver.insert(Uri.parse("content://com.android.contacts/raw_contacts"), values2 );/** 3. 有几个字段就让data表中插入几行记录(raw_contact_id,mimeytype,data1)*/ContentValues values = new ContentValues();//插入namevalues.put("raw_contact_id", new_contact_id);values.put("mimetype", "vnd.android.cursor.item/name");values.put("data1", contact.name);contentResolver.insert(Uri.parse("content://com.android.contacts/data"), values);//插入phonevalues.clear();values.put("raw_contact_id", new_contact_id);values.put("mimetype", "vnd.android.cursor.item/phone_v2");values.put("data1", contact.phone);contentResolver.insert(Uri.parse("content://com.android.contacts/data"), values);//插入addressvalues.clear();values.put("raw_contact_id", new_contact_id);values.put("mimetype", "vnd.android.cursor.item/postal-address_v2");values.put("data1", contact.address);contentResolver.insert(Uri.parse("content://com.android.contacts/data"), values);//插入emailvalues.clear();values.put("raw_contact_id", new_contact_id);values.put("mimetype", "vnd.android.cursor.item/email_v2");values.put("data1", contact.email);contentResolver.insert(Uri.parse("content://com.android.contacts/data"), values);Toast.makeText(this, "插入成功:"+contact, Toast.LENGTH_SHORT).show();}
}
//权限<uses-permission android:name="android.permission.READ_CONTACTS"/><uses-permission android:name="android.permission.WRITE_CONTACTS" />

03-联系人信息的获得(操作系统给我们提供好的URL)

  • 需求 : 获取本地通讯录中的联系人数据,获取联系人的名字,号码与ID号
  • 实现 : 通过系统定义好的常量去获取联系人数据
  • 联系人数据库地址: data/data/com.android.providers.contacts/databases/contacts2.db
  • URI简介

    • 联系人电话Uri: content://com.android.contacts/data/phones
    • 联系人Email Uri: content://com.android.contacts/data/emails
  • Android官方有将上述URI进行封装,我们可以使用API以更简单的方式获取这些URI

    • 联系人电话Uri: ContactsContract.CommonDataKinds.Phone.CONTENT_URI
    • 联系人名字列名常量:ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME
    • 联系人号码列名常量:ContactsContract.CommonDataKinds.Phone.NUMBER
    • 联系人ID列名常量: ContactsContract.CommonDataKinds.Phone.CONTACT_ID
  • 读取联系人需要添加权限

    • 写权限
  • 代码 :

public static ArrayList<PhoneNumEntry> getAllContacts(Context context) {ArrayList<PhoneNumEntry> list = new ArrayList<PhoneNumEntry>();ContentResolver cr = context.getContentResolver();// 查询联系人号码的URIUri uri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;// 返回的数据的列String[] projection = new String[] {Phone.DISPLAY_NAME, Phone.NUMBER};String selection = null;String[] selectionArgs = null;String sortOrder = null;Cursor cursor = cr.query(uri, projection, selection, selectionArgs, sortOrder);if (cursor != null) {while (cursor.moveToNext()) {PhoneNumEntry entry = new PhoneNumEntry();String name = cursor.getString(0);String num = cursor.getString(1);entry.name = name;entry.num = num;list.add(entry);}}cursor.close();return list;
}
  • 需求 : 联系人图片数据的获得
  • 实现 : 通过 ContactsContract.Contacts.openContactPhotoInputStream()方法获取文件流, 然后通过BitmapFactory获取对应的Bitmap,通过拿到联系人的ID,追加在Contacts.CONTENT_URI后面,才能够拿到联系人的头像
  • 代码 :

public static Bitmap getPhoto(Context context, String id) {ContentResolver cr = context.getContentResolver();Uri contactUri = Uri.withAppendedPath(Contacts.CONTENT_URI, id);InputStream is = ContactsContract.Contacts.openContactPhotoInputStream(cr, contactUri);return BitmapFactory.decodeStream(is);
}

04-来电日志的删除(内容提供者)

  • 需求 :

    • 拦截骚扰号码以后, 在手机通话记录中会留有通话记录, 将其删除
  • 实现:
    • 添加权限 android.permission.WRITE_CALL_LOG
    • 让线程休眠一会儿, 再删除, 能否删除成功无法保证
    • 来电日志Uri uri = CallLog.Calls.CONTENT_URI;
    • 通过内容观察者. 代码:

final ContentResolver contentResolver = getContentResolver();
final Uri uri = CallLog.Calls.CONTENT_URI;
// true,当前注册的URI及其分支发生改变时,通知观察者
// false,仅当前注册的URI发生改变时,通知观察者,分支发生改变时,不通知观察者
boolean notifyForDescendents = true;
ContentObserver observer = new ContentObserver(new Handler()) {public void onChange(boolean selfChange) {contentResolver.delete(uri, CallLog.Calls.NUMBER + " = ?",new String[] { incomingNumber });// 取消监听contentResolver.unregisterContentObserver(this);};
};
contentResolver.registerContentObserver(uri, notifyForDescendents, observer);

05-短信监听(内容提供者)


//短信监听
//data/data/com.android.providers.telephony/databases/mmssms.db的短信数据库
public class MainActivity extends Activity {private SmsContentObserver smsContentObserver;private TextView tv_sms;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);tv_sms = (TextView) findViewById(R.id.tv_sms);/** 1. 注册内容观察者*///首先获取内容解析者,然后通过内容解析者注册内容观察者ContentResolver contentResolver = getContentResolver();/** 参数1: 你想观察哪个uri* 参数2:true,代表不仅可以观察"content://sms"还可以观察到所有以"content://sms"开头(派生)的uri的变化*/smsContentObserver = new SmsContentObserver(new Handler());//Uri uri = Uri.parse("content://sms/outbox");则只会收到发件箱的消息//发送短信:先放到草稿箱->再放到发件箱->放到普通的短信里,所以发送短信会收到三条信息.这三种信息都会放在sms表中contentResolver.registerContentObserver(Uri.parse("content://sms"), true, smsContentObserver);}class SmsContentObserver extends ContentObserver{//handler用于决定onChange方法在哪个线程中执行,handler是在主线,这运行在主线程中,如果是在子线中这是子线程中.默认在子线程中执行.public SmsContentObserver(Handler handler) {super(handler);}//覆写一个回调方法/** 参数1:是自己app发出的通知吗* 参数2:观察到变化的uri*///注意在这里查询到的是变化的一部分.原因在onChange()方法给我们传入了变化的Uri地址.@Overridepublic void onChange(boolean selfChange, Uri uri) {super.onChange(selfChange, uri);Log.d("tag", "selfChange="+selfChange+"/uri="+uri);Log.d("tag", "ThreadName="+Thread.currentThread().getName());//读取短信Cursor cursor = getContentResolver().query(uri, new String[]{"body","address","date","type"}, null, null, null);if (cursor.moveToNext()) {String body = cursor.getString(0);String address = cursor.getString(1);long date = cursor.getLong(2);int type = cursor.getInt(3);if (type==2) {tv_sms.setText("她在发短信:body="+body+"\naddress="+address+"\ntype="+type);Toast.makeText(MainActivity.this, "她在发短信:body="+body+"\naddress="+address+"\ntype="+type, Toast.LENGTH_LONG).show();}else if (type==1) {tv_sms.setText("她在收短信:body="+body+"\naddress="+address+"\ntype="+type);Toast.makeText(MainActivity.this, "她在收短信:body="+body+"\naddress="+address+"\ntype="+type, Toast.LENGTH_LONG).show();}}cursor.close();}}@Overrideprotected void onDestroy() {super.onDestroy();/** 2. 取消内容观察者*/if (smsContentObserver!=null) {//取消内容观察者,就是不在观察getContentResolver().unregisterContentObserver(smsContentObserver);smsContentObserver = null;}}
}

2016/9/15 13:48:19

ContentPrivider相关推荐

  1. 采用contentprivider扫描手机SD卡的图片资源

    Intent inten = new Intent(Intent.ACTION_PICK,MediaStore.Images.Media.EXTERNAL_CONTENT_URI);startActi ...

  2. 09_Android中ContentProvider和Sqllite混合操作,一个项目调用另外一个项目的ContentProvider

    1.  编写ContentPrivider提供者的Android应用 清单文件 <?xml version="1.0" encoding="utf-8"? ...

  3. WebView的使用之Android与JS通过WebView互调方法

    WebView的使用之Android与JS通过WebView互调方法 一.概述:  Android与JS通过WebView实现交互,实际上是: Android调用JS的代码: JS调用Android的 ...

  4. Android开发之路之 webview

    公众号: 欢迎关注我的个人公众号,来一起交流Android 开发知识 一.简介 本来不想专门找一节来介绍webview技术的,因为现在对于混合开发有很多的框架比如RN和FLutter,但是这些框架对于 ...

  5. Andorid-15k+的面试题。

    andorid开发也做了3年有余了,也面试很多加企业,借此机会分享一下,我们中遇到过的问题以及解决方案吧,希望能够对正在找工作的andoird程序员有一定的帮助. 特别献上整理过的50道面试题目 1. ...

  6. BAT等公司高薪招聘Android开发面试题目集锦

    整理过的50道面试题目 1.listView的优化方式 重用convertView viewHolder static class viewHolder 在列表里面有图片的情况下,监听滑动不加载图片 ...

  7. Android LruCache和DiskLruCache相结合打造图片加载框架(仿微信图片选择,照片墙)

    LrcCache和DiskLruCache相结合打造图片加载框架 转载请标明出处:http://blog.csdn.net/luoshishou/article/details/51299169 源码 ...

  8. android 图片读写,Android读取本地照片和视频相册

    前言 项目中经常要选择本地照片或者视频的需求,如果去扫描整个SD卡就太耗时间,其实Android系统在启动时就已经把整个设备中的多媒体文件信息(文件名,类型,大小等)都存到了数据库,然后提供了Cont ...

  9. rxjs_如何阅读rxjs大理石图

    rxjs To an experienced RxJS user, marble diagrams are helpful. To someone just starting out with RxJ ...

最新文章

  1. OpenCV4 C++学习 必备基础语法知识二
  2. 世界上最美丽的23个公式
  3. asp.net访问被拒绝,程序集无法加载原因与解决方法[摘录]
  4. EasyUI之简单实现Datagrid分页(C#)
  5. 数据分析之Pandas(一)
  6. 怎么用ajax导出word_Word文档太大怎么压缩?你应该用这种方法压缩
  7. .net怎么读_想考UKVI不知道怎么报名?亲亲,这边建议你阅读全文并收藏呢
  8. SuperSQL:跨数据源、跨DC、跨执行引擎的高性能大数据SQL中间件
  9. js DOM——JS学习笔记2015-7-2(第73天)
  10. 【转】QT实现不规则窗体
  11. swoft php怎么样,[原创]Swoole和Swoft的那些事 (Http/Rpc服务篇)
  12. plugin zsh-autosuggestions/zsh-syntax-highlighting not found
  13. 基于Labview的小波去噪
  14. 【Processing】使用vscode编辑运行Processing
  15. 用matlab表示线极化波,圆极化波及其MATLAB仿真_西电.doc
  16. 终端便捷ssh(免密)连接
  17. 解读|TARS开源项目发布Go语言版本
  18. C++ 内容基础知识
  19. 向鼠标右键添加菜单,例:向鼠标右键添加git bash here菜单
  20. Java 开发最容易写的 10 个bug

热门文章

  1. 前洛克希德马丁高工谈宽带RF接收机架构
  2. MFC Windows 程序设计[218]之网络打印机(附源码)
  3. safari保存视频_如何使用Safari的“阅读列表”保存文章供以后使用
  4. 5G时代的物联网:福器还是凶器
  5. java中带参数的try(){}语法含义
  6. 文件中查找并删除feff
  7. 2020.7.25T1挑竹签(jz暑假训练day10)
  8. 不知道怎样做自媒体视频剪辑?分享几个必备素材网站
  9. 泡沫?玩笑?PlusFo才是“复仇”利器
  10. m277dw恢复出厂设置_惠普M277dw打印机使用说明书(惠普M277dw打印机用户手册PDF资料)V1.0 官方版...