活动
1.Adroid应用开发特色
1)四大组件:活动(Activity)、服务(Service)、内容提供器(Content Provider)、广播接收器(Broadcast Receiver)
2)丰富的系统控件
3)SQLite数据库
4)强大的多媒体:例如音乐、视频、录音、拍照等
5)地理位置定位
2.活动状态:
1)运行状态:活动位于返回栈的栈顶。
2)暂停状态:活动不在栈顶,但是依旧可见。只有在系统内存极低的情况下,系统才会考虑回收这种活动。
3)停止状态:活动不在栈顶,并且完全不可见。系统依然会为停止状态的活动保存相应的状态和成员变量,但是这并不是完全可靠的,当其他地方需要内存时,处于停止状态的活动有可能会被系统回收。
4)销毁状态:活动从返回栈中移除后就是销毁状态。系统最倾向于回收处于这种状态的活动,从而保证手机的内存充足。
3.活动的生存周期:
1)onCreate():会在活动第一次被创建的时候调用,应该在这个方法中完成活动的初始化操作,比如加载布局,绑定事件等。
2)onStart():这个方法在活动由不可见变为可见的时候调用。
3)onResume():这个方法在活动准备好和用户进行交互的时候调用,此时的活动一定位于返回栈的栈顶,并且处于运行状态。
4)onPause():这个方法在系统准备去启动或恢复另一个活动的时候调用。通常会在这方法中将一些消耗CPU的资源释放掉,以及保存一些关键数据,但这个方法的执行一定要快,不然会影响到新的栈顶活动的使用。
5)onStop():这个方法在活动完全不可见的时候调用。他和onPause()方法的主要区别在于,如果启动的新活动是一个对话框式的活动,那么onPause()方法会得到执行,而onStop()方法并不会执行。
6)onDestroy():这个方法在活动被销毁之前调用,之后的活动的状态将变为销毁状态。
7)onRestart():这个方法在活动由停止状态变为运行状态的时候调用,也就是活动被重新启动了。
**4.onSaveInstanceState(Bundle outState):**此方法可以起到临时保存数据的作用。
5.活动的四种启动模式:
1)standard:系统不在乎返回栈中有这个活动,每次启动都会创建该活动的一个新的实例。
2)singleTop:如果返回栈的栈顶已经是该活动,则直接使用它,不会在创建新的活动实例。
3)singleTask:启动该活动时会在返回栈检查是否存在该活动的实例,有就直接使用,并且把这活动之上的活动统统出栈,如果没有就创建一个新的活动实例。
4)singleInstance:这个活动的会启用一个新的返回栈来管理这个活动,不管哪个应用程序来访问这个活动,都共用一个同一个返回栈,也就解决了共享活动实例的问题
**6.如何知晓当前是哪个活动:**可以写一个继承了AppCompatActivity的BaseActivity类,重写onCreat方法,加上Log.d(“BaseActivity”,getClass().getSimpleName());
7.可以写一个ActivityCollector类作为活动管理器,包含List来存放活动实例,以及add活动,remove活动,removeAll活动,以此实现一键退出程序的功能。
8.启动活动的最佳写法,《第一行代码》
广播
广播机制简介:
每个应用程序都可以对自己感兴趣的广播进行注册,这些广播可能来自系统,也可能来自于其他应用程序
广播类型:
- 标准广播:是一种完全异步执行的广播,广播发出之后所有的广播接收器几乎会同时接受到这条广播消息,他们之间没有任何先后顺序,效率较高但是意味着无法截断。
- 有序广播:是一种同步执行的广播,同一时刻只有一个广播接收器能接受到这条广播消息,当这个广播接收器中的逻辑执行完之后才会继续传播,优先级高的广播接收器可以先收到广播消息,并且前面的广播接收器还可以截断正在传递的广播,这样后面的广播就无法收到广播消息了。
接受系统广播:
Android内置了很多系统级别的广播,比如开机、电池的电量变化、时间或时区发生改变等。
注册广播:
-
动态注册:在代码中注册
-
使用:
- 在IntentFilter中添加自己想要的action
- 再创建一个广播接收器
- 在活动中通过registerReceiver传入上面两个参数,进行注册
- 别忘记动态注册的广播都要在onDestroy()中unregisterReceiver取消注册
-
代码:
public class TestBroadcastActivity extends AppCompatActivity { private IntentFilter intentFilter; private NetworkChangeReceiver networkChangeReceiver; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test_broadcast); intentFilter = new IntentFilter(); intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE"); networkChangeReceiver = new NetworkChangeReceiver(); registerReceiver(networkChangeReceiver,intentFilter); } class NetworkChangeReceiver extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent) { ToastUtils.showMessage(getApplicationContext(),"NetworkChangeReceiver--->onReceive--->NetworkChange"); Log.d("TestBroadcastActivity","onReceive"); } } @Override protected void onDestroy() { super.onDestroy(); unregisterReceiver(networkChangeReceiver); } }
-
-
静态注册:
-
在AndroidManifest.xml中注册
-
可以让程序在未启动的情况下接受广播
-
注意:不要在onReceive()方法中添加过多的逻辑或者进行任何的耗时操作,因为在广播接收器中是不允许开启线程的,如果onReceive()方法运行了较长时间而没有结束时,程序就会报错
-
代码
/*申请权限*/ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> /*注册广播*/ <receiver android:name=".BootCompleteReceiver" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED"/> </intent-filter> </receiver>
-
自定义广播:
-
发送标准广播:
将action传入Intent中,然后sendBroadcast即可发出标准广播。广播是使用Intent传递的,因此还可以在Intent中携带一些数据传递给广播接收器。
-
发送有序广播:
- 和标准广播一样,只是将sendBroadcast变为sendOrderedBroadcast
- 在设置的priority属性,设置优先级
- 可以在onReceive中调用abortBroadcast截断广播
本地广播:
-
概念
- 之前的广播都属于系统全局广播,即发出的广播可以被其他任何应用程序接收到,我们也可以接受来自于其他任何应用程序的广播,这样很容易引起安全性的问题。
- 本地广播机制:广播只能够在应用程序的内部进行传递,广播接收器也只能接受来自本应用程序发出的广播。
-
实践
-
获得LocalBroadcastManager:
localBroadcastManager = LocalBroadcastManager.getInstance(MyLocalBroadcastReceiverActivity.this);
-
别的和动态注册一样,唯一不同是在本地广播管理器中注册
localBroadcastManager.registerReceiver(myLocalBroadcastReceiver,intentFilter);
-
发送广播也要在本地广播管理器中发送
localBroadcastManager.sendBroadcast(intent);
-
最后别忘记在onDestroy方法中取消注册
protected void onDestroy() { super.onDestroy(); localBroadcastManager.unregisterReceiver(myLocalBroadcastReceiver); }
-
-
优点:
- 可以明确正在发送的广播不会离开我们的程序,因此不必担心机密数据泄露
- 其他程序无法将广播发送到我们程序内部,因此不必担心有安全漏洞的隐患
- 发送本地广播比发送系统广播更加高效
通过广播来强制下线:
-
base类(所有类都继承这个BaseActivity类):
public class BaseActivity extends AppCompatActivity { private ForceOfflineReceiver receiver; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityCollector.addActivity(this); } @Override protected void onDestroy() { super.onDestroy(); ActivityCollector.removeActivity(this); } @Override protected void onResume() { super.onResume(); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction("com.nick.example.FORCE_OFFLINE"); receiver = new ForceOfflineReceiver(); registerReceiver(receiver,intentFilter); } @Override protected void onPause() { super.onPause(); if(receiver!=null){ unregisterReceiver(receiver); } receiver = null; } class ForceOfflineReceiver extends BroadcastReceiver{ @Override public void onReceive(final Context context, Intent intent) { Log.d("ForceOfflineReceiver","onReceive..."); AlertDialog.Builder builder = new AlertDialog.Builder(context); //构建者模式 builder.setTitle("Warning").setMessage("You are forced to be offline. Please" + " try to login again...").setCancelable(false).setPositiveButton("OK", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { ActivityCollector.removeAll(); context.startActivity(new Intent(context,LoginActivity.class)); } }); builder.show(); } } }
-
销毁活动管理器(自定义的)可以用来关闭所有活动
public class ActivityCollector { public static List<Activity> activities = new ArrayList<>(); public static void addActivity(Activity activity){ activities.add(activity); } public static void removeActivity(Activity activity){ activities.remove(activity); } public static void removeAll(){ for(Activity activity:activities){ if(!activity.isFinishing()){ activity.finish(); } activities.clear(); } } }
-
最后再Main活动中发送一个广播,由于都是继承BaseActivity的,所以在BaseActivity中接受到广播消息之后弹出一个AlertDialog,并关闭所有活动,实现强制下线功能
持久化技术
持久化技术简介:
- 数据持久化就是将内存中的瞬时数据保存到存储设备中
- Android系统主要提供了三种方式用于简单的实现数据持久化功能
- 文件存储:
- SharedPreferences存储
- 数据库存储
文件存储:
- 概念:是Adroid中最基本的一种数据存储方式,它不对存储的内容进行任何的格式化处理。比较适用于存储一些简单的文本数据或二进制数据。
- 实现:
- Context类中提供了openFileOutput()方法,可以用于将数据存储到指定的文件中,接受两个参数第一个参数名,**注意:**这个参数名不能包含路径。第二个参数是文件的操作模式:MODE_PRIVATE和MODE_APPEND。
- MODE_PRIVATE:所写入的内容将会覆盖源文件中的内容
- MODE_APPEND则表示如果该文件已存在,就往文件里面追加内容,不存在就创建新文件。
- openFileOutput()方法返回一个FileOutputStream对象
- openFileInput()方法返回一个FileInputStream对象
SharedPreferences存储:
-
概念
- SharedPreferences是使用键值对的方式来存储数据的
- SharedPreferences还支持多种不同的数据类型存储
-
获取SharedPreferences对象三种方法
- Context类中的getSharedPreferences():第一个参数文件名,第二个参数操作模式
- Activity类中的getPreferences():只要一个操作模式参数,他会将当前类名作为SharedPreferences的文件名
- PreferenceManager类中的getDefaultSharedPreferences()方法:这是一个静态方法,接受一个Context参数,并自动使用当前应用程序的包名作为前缀来命名SharedPreferences文件
- 调用SharedPreferences对象的edit()方法获取SharedPreferences.Editor对象
- 向Editor对象中添加数据,例如putInt、putString等
- 调用apply()方法将添加的数据提交
-
代码:
//写入 SharedPreferences.Editor editor = getSharedPreferences("data",MODE_PRIVATE).edit(); editor.putString("username",username); editor.putString("password",password); editor.apply(); //读取 SharedPreferences sharedPreferences = getSharedPreferences("data",MODE_PRIVATE); sharedPreferences.getString("username",""); sharedPreferences.getString("password","");
数据库存储:
-
SQLite数据库存储:
详见《第一行代码》P221-229,比较麻烦就直接用LitePal操作数据库了
-
LitePal:
-
简介:
- LitePal是一款开源的Android数据库框架,采用对象映射(ORM)的模式
- 它将开发常用的一些数据库功能进行了封装
- 可以不用写SQL语句完成CRUD操作
- 将面向对象的语言和面向关系的数据库之间建立一种映射关系,就是对象关系映射了。
-
配置LitePal:
-
导入依赖
dependencies { ...... implementation 'org.litepal.android:java:3.0.0' }
-
在main下创建assets目录,然后创建litepal.xml文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8BlvGrBc-1623110801266)(C:\Users\Administrator.PC201809101449\AppData\Roaming\Typora\typora-user-images\1574608583906.png)]
-
编辑litepal.xml
<?xml version="1.0" encoding="utf-8"?> <litepal> <dbname value="BookStore"></dbname> <version value="1"></version> <list> <mapping class="com.nick.testlitepal.Book"></mapping> </list> </litepal>
-
在AdroidManifest.xml中配置LitePalApplication
<application android:name="org.litepal.LitePalApplication" ....
-
-
使用LitePal
- 配置完毕后,任意使用一次操作数据库,数据库都能创建出来
- 如果属性中有另外的javabean,需要在litepal.xml文件的list中配置mapping
- 记得要升级版本号
-
LitePal的CRUD操作:此操作需要Javabean继承LiteSupport类
public class Book extends LitePalSupport { ...
-
增加数据:
Book book = new Book(); ... book.save();
-
更新数据:
-
model.isSaved()在两种情况下会返回true,一种情况是已经调用save方法去添加数据了,一种是model对象通过LitePal查询出来的数据。
-
直接调用如果是保存过的数据,直接调用save方法就是更新了
-
先写需要更新的数据到一个Book中,然后在updateAll中写类似sql的语句完成更新
Book updateBook = new Book(); updateBook.setPrice(10000); updateBook.updateAll("price < ?","1000");
-
想要将数据更新成默认值可以用setToDefault方法,传入相应的列名就可以了
Book updateBook = new Book(); updateBook.setPrice(10000); updateBook.setToDefault("price"); updateBook.updateAll("price < ?","1000");
-
-
删除数据:
-
isSaved()为true的数据可以直接调用delete()方法,
-
调用LitePal.deleteAll() 方法
LitePal.deleteAll(Book.class,"price > ?","100");
-
如果不指定约束条件,就表明你要删除表中的所有数据
-
-
查询数据:
-
查询所有数据:
List<Book> list = LitePal.findAll(Book.class);
-
定制查询功能
List<Book> list = LitePal.select("name","author").find(Book.class); list = LitePal.where("price > ?","100").find(Book.class);
-
定制查询功能都对应sql语句
- select:指定查询哪几列的数据
- where:指定查询的约束条件
- order:用于指定结果排序方式—例子:LitePal.order(”price desc“).find(Book.class);指定按照书价从高到低
- limit:用于指定查询结果的数量
- offset:用于指定查询结果的偏移量
-
-
-
运行时权限
简介:
Android 6.0系统中开始引用运行时权限,从而更好的保护了用户的隐私和安全。
没有权限直接操作的话程序会崩溃。
权限分类:
- 普通权限:普通权限指的是那些不会直接威胁到用户的安全和隐私的权限,这些权限系统会帮我们进行授权,不需要用户手动去操作
- 危险权限:可能会触及用户隐私或者对设备安全性造成影响的权限,如获取设备联系人、定位等等。危险权限必须用户手动点击授权才可以。(危险权限表见《第一行代码》P248 )
拨打电话为例子:
-
在AndroidManifest中申请权限
<uses-permission android:name="android.permission.CALL_PHONE"/>
-
用ContextCompat.checkSelfPermission判断是否授权,没授权ActivityCompat.requestPermissions申请权限
if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED){ ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CALL_PHONE},1); }else{ call(); }
-
requestPermissions申请完权限后,无论同意否都会回调onRequestPermissionsResult方法,再次判断结果
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); switch (requestCode){ case 1: if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){ call(); }else{ Toast.makeText(getApplicationContext(),"You denied the permission",Toast.LENGTH_LONG).show(); } break; } }
内容提供器
简介:
- 内容提供器主要用于在不同的应用程序之间实现数据共享的功能。
- 他提供了一套完整的机制,允许一个程序访问另一个程序的数据,同时还能保证被访数据的安全性
- 不同于文件存储和SharedPreferences存储的两种全局可读写操作模式,内容只对一部分数据进行共享,从而保证隐私数据不会有泄露的风险。
读取系统中联系人信息例子:
-
在配置文件中添加一个ListView
-
利用Cursor查询数据:CONTENT_URI是用来Uri.parse()方法解析出来的结果,然后对Cursor进行遍历
cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,null,null,null);
-
还有记得申请权限:
<uses-permission android:name="android.permission.READ_CONTACTS"/>
-
主要代码:
public class MainActivity extends AppCompatActivity { private ArrayAdapter<String> adapter; private List<String> contacts = new ArrayList<>(); private ListView lv_Contacts; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); lv_Contacts = findViewById(R.id.lv_contacts); adapter = new ArrayAdapter<>(this,android.R.layout.simple_list_item_1,contacts); lv_Contacts.setAdapter(adapter); if(ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED){ ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.READ_CONTACTS},1); }else{ readContacts(); } } private void readContacts(){ Cursor cursor = null; try{ Log.d("readContacts","Try..."); //核心 cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,null,null,null); if(cursor != null){ while (cursor.moveToNext()){ String displayName = cursor.getString(cursor.getColumnIndex (ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)); String number = cursor.getString(cursor.getColumnIndex (ContactsContract.CommonDataKinds.Phone.NUMBER)); contacts.add(displayName + ":" + number); } adapter.notifyDataSetChanged(); } }catch (Exception e){ e.printStackTrace(); }finally { if(cursor != null){ cursor.close(); } } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); switch (requestCode){ case 1: if(grantResults.length>0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){ readContacts(); }else{ Toast.makeText(getApplicationContext(),"读取联系人信息失败...",Toast.LENGTH_LONG); } break; } } }
创建自己的内容提供器
- 写一个MyProvider继承ContentProvider,重写它的六个方法
- onCreate:通常会在这里完成对数据库的创建和升级,返回true表示初始化成功
- query:使用uri参数来确定查询哪张表,projection参数用于确定查询哪列,以及selection、selectionArgs参数用于约束查询哪些行
- insert:使用uri参数确定要添加到的表,待添加的数据存放在values。添加完成后返回一个表示这条新纪录的URI
- uodate:通过uri参数确定需要更新的数据,返回受影响的行数
- delete:使用uri参数确定删除哪一张表中的数据
- getType:根据传入的uri来返回相应的MIME类型
- 标准的URI写法:content://com.example.app.provider/table1
- 标志着期望访问com.example.app这个应用的table1表中的数据,还可以继续在后面加一个id
- 通配符:*:表示任意长度的任意字符 #:表示匹配任意长度的数字
- 再借助UriMatcher这个类可以轻松地实现匹配内容URI的功能
- 暂停…
通知(Notification)
简介:
- 当某个应用程序希望给客户发送一些提示信息,而又不在前台运行时,就可以借助通知来实现。
- 在手机最上方的状态栏中会显示一个通知的图标,下拉状态栏就可以看到通知的详细内容。
通知的基本使用:一定要记得应用程序要 开启通知 !!!
-
需要NotificationManager来对通知进行管理,可以调用getSystemService方法获取到。
NotificationManager manager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
-
需要使用Builder构造器来创建Notification对象:
Notification notification = new NotificationCompat.Builder(this);
-
上面只是创建了一个空的Notification对象,我们可以在build()方法之前连缀任意多的设置方法来创建一个丰富的通知
notification.setContentTitle("This is title") .setContentText("This is text") .setWhen(System.currentTimeMillis()) .setSmallIcon(R.mipmap.ic_launcher) .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)) .setPriority(100) .build();
-
最后只需调用NotificationManager的notify()方法就可以让通知显示出来了
manager.notify(1,notification);
第一个参数是id,需要保证通知的每个id都是不同的,第二个参数就是Notification对象了。
-
设置点击之后自动取消:.setAutoCancel(true),或者用NotificationManager的cansel方法,传入通知的id即可。
-
PedingIntent:
-
他和Intent很像,Intent更趋向于立即执行某个动作,而PedingIntent更倾向于在某个合适的时机去执行某个动作,所以可以简单地理解为延迟执行的Intent
-
根据需求来选择三种不同获得PedingIntent的实例:getActivity、getBroadcast、getService,所接收的参数都是相同的。
-
第一个参数是Context,第二个一般传入0,第三个是Intent对象,第四个用于确定PendingIntent的行为,通常0。
Intent intent = new Intent(MainActivity.this,ShowActivity.class);PendingIntent pi = PendingIntent.getActivity(MainActivity.this,0,intent,0); notification.setContentIntent(pi);
-
通知进阶技巧:
通知可以设置音效,震动,显示灯的颜色以及闪烁时间,或者用系统默认的操作,注意申请权限。
通知的高级功能:注意手机需要对程序通知设置高优先级才能看到效果,不然只显示Title。
setStyle允许我们构建出丰富的文本内容,长文本、图片等
-
长文本:
.setStyle(new NotificationCompat.BigTextStyle().bigText("TTTTT" + "TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT" + "TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT"))
-
图片:
.setStyle(new NotificationCompat.BigPictureStyle() .bigPicture(BitmapFactory.decodeResource(getResources(),R.drawable.worktime)))
-
通知优先级:setPriority方法有五个选择
NotificationCompat.PRIORITY_MAX、MIN、HIGH、LOW、DEFAULT
运用手机多媒体
调用摄像头拍照:
-
XML中放一个Button和ImageView
-
按钮事件:
//用于存放摄像头拍下的图片 File outputImage = new File(getExternalCacheDir(),"output_image.jpg"); try { if(outputImage.exists()){ outputImage.delete(); } outputImage.createNewFile(); }catch (IOException e){ e.printStackTrace(); } //判断安卓系统版本 //高于Android7.0(Android7.0开始认为直接使用本地真实路径的Uri是不安全的) if(Build.VERSION.SDK_INT >= 24){ //FileProvideer是一种特殊的内容提供器 //第一个参数Context对象,第二个参数是任意唯一的字符串,第三个参数是刚刚创建的File image_Uri = FileProvider.getUriForFile(MainActivity.this, "com.nick.operatingcamera.provide",outputImage); }else{ image_Uri = Uri.fromFile(outputImage); } //启动相机 Intent intent = new Intent("android.media.action.IMAGE_CAPTURE"); //指定照片存储位置 intent.putExtra(MediaStore.EXTRA_OUTPUT,image_Uri); startActivityForResult(intent,TAKE_PHOTO);
-
在onActivitResult()中取出Bitmap对象,放到ImageView中。
case TAKE_PHOTO: if(resultCode == RESULT_OK){ try { //将照片显示出来 Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(image_Uri)); iv_Photo.setImageBitmap(bitmap); }catch (FileNotFoundException e){ e.printStackTrace(); } } break;
-
记得声明权限—为了兼容低版本的Adriod系统