Android四大组件 --- Activity
Activity生命周期
生命周期:
onCreate()
->onStart()
- >onResume()
->onPause()
->onStop()
->onDestroy()
启动
activity
:系统先调用onCreate()
,然后调用onStart()
,最后调用onResume()
方法,activity
进入运行状态。activity
被其他activity覆盖其上(DialogActivity)或者锁屏:系统会调用onPause()
方法,暂停当前activity
的执行。当前
activity
由被覆盖
状态回到前台或者解锁屏
:系统会调用onResume()
方法,再次进入运行状态。当前
Activity
转到新的Activity
界面或按Home
键回到主屏,自身退居后台:系统会先调用onPause
方法,然后调用onStop
方法,进入停滞状态。用户后退回到此
Activity
:系统会先调用onRestart
方法,然后调用onStart
方法,最后调用onResume
方法,再次进入运行状态。当前
Activity
处于被覆盖状态或者后台不可见状态,即第2步和第4步,系统内存不足,杀死当前Activity
,而后用户退回当前Activity
:再次调用onCreate
方法、onStart
方法、onResume
方法,进入运行状态。用户退出当前
Activity
:系统先调用onPause
方法,然后调用onStop
方法,最后调用onDestory
方法,结束当前Activity
。onRestart()
:表示activity
正在重新启动 ,一般情况下,当前activity
从不可见
重新变成可见
状态时,onRestart()
就会被调用,这种情形一般是用户行为所导致的,比如用户按HOME
键切换到桌面然后重新打开APP
或者按back
键。onStart()
:activity
可见了,但是还没有出现在前台,还无法和用户交互。onPause()
:表示activity
正在停止,此时可以做一些存储数据,停止动画等工作,注意不能太耗时,因为这会影响到新activity
的显示,onPause
必须先执行完,新的activity
的onResume
才会执行。从
activity
是否可见来说,onstart()
和onStop()
是配对的,从activity
是否在前台来说,onResume()
和onPause()
是配对的。旧
activity
先onPause
,然后新activity
在启动
注意:当activity
中弹出dialog
对话框的时候,activity不会回调onPause
。
然而当activity
启动dialog风格的activity
的时候,此activity会回调onPause函数
。
异常情况下的生命周期:比如当系统资源配置发生改变以及系统内存不足时,activity
就可能被杀死。
-
情况1:资源相关的系统配置发生改变导致
activity
被杀死并重新创建
比如说当前activity
处于竖屏状态,如果突然旋转屏幕,由于系统配置发生了改变,在默认情况下,activity
就会被销毁并且重新创建,当然我们也可以组织系统重新创建我们的activity
。
activity
会销毁,其onPause
,onStop
,onDestory
均会被调用,由于activity
是在异常情况下终止的,系统会调用onSaveInstance来保存当前activity
状态,这个方法的调用时机是在onStop之前。与onPause
没有既定的时序关系,当activity
重新创建后,系统会调用onRestoreInstanceState
,并且把activity
销毁时onSaveInstanceState
方法保存的Bundle
对象作为参数同时传递给onRestoreInstanceState和onCreate方法。onRestoreInstanceState()onStart()方法后回调。
同时,在onSaveInstanceState
和onRestoreInstanceState
方法中,系统自动为我们做了一些恢复工作,如:文本框(EditeText
)中用户输入的数据,ListView
滚动的位置等,这些view相关的状态系统都能够默认为我们恢复。可以查看view
源码,和activity
一样,每个view都有onSaveInstanceState方法和onRestoreInstanceState方法
。
生命周期日志打印:
04-11 09:44:57.350 11757-11757/cn.hotwoo.play:remote I/MainActivity: onCreate
04-11 09:44:57.354 11757-11757/cn.hotwoo.play:remote I/MainActivity: onStart
04-11 09:44:57.356 11757-11757/cn.hotwoo.play:remote I/MainActivity: onResume
04-11 09:44:57.425 11757-11757/cn.hotwoo.play:remote I/MainActivity: onCreateOptionsMenu
04-11 09:44:59.149 11757-11757/cn.hotwoo.play:remote I/MainActivity: onPause
04-11 09:44:59.151 11757-11757/cn.hotwoo.play:remote I/MainActivity: onSaveInstanceState
04-11 09:44:59.151 11757-11757/cn.hotwoo.play:remote I/MainActivity: onStop
04-11 09:44:59.151 11757-11757/cn.hotwoo.play:remote I/MainActivity: onDestroy
04-11 09:44:59.234 11757-11757/cn.hotwoo.play:remote I/MainActivity: onCreate
04-11 09:44:59.235 11757-11757/cn.hotwoo.play:remote I/MainActivity: onStart
04-11 09:44:59.236 11757-11757/cn.hotwoo.play:remote I/MainActivity: onRestoreInstanceState
04-11 09:44:59.237 11757-11757/cn.hotwoo.play:remote I/MainActivity: onResume
04-11 09:44:59.270 11757-11757/cn.hotwoo.play:remote I/MainActivity: onCreateOptionsMenu
04-11 10:02:32.320 11757-11757/cn.hotwoo.play:remote I/MainActivity: onPause
04-11 10:02:32.516 11757-11757/cn.hotwoo.play:remote I/MainActivity: onStop
04-11 10:02:32.516 11757-11757/cn.hotwoo.play:remote I/MainActivity: onDestroy
-
情况2:资源内存不足导致低优先级的
activity
被杀死
这里的情况和前面的情况1数据存储和恢复是完全一致的,activity
按照优先级从高到低可以分为如下三种:
(1)前台activity
---正在和用户交互的activity
,优先级最高
(2)可见但非前台activity
---比如activity
中弹出了一个对话框,导致activity
可见但是位于后台无法和用户直接交互。
(3)后台activity
---已经被暂停的activity
,比如执行了onStop
,优先级最低。
防止重新创建activity:activity
指定configChange
属性来不让系统重新创建activity
。
android : configChanges = "orientation"
Activity与Fragment生命周期关系
创建过程:
销毁过程:
Activity与menu创建先后顺序
在activity
创建完回调onResume
后创建menu
,回调onCreateOptionsMenu
04-05 00:35:03.452 2292-2292/cn.hotwoo.play:remote I/MainActivity: onCreate
04-05 00:35:03.453 2292-2292/cn.hotwoo.play:remote I/MainActivity: onStart
04-05 00:35:03.454 2292-2292/cn.hotwoo.play:remote I/MainActivity: onResume
04-05 00:35:03.482 2292-2292/cn.hotwoo.play:remote I/MainActivity: onCreateOptionsMenu
Activity的启动模式
有四种启动模式:
standard
,singleTop
,singleTask
,singleInstance
- standard模式:在这种模式下,activity默认会进入启动它的activity所属的任务栈中。 注意:在非activity类型的context(如ApplicationContext)并没有所谓的任务栈,所以不能通过ApplicationContext去启动standard模式的activity。
- singleTop模式:栈顶复用模式。如果新activity位于任务栈的栈顶的时候,activity不会被重新创建,同时它的onNewIntent方法会被回调。 注意:这个activity的onCreate,onStart,onResume不会被回调,因为他们并没有发生改变。
- singleTask模式:栈内复用模式。只要activity在一个栈中存在,那么多次启动此activity不会被重新创建单例,系统会回调onNewIntent。比如activityA,系统首先会寻找是否存在A想要的任务栈,如果没有则创建一个新的任务栈,然后把activityA压入栈,如果存在任务栈,然后再看看有没有activityA的实例,如果实例存在,那么就会把A调到栈顶并调用它的onNewIntent方法,如果不存在则把它压入栈。
- singleInstance模式:单实例模式。这种模式的activity只能单独地位于一个任务栈中。由于站内复用特性,后续的请求均不会创建新的activity实例。
注意:默认情况下,所有activity所需的任务栈的名字为应用的包名,可以通过给activity指定TaskAffinity属性来指定任务栈,**这个属性值不能和包名相同,否则就没有意义 ** 。
Android四大组件 --- Service
本地服务(LocalService)
调用者和service在同一个进程里,所以运行在主进程的main线程中。所以不能进行耗时操作,可以采用在service里面创建一个Thread来执行任务。service影响的是进程的生命周期,讨论与Thread的区别没有意义。
任何 Activity 都可以控制同一 Service,而系统也只会创建一个对应 Service 的实例。
两种启动方式
第一种启动方式:
通过start方式开启服务.
使用service的步骤:
1,定义一个类继承service
2,manifest.xml文件中配置service
3,使用context的startService(Intent)方法启动service
4,不在使用时,调用stopService(Intent)方法停止服务
使用start方式启动的生命周期:
onCreate() -- > onStartCommand() -- > onDestory()
注意:如果服务已经开启,不会重复回调onCreate()方法,如果再次调用context.startService()方法,service而是会调用onStart()或者onStartCommand()方法。停止服务需要调用context.stopService()方法,服务停止的时候回调onDestory被销毁。
特点:
一旦服务开启就跟调用者(开启者)没有任何关系了。开启者退出了,开启者挂了,服务还在后台长期的运行,开启者不能调用服务里面的方法。
第二种启动方式
采用bind的方式开启服务
使用service的步骤:
1,定义一个类继承Service
2,在manifest.xml文件中注册service
3,使用context的bindService(Intent,ServiceConnection,int)方法启动service
4,不再使用时,调用unbindService(ServiceConnection)方法停止该服务
使用这种bind方式启动的service的生命周期如下:
onCreate() -- > onBind() --> onUnbind() -- > onDestory()
注意:绑定服务不会调用onStart()或者onStartCommand()方法
特点:bind的方式开启服务,绑定服务,调用者挂了,服务也会跟着挂掉。绑定者可以调用服务里面的方法。
示例:
定义一个类继承service
//本地service不涉及进程间通信
public class MyService extends Service {
private String TAG = "MyService";
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG,"onCreate");
}
@Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
Log.i(TAG,"onStart");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG,"onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
//绑定服务时调用这个方法,返回一个IBinder对象
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG,"onBind");
return new MyBinder();
}
@Override
public boolean onUnbind(Intent intent) {
Log.i(TAG,"onUnbind");
return super.onUnbind(intent);
}
// 停止服务,通过调用Context.unbindService(),别忘了service也继承了Context类
// @Override
// public void unbindService(ServiceConnection conn) {
// super.unbindService(conn);
// Log.i(TAG,"unbindService");
// }
//服务挂了
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG,"onDestroy");
}
public interface MyIBinder{
void invokeMethodInMyService();
}
public class MyBinder extends Binder implements MyIBinder{
public void stopService(ServiceConnection serviceConnection){
unbindService(serviceConnection);
}
@Override
public void invokeMethodInMyService() {
for(int i =0; i < 20; i ++){
System.out.println("service is opening");
}
}
}
在manifest.xml文件中注册service
//Service 必须要注册
<service android:name=".server.MyService"
android:exported="true">
<intent-filter>
<action android:name="cn.hotwoo.play.server.MyService"/>
<category android:name="android.intent.category.default" />
</intent-filter>
</service>
绑定自定义的service
public class CustomActivity extends AppCompatActivity {
private Button startService, unbindService;
private MyService.MyBinder myBinder;
private ServiceConnection serviceConnection;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_custom);
startService = (Button) findViewById(R.id.service_start);
unbindService = (Button) findViewById(R.id.unbind_service);
startService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// startService(new Intent(CustomActivity.this, MyService.class));
serviceConnection = new MyServiceConnection();
bindService(new Intent(CustomActivity.this, MyService.class), serviceConnection, Context.BIND_AUTO_CREATE);
}
});
unbindService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
unbindService(serviceConnection);
}
});
}
class MyServiceConnection implements ServiceConnection {
//这里的第二个参数IBinder就是Service中的onBind方法返回的
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i("MyService", "onServiceConnected");
myBinder = (MyService.MyBinder) service;
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i("MyService", "onServiceDisconnected");
}
}
}
startService输出日志:
04-01 19:56:09.846 22845-22845/cn.hotwoo.play I/MyService: onCreate
04-01 19:56:09.854 22845-22845/cn.hotwoo.play I/MyService: onStartCommand
04-01 19:56:09.854 22845-22845/cn.hotwoo.play I/MyService: onStart
bindService 输出日志:
04-01 19:53:21.459 14704-14704/cn.hotwoo.play I/MyService: onCreate
04-01 19:53:21.460 14704-14704/cn.hotwoo.play I/MyService: onBind
04-01 19:53:21.461 14704-14704/cn.hotwoo.play I/MyService: onServiceConnected
点击back键关闭activity或者调用Context.unbindService()方法后:
04-05 01:16:27.508 11427-11427/cn.hotwoo.play I/MyService: onUnbind
04-05 01:16:27.508 11427-11427/cn.hotwoo.play I/MyService: onDestroy
远程服务
调用者和service不在同一个进程中,service在单独的进程中的main线程,是一种垮进程通信方式。学习地址
绑定远程服务的步骤:
- 在服务的内部创建一个内部类,提供一个方法,可以间接调用服务的方法
- 把暴露的接口文件的扩展名改为.aidl文件 去掉访问修饰符
- 实现服务的onbind方法,继承Bander和实现aidl定义的接口,提供给外界可调用的方法
- 在activity 中绑定服务。bindService()
- 在服务成功绑定的时候会回调 onServiceConnected方法 传递一个 IBinder对象
- aidl定义的接口.Stub.asInterface(binder) 调用接口里面的方法
IntentService
IntentService是Service的子类,比普通的Service增加了额外的功能。先看Service本身存在两个问题:
- Service不会专门启动一条单独的进程,Service与它所在应用位于同一个进程中;
- Service也不是专门一条新线程,因此不应该在Service中直接处理耗时的任务;
IntentService特征:
- 会创建独立的worker线程来处理所有的Intent请求;
- 会创建独立的worker线程来处理onHandleIntent()方法实现的代码,无需处理多线程问题;
- 所有请求处理完成后,IntentService会自动停止,无需调用stopSelf()方法停止Service;
- 为Service的onBind()提供默认实现,返回null;
- 为Service的onStartCommand提供默认实现,将请求Intent添加到队列中;
Android四大组件 --- BroadcastReceiver
广播被分为两种不同的类型:“普通广播(Normal broadcasts)”和“有序广播(Ordered broadcasts)”。普通广播是完全异步的,可以在同一时刻(逻辑上)被所有接收者接收到,消息传递的效率比较高,但缺点是:接收者不能将处理结果传递给下一个接收者,并且无法终止广播Intent的传播;然而有序广播是按照接收者声明的优先级别(声明在intent-filter元素的android:priority属性中,数越大优先级别越高,取值范围:-1000到1000。也可以调用IntentFilter对象的setPriority()进行设置),被接收者依次接收广播。如:A的级别高于B,B的级别高于C,那么,广播先传给A,再传给B,最后传给C。A得到广播后,可以往广播里存入数据,当广播传给B时,B可以从广播中得到A存入的数据。
发送广播
Context.sendBroadcast()
发送的是普通广播,所有订阅者都有机会获得并进行处理。
Context.sendOrderedBroadcast()
发送的是有序广播,系统会根据接收者声明的优先级别按顺序逐个执行接收者,前面的接收者有权终止广播(BroadcastReceiver.abortBroadcast()),如果广播被前面的接收者终止,后面的接收者就再也无法获取到广播。对于有序广播,前面的接收者可以将处理结果通过setResultExtras(Bundle)方法存放进结果对象,然后传给下一个接收者,通过代码:Bundle bundle =getResultExtras(true))可以获取上一个接收者存入在结果对象中的数据。
系统收到短信,发出的广播属于有序广播。如果想阻止用户收到短信,可以通过设置优先级,让你们自定义的接收者先获取到广播,然后终止广播,这样用户就接收不到短信了。
生命周期:如果一个广播处理完onReceive 那么系统将认定此对象将不再是一个活动的对象,也就会finished掉它。
至此,大家应该能明白 Android 的广播生命周期的原理。
步骤:
1,自定义一个类继承BroadcastReceiver
2,重写onReceive方法
3,在manifest.xml中注册
注意 :BroadcastReceiver生命周期很短
如果需要在onReceiver完成一些耗时操作,应该考虑在Service中开启一个新线程处理耗时操作,不应该在BroadcastReceiver中开启一个新的线程,因为BroadcastReceiver生命周期很短,在执行完onReceiver以后就结束,如果开启一个新的线程,可能出现BroadcastRecevier退出以后线程还在,而如果BroadcastReceiver所在的进程结束了,该线程就会被标记为一个空线程,根据Android的内存管理策略,在系统内存紧张的时候,会按照优先级,结束优先级低的线程,而空线程无异是优先级最低的,这样就可能导致BroadcastReceiver启动的子线程不能执行完成。
示例
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.i("fuck","intent-action : " + intent.getAction());
if(intent.getAction().equals("test")){
Toast.makeText(context,"fuck",Toast.LENGTH_LONG).show();
}
}
}
注册
//广播接收器
<receiver android:name=".broadcast.MyBroadcastReceiver">
<intent-filter>
<action android:name="android.intent.action.ACTION_POWER_CONNECTED" />
<action android:name="test"/>//这里自定义一个广播动作
</intent-filter>
</receiver>
广播还可以通过动态注册:
registerReceiver(new MyBroadcastReceiver(),new IntentFilter("test"));
一定要加上这个权限(坑)
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
注意:xml中注册的优先级高于动态注册广播。
发送广播
Intent intent = new Intent("test");
sendBroadcast(intent);
静态注册和动态注册区别
- 动态注册广播不是常驻型广播,也就是说广播跟随activity的生命周期。注意: 在activity结束前,移除广播接收器。
静态注册是常驻型,也就是说当应用程序关闭后,如果有信息广播来,程序也会被系统调用自动运行。 - 当广播为有序广播时:
1 优先级高的先接收
2 同优先级的广播接收器,动态优先于静态
3 同优先级的同类广播接收器,静态:先扫描的优先于后扫描的,动态:先注册的优先于后注册的。 - 当广播为普通广播时:
1 无视优先级,动态广播接收器优先于静态广播接收器
2 同优先级的同类广播接收器,静态:先扫描的优先于后扫描的,动态:先注册的优先于后注册的。
小结:
- 在Android 中如果要发送一个广播必须使用sendBroadCast 向系统发送对其感兴趣的广播接收器中。
- 使用广播必须要有一个intent 对象必设置其action动作对象
- 使用广播必须在配置文件中显式的指明该广播对象
- 每次接收广播都会重新生成一个接收广播的对象
- 在BroadCastReceiver中尽量不要处理太多逻辑问题,建议复杂的逻辑交给Activity 或者 Service 去处理
- 如果在AndroidManifest.xml中注册,当应用程序关闭的时候,也会接收到广播。在应用程序中注册就不产生这种情况了。
注意
当如果要进行的操作需要花费比较长的时间,则不适合放在BroadcastReceiver中进行处理。
引用网上找到的一段解释:
在 Android 中,程序的响应( Responsive )被活动管理器( Activity Manager )和窗口管理器( Window Manager )这两个系统服务所监视。当 BroadcastReceiver 在 10 秒内没有执行完毕,Android 会认为该程序无响应。所以在 BroadcastReceiver 里不能做一些比较耗时的操作,否侧会弹出ANR ( Application No Response )的对话框。如果需要完成一项比较耗时的工作,应该通过发送Intent 给 Service ,由 Service 来完成。而不是使用子线程的方法来解决,因为 BroadcastReceiver 的生命周期很短(在 onReceive() 执行后 BroadcastReceiver 的实例就会被销毁),子线程可能还没有结束BroadcastReceiver 就先结束了。如果 BroadcastReceiver 结束了,它的宿主进程还在运行,那么子线程还会继续执行。但宿主进程此时很容易在系统需要内存时被优先杀死,因为它属于空进程(没有任何活动组件的进程)。
Android四大组件 --- ContentProvider
contentprovider是android四大组件之一的内容提供器,它主要的作用就是将程序的内部的数据和外部进行共享,为数据提供外部访问接口,被访问的数据主要以数据库的形式存在,而且还可以选择共享哪一部分的数据。这样一来,对于程序当中的隐私数据可以不共享,从而更加安全。contentprovider是android中一种跨程序共享数据的重要组件。
使用系统的ContentProvider
系统的ContentProvider有很多,如通话记录,短信,通讯录等等,都需要和第三方的app进行共享数据。既然是使用系统的,那么contentprovider的具体实现就不需要我们担心了,使用内容提供者的步骤如下
- 获取ContentResolver实例
- 确定Uri的内容,并解析为具体的Uri实例
- 通过ContentResolver实例来调用相应的方法,传递相应的参数,但是第一个参数总是Uri,它制定了我们要操作的数据的具体地址
可以通过读取系统通讯录的联系人信息,显示在Listview中来实践这些知识。不要忘记在读取通讯录的时候,在清单文件中要加入相应的读取权限。
自定义ContentProvider
系统的contentprovider在与我们交互的时候,只接受了一个Uri的参数,然后根据我们的操作返回给我们结果。系统到底是如何根据一个Uri就能够提供给我们准确的结果呢?只有自己亲自实现一个看看了。
和之前提到的一样,想重新自定义自己程序中的四大组件,就必须重新实现一个类,重写这个类中的抽象方法,在清单文件中注册,最后才能够正常使用。
重新实现ContentProvider之后,发现我们重写了6个重要的抽象方法
- oncreate
- query
- update
- insert
- delete
- gettype
大部分的方法在数据库那里已经见过了,他们内部的逻辑可想而知都是对数据的增删改查操作,其中这些方法的第一个参数大多都是Uri实例。其中有两个方法比较特殊:
- oncreate方法应该是内容提供者创建的时候所执行的一个回调方法,负责数据库的创建和更新操作。这个方法只有我们在程序中获取ContentResolver实例之后准备访问共享数据的时候,才会被执行。
- gettype方法是获取我们通过参数传递进去的Uri的MIME类型,这个类型是什么,后面会有实例说明。
内容提供者首先要做的一个事情就是将我们传递过来的Uri解析出来,确定其他程序到底想访问哪些数据。Uri的形式一般有两种:
1,以路径名为结尾,这种Uri请求的是整个表的数据,如: content://com.demo.androiddemo.provider/tabl1 标识我们要访问tabl1表中所有的数据
2,以id列值结尾,这种Uri请求的是该表中和其提供的列值相等的单条数据。 content://com.demo.androiddemo.provider/tabl1/1 标识我们要访问tabl1表中_id列值为1的数据。
如果是内容提供器的设计者,那么我们肯定知道这个程序的数据库是什么样的,每一张表,或者每一张表中的_id都应该有一个唯一的内容Uri。我们可以将传递进来的Uri和我们存好的Uri进行匹配,匹配到了之后,就说明数据源已经找到,便可以进行相应的增删改查操作。
五种布局
RelativeLayout 实现平分父布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<View android:id="@+id/strut"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_centerHorizontal="true"/>
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_alignRight="@id/strut"
android:layout_alignParentLeft="true"
android:text="Left"/>
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/strut"
android:layout_alignParentRight="true"
android:text="Right"/>
</RelativeLayout>
RelativeLayout 的子view的 layout_gravity属性是没有效果的,而是通过
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
这样的一些属性来代替。