Android Review—2
零蚀
-
数据持久化
-
文件存储
Context类提供了一个openFileOutput(“filename”, Mode)方法,可以将文件储存到指定的文件中,这个参数接收两个参数,- 第一个参数:文件名,在文件创建的时候用到的就是这个文件名,文件默认的存储的位置/data/data/< packagename >/files/ 目录下(没有时自建)。
- 第二个参数:操作模式,主要有两种模式,MODE_PERIVATE(写入内容覆盖原内容),MODE_APPEND(内容追加)。其他两种
MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE由于安全问题,已经在4.2被废除
//创建文件or获取文件路径,名称;输出流;缓冲写入 try { FileOutputStream outputStream = openFileOutput("fileName", MODE_PRIVATE); writer = new BufferedWriter(new OutputStreamWriter(outputStream)); writer.write("写入数据"); } catch (Exception e) { }finally { try { if(writer!=null) writer.close(); } catch (IOException e) { e.printStackTrace(); } }
//读取文件,找到名称;输入流;缓冲读出 try { FileInputStream input = openFileInput("filename"); InputStreamReader streamReader = new InputStreamReader(input); reader = new BufferedReader(streamReader); String line = ""; while ((line = reader.readLine()) != null) { Log.d(TAG, line); } } catch (Exception e) { } finally { try { if(read!=null) reader.close(); } catch (IOException e) { e.printStackTrace(); } }
-
SharedPerferences存储
- Context 类中的getSharedPrefences(“filename”,Context.MODE_PRIVATE)方法(不存在则创建)目前只有Context.MODE_PRIVATE一个模式,和传入0的效果一致,都是默认的模式,其他模式在6.0及以前全部废除。
- Activity 类中的getPreference(Context.MODE_PRIVATE)方法,其中Activity中的getSharedPreference调用的还是Context中的。getPreference()和getSharedPreferences类似,不过没有文件名,因为,它将当前的 活动类名 直接作为SharedPreferences的文件名。
- PreferenceManager 类中的getDefaultSharedPreference(context)静态方法,自动将当前的 应用包名 作为当前未命名的SharedPreferences文件名称,
SharedPreferences example = getSharedPreferences("example", MODE_PRIVATE); //获取编译对象 SharedPreferences.Editor edit = example.edit(); //存入键值对的内容 edit.putFloat("elem",1.1f); //提交 edit.apply(); //获取内容 example.getFloat(key,value)
存储路径:/data/data/< packagename > /shared_prefs/ 目录下的xml文件。
-
SQLite数据库存储
安卓为了更好的使用管理数据库,专门提供了一个SQLiterOpenHelper 抽象类,SQLiteOpenHelper有两个实例方法getReadableDatabase(),getWritableDateBase()连个方法都可以创建或打开(升级)一个现有数据库,并返回一个可以对数据库操作读写的对象。不同的是,当控件磁盘已满等不能读写的情况,getReadableDateBase()返回的对象将只以 只读 的方式打开,getWritableDataBase()将出现异常。
//数据库的实例 public class MySQLiteOpenHelper extends SQLiteOpenHelper { /** * @param context * @param name 数据库名,使用的时候需要指定的数据库名字 * @param factory 查询数据的时候会返回一个自定义Cursor,一般传入null * @param version 版本号 */ public MySQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) { super(context, name, factory, version); } String dataBase="create table book(" + "id integer primary key autoincrement," + "author text," + "price real," + //浮点 "page integer," + "name text)"; //bolb 二进制 @Override public void onCreate(SQLiteDatabase db) { //如过这里有符㷣个建表语句,且其中有一存在的表,则不会走create方法 db.execSQL(dataBase); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } }
数据库的创建
//数据库的创建等操作 MySQLiteOpenHelper helper = new MySQLiteOpenHelper(this, "Database.db", null, 1); //如果数据库查找不存在,代码走到getReaderDataBase时候,就会调用实例里面的onCreate方法 //如果数据库存在,则不会调用onCreate()方法。 SQLiteDatabase readableDatabase = helper.getReadableDatabase();
数据库的路径:/data/data/< packagename >/database/目录下
找到sqlite.db文件可用以下命令操作
- sqlite3 datebase.db 打开数据
- .table 查看数据库中的表哥
- .schema 查看简表语句
- .exit / .quit 退出建表语句
//数据库的升级 为了新添加表格,先把数据库的版本号升级为更高级的版本号,当检测到版本号大于之前的版本号,则调用onUpdata方法 @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("drop table if exists book"); db.execSQL("drop table if exists newbook"); onCreate(db) } //添加数据库 ContentValues values = new ContentValues(); values.put("key","value"); readableDatabase.insert("tableName",null,values); //更新数据 ContentValues values = new ContentValues(); values.put("price",10); //将书名字改成价格 readableDatabase.update("tablename",values,"name=?",new String[]{"bookname"}); //删除数据 readableDatabase.delete("tablename","price>?",new String[]{"500"}); //查寻数据 /** * @param table 查询表格的名字 * @param column 指定列 * @param selection 指定where的约束条件 * @param selectionArgs 为where提供占位符的具体值 * @param groupby 需要制定的groupby列 * @param having 对groupby的进一步约束 * @param orderby 指定查询结果的排序方式 */ Cursor cursor = readableDatabase.query("tablename", null, null, null, null, null, null, null ); while(cursor.moveToNext()!=null){ //获取位置索引,然后取值 String name = cursor.getString(cursor.getColumnIndex("name")); int page = cursor.getInt(cursor.getColumnIndex("page")); } //一定要关闭游标 cursor.close();
- LitePals数据库
-
-
Content Provider
- 权限
危险权限需要手动处理,这里的ContextCompat.checkShelfPermission()来检测是否权限被申请,否则,
if(ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE)!= PackageManager.PERMISSION_GRANTED){ ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.CALL_PHONE},1); } Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:10086" )); startActivity(intent);
- 数据共享
,sharePerferences在4.2时已经废除了应用之间数据共享的功能,现在访问共享内容,要用到contentResolver,可以通过Context中的getContentResolver方法获得实例,contentResolver提供了增删改查的API,其中不接受表名等形参,只接受URI,URI建立唯一标识符,主要有authority和path构成,都是用来避免冲突。 authority 用于区分应用,一般用包名来命名, path 一般来区分表名,除此之外,还要将两个标示的结合的URI字符串上添加Content的特有标示content://,所以综合的标准写法为:
content://< packagename >.provider/tablename
contentResolver中的增删改查也都是接收URI对象,使用表名则不知道具体的应用对象。
Uri uri=Uri.parse("content://<packagename>.provider/table1"); //insert ContentValues values=new ContentValues(); getContentResolver().insert(uri,values); //delete getContentResolver().delete(uri,"where=?",new String[]{"300"}); //update getContentResolver().update(uri,values,"column1=? and column2=?",new String[]{"text","1"}); //query getContentResolver().query(uri,"columnName","price>?",new String[]{"100"},"desc")
- 案例
if (ContextCompat.checkSelfPermission(this,Manifest.permission.READ_CONTACTS) !=PackageManager.PERMISSION_GRANTED){ ActivityCompat.requestPermissions(this,new String[]{ Manifest.permission.READ_CONTACTS},1); }else{ logContacts(); } /** * 查询通讯录信息 */ private void logContacts(){ Cursor query = getContentResolver() .query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null); while(query.moveToNext()){ String name = query.getString( query.getColumnIndex( ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)); String number = query.getString( query.getColumnIndex( ContactsContract.CommonDataKinds.Phone.NUMBER)); Log.e(TAG,"name:"+name+"/number:"+number); } }
- 案例ContentProvider
public class MyProvider extends ContentProvider { /** * 通常完成数据库的创建和升级 * 只有当存在ContentRedolver访问应用程序的数据时才会触发 * @return true provider初始化成功 */ @Override public boolean onCreate() { return false; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { return null; } /** * * @param uri 除了包名表名外还需要添加id,表示查询的是表中的某个数据,例如: * "content://<packageName>.provider/table1/i001" * "content://<packageName>.provider/table1/*" * ⚠️ * 表示任意字符串 * ⚠️ # 表示任意长度的数字 * @return 根据传入的内容uri,返回对应的MIME类型(文本,图片,音视频等) */ @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 0; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { return 0; } }
内容提供者的构成
public class MyProvider extends ContentProvider { //设置表内属性id public static final int TABLE1_ALL=0; public static final int TABLE1_ITEM=1; private static UriMatcher matcher; //添加期望内容,根据resolver传来的uri来判断是什么数据。 static { matcher = new UriMatcher(UriMatcher.NO_MATCH); matcher.addURI("<packageName>","table",TABLE1_ALL); matcher.addURI("<packageName>","table/#",TABLE1_ITEM); } //根据uri进行数据查找 @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { switch (matcher.match(uri)){ case TABLE1_ALL: //查询table1所有的内容 break; case TABLE1_ITEM: //查询table1指定条内容 default: break; } return null; } @Override public String getType(Uri uri) { switch (matcher.match(uri)){ case TABLE1_ALL: return "vnd.android.cursor.dir/vnd.com.example.app.provider.table1"; case TABLE1_ITEM: return "vnd.android.cursor.item/vnd.com.example.app.provider.table1"; } return null; } ........ }
getType()用来获取对象的对应MIME类型(必要)
⚠️ 开头必须以vnd开头
⚠️ 若URI是以路径结尾(表),则其后接android.cursor.dir/ 。如果是以id结尾(属性)则后接android.curdor.item/。
⚠️ 最后接上vnd..
例:vnd.android.cursor.dir/vnd.com.example.app.provider.table1在每一个增删改查中编辑好,myOpenHelp.getReadableDatabase().insert()/delete()…操作,Manifest注册后,然后在其他应用可通过contentResolver可获得内容。
- 权限
-
通知
Notification是Android的特色功能之一,可由系统服务所提供(NotificationManager) Context.getSystemService(Context.NOTIFICATION_SERVICE),各个版本的对通知这一功能多多少少都有改动,API并不稳定,由此用v4-support库提供的兼容的API。NotificationCompat.Builder(Context)此构造函数在API级别26中已弃用。请改用Notification.Builder(Context,String channel)。所有发布的通知必须指定NotificationChannel ID。如下:
manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){ NotificationChannel channel = new NotificationChannel( "channelID", "channelName", NotificationManager.IMPORTANCE_HIGH); manager.createNotificationChannel(channel); } Notification notification= new NotificationCompat.Builder(this,"channelID") .setContentTitle("Notification") //需要无色图,不能用RGB图 .setSmallIcon(R.mipmap.ic_launcher) .setColor(200) .setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher)) .build(); manager.notify(1,notification); //.setLights(Color.GREEN,1000,1000)呼吸灯颜色 //.setDefaults(NotificationCompat.DEFAULT_ALL)//震动呼吸声音
在上有一个setSmallIcon其中设置的是状态栏的小图标,官网在新版中规定不能设置带有颜色的图片,只能用无RGB(纯白)的icon代替了。
- 高级
当通知的内容(多文字,大图片)在通知栏无法显示完整时,一般会显示省略号,但是需求显示时,可以通过以下方式进行设置,
//文字过多(notification高度增加) .setStyle(new NotificationCompat.BigTextStyle() .bigText("NotificationNotificationNotif" + "icationNotificationNotification" + "NotificationNotificationNotifica" + "tionNotificationNotificationNotification")) //显示大图(在notification下显示大图) .setStyle(new NotificationCompat.BigPictureStyle() .bigPicture(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher)))
设置Notification的等级(优先级),存在五个等级,依次递减
- PRIORITY_MAX
- PRIORITY_HIGH
- PRIORITY_DEFAULT
- PRIORITY_LOW
- PRIORITY_MIN
代码如下
.setPriority(NotificationCompat.PRIORITY_MAX)
- 高级
总结: 系统管理对象+通道+通知的样式
-
照相机
-
调用系统相机
调用系统相机在7.0之之前只要用URI的fromFile()将File转成URI对象来标示图片的真实地址,之后,用FileProvider.getUriForFile()方法,通过provider进行调用,由于URI的真实路径被认为是不安全的,会直接抛异常, FileProvider中会提供一个内容提供者,进行类似包装的的机制,对数据进行保护,可选择数据的共享,提高安全性。//这里存在缓存里,是因为方便,SD存储是危险权限,要动态申请 File file=new File(getExternalCacheDir(),"out_put_img.png"); try { if(file.exists()){ file.delete(); } file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } Uri imgUri = Uri.fromFile(file); if(Build.VERSION.SDK_INT>=24){ //authority=<packagename>.fileprovider imgUri= FileProvider.getUriForFile(this,"com.example.basejavademo.fileprovider",file); } Intent intent=new Intent("android.media.action.IMAGE_CAPTURE"); //加入图片传输地址 intent.putExtra(MediaStore.EXTRA_OUTPUT,imgUri); //启动活动 startActivityForResult(intent,1);
创建共享路径,用来指定uri的共享,
// res文件下,新建file_paths.xml,name属性可以随便设置,路径/表示整个共享。 <?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-path name="my_image" path="/"/> </paths>
注册provider, 指定共享的路径
<provider android:authorities="<packagename>.fileprovider" android:name="android.support.v4.content.FileProvider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths"/> </provider>
在onActivityResult()来完成来处理返回的bitmap数据。
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); //Bitmap处理 switch (requestCode){ case 3: if (resultCode==RESULT_OK){ //判断手机版本号作出不同的处理 if(Build.VERSION.SDK_INT>=19){ //4.4以后的系统 //处理图拍 BitmapFactory.decodeFile(imagePath) String imgPath=afterKitKat(data); }else{ //4.4以前的系统 String imgPath=beforeKitKat(data); } } break; } }
-
从相册里选择照片
⚠️从4.4开始手机返回的不再是图片真实的uri了,而是封装之后的uri,此后必须解析这个uri才行。代码如下:
授权if (ContextCompat.checkSelfPermission(this,Manifest.permission.WRITE_EXTERNAL_STORAGE)!=PackageManager.PERMISSION_GRANTED){ ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},2); }else{ openAlbum(); } /* * * */ @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); //获权后的操作 switch (requestCode){ case 2: if(grantResults.length>0&&grantResults[0]==PackageManager.PERMISSION_GRANTED){ openAlbum(); } break; } }
跳转相册
//跳转相册 private void openAlbum() { Intent intent=new Intent("android.intent.action.GET_CONTENT"); intent.setType("image/*"); startActivityForResult(intent,3); }
跳转之后的处理
@Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); //Bitmap处理 switch (requestCode){ case 3: if (resultCode==RESULT_OK){ //判断手机版本号作出不同的处理 if(Build.VERSION.SDK_INT>=19){ //4.4以后的系统 //处理图拍 BitmapFactory.decodeFile(imagePath) String imgPath=afterKitKat(data); }else{ //4.4以前的系统 String imgPath=beforeKitKat(data); } } break; } }
获取图片的真实路径
private String getImagePath(Uri uri,String selection) { String path = null; //通过Uri和selection来获取真实的图片路径 Cursor cursor=getContentResolver().query(uri,null,selection,null,null); if (cursor!=null){ if(cursor.moveToFirst()){ path=cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA)); } cursor.close(); } return path; }
4.4之前的版本处理方式
//4.4以前的处理。 private String beforeKitKat(Intent intent) { Uri uri=intent.getData(); String imagePath = getImagePath(uri, null); return imagePath; }
4.4及以上的版本处理方式
//4.4以后的处理。解析URI private String afterKitKat(Intent intent) { String imagePath=null; Uri uri=intent.getData(); if(DocumentsContract.isDocumentUri(this,uri)){ //如果是document类型的uri,则通过document id来处理 String docId=DocumentsContract.getDocumentId(uri); if("com.android.providers.media.documents".equals(uri.getAuthority())){ String id =docId.split(":")[1]; String selection =MediaStore.Images.Media._ID+"="+id; imagePath=getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,selection); }else if("com.android.providers.downloads.documents".equals(uri.getAuthority())){ Uri contentUri=ContentUris.withAppendedId( Uri.parse("content://downloads/public_downloads"), Long.valueOf(docId)); imagePath=getImagePath(contentUri,null); } }else if ("content".equalsIgnoreCase(uri.getScheme())){ //如果是content的类型的uri则普通处理 imagePath=getImagePath(uri,null); }else if ("file".equalsIgnoreCase(uri.getScheme())){ //如果是file类型的uri直接展示图片即可 imagePath=uri.getPath(); } return imagePath; }
-
-
影音
- 播放音频
在安安卓中播放音频,一般是由MediaPlayer来实现的,提供了比较全面的方法,操作简单。
停止方法名 描述 setDataSouce() 设置播放的音视频位置 prepare() 开始播放前的,完成准备工作 start() 开始/继续 pause() 暂停播放 reset() MediaPlayer重置为刚创建的状态 seekto() 指定位置开始 stop() 停止,调用这个方法后MediaPlayer无法再播放 release() 释放掉MediaPlayer相关的资源 isPlaying() 是否正在播放 getDuration() 获取载入音频的时长 代码如下:
try { MediaPlayer player=new MediaPlayer(); //播放本地资源 //player.setDataSource("/mnt/sdcard/test.mp3") //播放远程的资源文件 //Uri uri = Uri.parse("http://**"); //player.setDataSource(Context, uri); player.setDataSource(""); player.prepare(); } catch (IOException e) { e.printStackTrace(); } //结束 if(player!=null){ player.stop(); player.release(); }
- 播放视频
播放视频住哟啊是运用VideoView来实现的。这个类将显示和控制集于一身。主要的常用方法如下:
方法名 描述 setVideoPath() 设置文件位置 start() 开始/继续 pause() 暂停 resume() 视频从头开始 seekto() 指定位置开始播放 isPlaying() 是否在播放 getDuration() 获取载入文件的时常 void setVideoPath(String path):以文件路径的方式设置VideoView播放的视频源。 //Uri uri = Uri.parse("http://**"); void setVideoURI(Uri uri):以Uri的方式设置VideoView播放的视频源,可以是网络Uri或本地Uri。
- 播放音频
笔记资料来自书籍《第一行代码》郭霖,无商业目的
🔗 前言
🔗 Android Review 列表
🔗 Android Review—1
🔗 Android Review—3
🔗 Android Review—4
🔗 Android Review—5