一、Activity组件
1、Activity 相关概念介绍
一个 Activity 包含了用户能够看到的界面,从而于用户进行交互。一个应用程序中可以有零个或者多个Activity。零个 Activity 就表示,这个应用程序不包含与用户交互的界面。
Android应用中每一个Activity都必须要在AndroidManifest.xml配置文件中声明注册,否则系统将不识别也不执行该Activity。
<activity
android:name=".LoginActivity"
android:exported="true"
android:label="@string/title_activity_login"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
2、Activity生命周期
Android 是使用任务(task)来管理Activity的,一个任务就是一组存放在栈里Activity的集合,这个栈也叫作返回栈。
Activity的生命周期由以下几个部分组成:
函数 |
描述 |
onCreat() |
一个Activity启动后第一个被调用的函数,常用来在此方法中进行Activity的一些初始化操作。例如创建View,绑定数据,注册监听,加载参数等。 |
onStart() |
当Activity显示在屏幕上时,此方法被调用但此时还无法进行与用户的交互操作。 |
onResume() |
这个方法在onStart()之后调用,也就是在Activity准备好与用户进行交互的时候调用,此时的Activity一定位于Activity栈顶,处于运行状态。 |
onPause() |
这个方法是在系统准备去启动或者恢复另外一个Activity的时候调用,通常在这个方法中执行一些释放资源的方法,以及保存一些关键数据。 |
onStop() |
这个方法是在Activity完全不可见的时候调用的。 |
onDestroy() |
这个方法在Activity销毁之前调用,之后Activity的状态为销毁状态。 |
onRestart() |
当Activity从停止stop状态恢进入start状态时调用状态。 |
实际操作过程中,当打开启动Android应用时,MainActivity会经过onCreate -> onStart -> onResume三个阶段,此时若按home键或则back按键,MainActivity会经过onPause -> onStop这两个阶段,再进入此MainActivity时,会再调用onRestart -> onStart -> onResume三个阶段。
3、Activity 启动模式
启动模式一共有 4 中:standard、singleTop、singTask 和 singleInstance,可以在 AndroidManifest.xml 中通过 标签指定 android:launchMode 属性来选择启动模式。
(1)standard模式
standard 模式是 Activity 的默认启动模式,在不进行显示指定的情况下,所有 Activity 都会自动使用这种启动模式。对于使用 standard 模式启动的 Activity,系统不会在乎这个 Activity 是否已经存在在栈中了。每次启动的时候都会创建一个新的实例。一般用于打开邮件之类的。
(2)singleTop模式 (栈顶复用模式)
如果 Activity 指定为 singleTop,在启动 Activity 的时候发现返回栈的栈顶已经是该 Activity 了。则认为可以直接使用它,就不会再创建新的 Activity 实例了。因为不会创建新的 Activity 实例,所以 Activity 的生命周期就没有什么变化了。假设你在当前的Activity中又要启动同类型的Activity,此时建议将此类型Activity的启动模式指定为SingleTop,能够降低Activity的创建,节省内存!
一般用于登录页面,新闻详情页等。
(3)singTask 模式(栈内单例模式)
singleTop 很好的解决了重复创建栈顶 Activity 的问题。如果 Activity 没有处于栈顶的位置,还是可能会创建多个 Activity 实例的。那就需要借助 singleTask 了。当 Activity 的启动模式为 singleTask 的时候,每次启动该 Activity 的时候系统会首先在返回栈中检查是否存在该 Activity 的实例,如果发现已经存在则直接使用该实例。并把这个 Activity 之上的所有 Activity 全部出栈,如果没有就会创建一个新的 Activity 实例。
一般主页面使用,购物页面,付款页面,浏览器主页等。
(4) singleInstance模式(堆内单例模式)
singleInstance 模式的 Activity 会启用一个新的返回栈来管理这个 Activity 。在这种模式下会有一个单独的返回栈来管理这个 Activity,不管是哪个应用程序来访问这个 Activity,都共用的同一个返回栈,也就解决了共享 Activity 实例的问题。
4、Activity 之间相互跳转及通信
Intent(意图)是应用程序种各个组件联系的桥梁,通信的载体,负责应用程序中数据的传递。
(1)不带数据跳转
Intent intent = new Intent(this,MainActivity.class);
startActivity(intent);
此段代码实现从当前Activity,跳转到MainActivity,不带任何参数。
(2)带数据或多个数据跳转
//带数据跳转
String data = "是我主活动调用了你";
Intent intent = new Intent(this, SecondActivity.class);
intent.putExtra("ext", data);
startActivity(intent);
(3)带数据跳转,带数据返回
Intent intent = new Intent(LoginActivity.this,RegisterActivity.class);
Bundle bundle = new Bundle() ;
bundle.putString("register","请开始注册!");
intent.putExtras(bundle) ;
startActivityForResult(intent,1);
在跳转时使用startActivityForResult方法,此方法传入两个参数一个是Intent ,另外一个是给当前请求设置的一个请求ID,此ID在结束数据时辨识是否使当前请求的返回结果。
同时要在跳转源Activity中实现 onActivityResult 方法来接收处理目的Activity返回的数据。
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case 1:
if (resultCode == RESULT_OK) {
final EditText loginUsername = findViewById(R.id.username);
String returnUsername = data.getStringExtra("userName");
//序列化方式取出实体
User user = (User)data.getSerializableExtra("user");
loginUsername.setText(returnUsername);
loginUsername.setSelection(returnUsername.length());
}
break;
default:
}
}
(4)Intent隐式实现启动其他程序Activity
在当前Activity中调用百度请求界面。
Uri uri = Uri.parse("https://www.baidu.com");
Intent it = new Intent(Intent.ACTION_VIEW, uri);
startActivity(it);
直接调用打电话功能
Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse("tel:13207690000"));
startActivity(i);
在使用系统电话功能时需要给当前应用程序授权,在AndroidManifest.xml文件中增加
<uses-permission android:name="android.permission.CALL_PHONE" />
打开地图界面
Uri uri = Uri.parse("geo:38.899533,-77.036476");
Intent it = new Intent(Intent.ACTION_VIEW, uri);
startActivity(it);
二、Service组件
1、Service简单介绍
Service它可以在后台执行长时间运行操作而没有用户界面的应用组件,不依赖任何用户界面,例如后台播放音乐,后台下载文件等。
虽然服务是在后台运行的,但是Service和Activity都是运行在当前APP所在的main thread(UI主线程)中的,而耗时操作(如网络请求、拷贝数据、大文件)会阻塞主线程,给用户带来不好的体验。如果需要在服务中进行耗时操作,可以选择 IntentService,IntentService是Service的子类,用来处理异步请求。
2、Service启动的两种方式
(1)startService()
在Acitivity界面通过显式意图(或隐式意图)的方式来启动服务和关闭服务。
Intent intentService = new Intent(MainActivity.this, AudioServiceOnBind.class);
startService(intentService);
生命周期顺序:onCreate->onStartCommand->onDestroy
onCreate() 当Service第一次被创建时调用。
onStartCommand() 当startService方法启动Service时,该方法被调用。
onDestroy() 当Service不再使用时调用。
(2)bindService()绑定服务
当应用组件通过调用 bindService() 绑定到服务时,服务即处于“绑定”状态。绑定服务提供了一个客户端-服务器接口,允许组件与服务进行交互、发送请求、获取结果。 仅当与另一个应用组件绑定时,绑定服务才会运行。 多个组件可以同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。
首先在我们的服务中创建一个内部类AudioBinder继承Binder来获取当服务实例,然后在onBind方法中返回当前服务的实例。
public class AudioServiceOnBind extends Service implements MediaPlayer.OnCompletionListener{
private final IBinder binder = new AudioBinder();
//用于播放音乐等媒体资源
private MediaPlayer mediaPlayer;
public AudioServiceOnBind() {
super();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(AudioServiceOnBind.this.getClass().getName(),"执行onStartCommand()");
return 0;
}
@Override
public IBinder onBind(Intent intent) {
return binder;
}
@Override
public void onCreate(){
super.onCreate();
Log.d(AudioServiceOnBind.this.getClass().getName(),"执行onCreate()");
if (mediaPlayer==null){
mediaPlayer=MediaPlayer.create(this,R.raw.gumeng);
mediaPlayer.setOnCompletionListener(this);
}
mediaPlayer.start();
}
@Override
public void onCompletion(MediaPlayer mp) {
stopSelf();
}
@Override
public void onDestroy(){
if(mediaPlayer.isPlaying()){
mediaPlayer.stop();
}
mediaPlayer.release();
stopForeground(true);
Log.d(AudioServiceOnBind.this.getClass().getName(),"执行onDestroy()");
}
//为了和Activity交互,我们需要定义一个Binder对象
class AudioBinder extends Binder {
//返回Service对象
AudioServiceOnBind getService(){
return AudioServiceOnBind.this;
}
}
}
然后在调用的Activity中创建一个ServiceConnection对象重写其中的onServiceConnected方法获取所要绑定的服务。在触发事件的方法中调用bindService实现服务的最终绑定。
public class MainActivity extends AppCompatActivity {
private AudioServiceOnBind audioServiceOnBind;
//使用ServiceConnection来监听Service状态的变化
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
audioServiceOnBind = null;
}
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
//这里我们实例化audioService,通过binder来实现
audioServiceOnBind = ((AudioServiceOnBind.AudioBinder) binder).getService();
}
};
public void click_music_open(View view) {
Intent intentService = new Intent(MainActivity.this, AudioServiceOnBind.class);
bindService(intentService, conn, Context.BIND_AUTO_CREATE);
}
}
生命周期:onCreate->onBind->onUnBind->onDestroy
onCreate() 当Service被创建时,由系统调用。
onBind() 当bindService方法启动Service时,该方法被调用。
onUnbind() 当unbindService方法解除绑定时,该方法被调用。
onDestroy() 当Service不再使用时,由系统调用。
(3)实现前台Service
Android 8.0 后系统不允许后台应用创建后台服务。 因此,Android 8.0 引入了一种全新的方法,即 Context.startForegroundService(),以在前台启动新服务。
在service onStartCommand方法中增加以下代码
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(AudioServiceOnBind.this.getClass().getName(),"执行onStartCommand()");
Intent nfIntent = new Intent(this, MainActivity.class);
Notification.Builder builder = new Notification.Builder(this.getApplicationContext())
.setContentIntent(PendingIntent.getActivity(this, 0, nfIntent, 0))
.setSmallIcon(R.mipmap.touxiang)
.setContentTitle("wu")
.setContentText("Android测试")
.setWhen(System.currentTimeMillis());
String CHANNEL_ONE_ID = "com.wu";
String CHANNEL_ONE_NAME = "Channel One";
//安卓8.1以上系统
NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ONE_ID, CHANNEL_ONE_NAME, NotificationManager.IMPORTANCE_MIN);
notificationChannel.enableLights(false);
notificationChannel.setShowBadge(true);
notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.createNotificationChannel(notificationChannel);
builder.setChannelId(CHANNEL_ONE_ID);
Notification notification = builder.build();
startForeground(1, notification);
return super.onStartCommand(intent,flags,startId);
}
在调用Activity中调用startForegroundService方法。
public void click_music_open(View view) {
Intent intentService = new Intent(MainActivity.this, AudioServiceOnBind.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intentService);
} else {
startService(intentService);
}
}
(4)使用IntentService
由于Service中的逻辑都是执行在主线程中的,此时如果在Service中处理一下耗时操作可能就会给用户带来不好的体验。所以此时就可以使用Android多线程Service.
本质上IntentService是在Service里开启线程去做任务处理。IntentService会在任务执行完成后自行结束自己,而不需要外部去调用stopService了。
使用IntentService定义的服务,要开启线程,只要重写一个onHandleIntent()方法就可以了,而且在运行完之后会自动停止。
例如数据下载 数据上传等。
public class MyIntentService extends IntentService {
private static final String TAG = "MyIntentService";
public MyIntentService() {
super("MyIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
Log.d(TAG, "当前是子线程: " + Thread.currentThread().getId());
//在这里实现异步多线程处理逻辑
//例如数据下载 数据上传等。
Log.d(TAG, "我在后台默默下载数据中。。。。");
}
@Override
public void onCreate() {
Log.d(TAG, "onCreate: ");
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand: ");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy: ");
super.onDestroy();
}
}
三、Broadcast Receiver组件
1、简单介绍
广播接收器是一个用于接收广播信息,并做出对应处理的组件。比如我们常见的系统广播:通知时区改变、电量低、用户改变了语言选项等。
(1)有序广播
只发送广播到优先级较高的接收者那里,然后由优先级高的接收者决定是继续传播到优先级低的接收者那里还是终止这个广播。
(2)无序广播
对于多个接收者来说是完全异步的,通常每个接收者都无需等待即可收到广播,接收者相互之间不会有影响。对于这种广播,接收者无法终止广播,即无法阻止其他接收者的接收动作。是我们使用较多的一种普通广播类型。
(3)生命周期
BroadcastReceiver的生命周期从对象调用它开始,到onReceiver方法执行完成之后结束。每次广播被接收后会重新创建BroadcastReceiver对象,并在onReceiver方法中执行完就销毁,如果BroadcastReceiver的onReceiver方法中不能在10秒内执行完成,Android会出现ANR异常。所以不要在BroadcastReceiver的onReceiver方法中执行耗时的操作。
2、使用介绍
(1)无序广播的使用
1、创建BreadcastReceiver的子类。
2、注册BroadcastReceiver。
静态注册
<!-- 在配置文件中注册BroadcastReceiver能够匹配的Intent -->
<receiver
android:name=".MyReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MyReceiver"></action>
<category android:name="android.intent.category.DEFAULT"></category>
</intent-filter>
</receiver>
动态注册
MyReceiver receiver = new MyReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction("android.intent.action.MyReceiver");
registerReceiver(receiver, filter);
(2)有序广播的使用
接收者的创建和普通广播一样。有序和无序广播区别在于可以实现广播的拦截,高优先级的接收者可以通过abortBroadcast()终止广播。比如系统的接收短信广播就是一个有序广播,可以通过自定义的广播进行拦截,只要设置的android:priority属性,越大优先级越高。这个值介于[-1000,1000]。
(2)有序广播的使用
<receiver
android:name="com.example.myapplication.MySecondReceiver"
android:exported="true">
<intent-filter android:priority="100">
<action android:name="MY_BROADCAST"/>
</intent-filter>
</receiver>
注册完广播接收器后简单实现一个广播的发送器。 调用sendOrderedBroadcast方法进行有序广播的发送。
Intent intent = new Intent();
intent.setAction("MY_BROADCAST");
intent.putExtra("msg", "你好广播接收器,我是有序广播");
intent.setPackage("com.example.myapplication");
sendOrderedBroadcast(intent, null);
发送完有序广播后我们在自己定义的MySecondReceiver中实现接收到广播的处理。
public class MySecondReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.i(MySecondReceiver.class.getName(),"我收到了广播:" + intent.getStringExtra("msg"));
Toast t = Toast.makeText(context,intent.getStringExtra("msg"),Toast.LENGTH_LONG);
t.setGravity(Gravity.TOP,0,0);
t.show();
//可以选择是否继续广播
abortBroadcast();
}
}
(3)常用的系统广播的action 和permission
开机启动
//开机启动对应的Action
<action android:name="android.intent.action.BOOT_COMPLETED"/>
//开机启动需要的授权
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
网络状态变化广播
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
电量变化广播
<action android:name="android.intent.action.BATTERY_CHANGED"/>
四、内容提供者(Content Provider)组件
1、简单介绍
Content Provider属于Android应用程序的组件之一,作为应用程序之间唯一的共享数据的途径,Content Provider主要的功能就是存储并检索数据以及向其他应用程序提供访问数据的接口。
Android内置的许多数据都是使用Content Provider形式,供开发者调用的(如视频,音频,图片,通讯录等)。
2、使用
(1)使用系统提供的内容提供器
Android系统中自带的电话簿、短信、媒体库等程序都提供了这样的内容提供器供外界访问。
每个应用程序要想访问内容提供器中的数据,就一定要借助ContentResolver类,可以通过Context中的getContentResolver方法来获取。ContentResolver类提供了一系列方法用于对数据进行CRUD操作,insert方法用于添加数据,update方法用于更新数据,delete方法用于删除数据,query方法用于查询数据。它和SQLiteDatabase很像,只不过它不接收表名,而是换成了Uri参数。
Uri包括两部分:authority和path。authority命名为 包名.provider,path命名为 表名 ,所以一个标准的Uri可以为content://com.example.testspecial.provider/table1/1
获取系统的手机联系人
public class SecondActivity extends AppCompatActivity {
private static final String TAG = "SecondActivity";
private ContentResolver contentResolver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Objects.requireNonNull(getSupportActionBar()).hide();
setContentView(R.layout.activity_second);
Button bt_getPhone = (Button) findViewById(R.id.bt_getPhone);
bt_getPhone.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
contentResolver = getContentResolver();
if(ContextCompat.checkSelfPermission(SecondActivity.this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED){
requestPermissions(new String[]{Manifest.permission.READ_CONTACTS},1);
}
Cursor cursor=contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null,null,null,null);
while (cursor.moveToNext()){
//获得联系人的id
int _id=cursor.getInt(cursor.getColumnIndex("_id"));
//获得联系人姓名
String display_name=cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
Log.i(TAG,"当前手机里的联系人:" + _id+" 姓名:"+display_name + "电话号码 "+number );
}
}
});
}
}