Android大致可以分为四层架构:Linux内核层、系统运行库层、应用框架层、应用层。
1. Linux内核层
Android系统是基于Linux内核的,这一层为Android设备的各种硬件提供了底层的驱动,如显示驱动、音频驱动、照相机驱动、蓝牙驱动、Wi-Fi驱动、电源管理。
2. 系统运行库层
这一层通过一些c/c++库来为Android系统提供了主要的特性支持。如SQLite库提供了数据库的支持,OpenGL|ES提供了3D绘图的支持,Webkit提供了浏览器内核的支持。
在这一层还有Android运行时库,他主要提供了一些核心库,能够允许开发这使用Java语言来编写Android应用。另外,Android运行时库中还包含了Dalvik虚拟机(5.0系统之后改为ART运行环境),它使得每一个Android应用都能运行在独立的进程当中,并且拥有一个自己的Dalvik虚拟机实例。相较于Java虚拟机,Dalvik是专门为移动设备定制的。它针对手机内存、CPU性能有限等情况做了优化处理。
3. 应用框架层
这一层主要提供了构建应用程序时可能用到的各种API,Android自带的一些核心应用就是使用这些API完成,开发者也可以通过这些API来构建自己的应用程序
4. 应用层
所有安装在手机上的应用程序都是属于这一层的。
1. 四大组件
Android系统四大组件分别是活动(Activity)、服务(Service)、广播接收器(Broadcast Receiver)和内容提供器(Content Provider)。其中活动是所有Android应用程序的门面,凡是在应用中你看得到的东西,都是放在活动中的。而服务就比较低调了,你无法看到它,但它会一直在后台默默地运行,即使用户退出了应用,服务仍然是可以继续运行的。广播接收器允许你的应用接收来自各处的广播消息,比如电话,短信等。当然你的应用也可以向外发出广播消息。内容提供器则为应用程序之间共享数据了可能,比如你想要读取系统电话本中的联系人,就需要通过内容提供器来实现。
2. 丰富的系统控件
Android系统为开发者提供了丰富的系统控件,使得我们可以很轻松地编写出漂亮的界面。也可以自己定制自己的控件。
3.SQLite数据库
Android系统还自带了这种轻量级、运算速度极快的嵌入式关系型数据库,不仅支持标准的SQL语法,还可以通过Android封装好的API进行操作、让存储和读取数据变得非常方便。
4. 强大的多媒体
Android系统还提供了丰富的多媒体服务,如音乐、视频、录音、拍照等等。这些都可以在程序中通过代码进行控制。
5. 地理位置定位
Android手机都内置GPS。
* name为软件名称,package name是项目的包名,Andriod系统是通过包名来区分不同的应用程序,所以要保证包名的唯一性。Minimum API Level为项目的最低兼容版本*
1.点击菜单栏File下的project structure。配置Artifacts。artifact是一种用于装载项目资产以便于测试,部署,或者分布式软件的解决方案。
2. 选择运行设备
1. 项目结构的分析
4. .gradle和.idea都是自动生成的一些文件,无需关心,也不需要编辑。
5. app :项目中的代码、资源等内容几乎都是放置在这个目录下的。
6. gradle:这个目录下包含了gradle wrapper的配置文件,使用gradle wrapper的方式不需要将gradle下载好,而是会自动根据本地的缓存情况决定是否需要联网下载gradle。
7. .gitgnore:这个文件是用来将指定的目录或文件排除在版本控制之外的。
8. build.gradle:项目全局的gradle构建脚本,通常这个文件的内容是不需要修改的。
9. gradle.properties:全局的gradle配置文件,在这里配置的属性将会影响到项目中所有的gradle编译脚本。
10. gradlew和gradlew.bat:用来是命令行界面执行gradle命令,其中gradlew是在linux或mac系统中使用的,gradlew.bat是在windows系统中使用的。
11. 项目名.iml:自动生成的文件,用于标识这是一个IntelliJ IDEA项目,不需要修改这个文件中的任何内容。
12. local.properties:用于指定本机中的Android SDK路径,通常内容都是自动生成的,不需要修改。
10.settings.gradle:这个文件用于指定项目中所有引入的模块。
2. app目录的分析
1 build:主要包含了一些在编译时自动生成的文件,不需要过多关心。
2 libs:在项目中使用的第三方jar包,放在这个目录下的jar包会自动添加到构建路径里去。
3 androidTest:用来编写Android Test测试用例,可以对项目进行一些自动化测试。
4 main下面的java:放置所有Java代码的地方。
5 res:项目中使用到的所有图片、布局、字符串等资源需要存放在这个目录下。图片放在drawable目录下,布局放在layout目录下,字符串放在values目录下。应用图标放在mipmap目录下。
6 AndroidMainifest.xml:整个Android项目的配置文件,在程序中定义的所有四大组件都需要在这个文件里注册,还可以在这个文件中给应用程序添加权限声明。
7 test:用来编写Unit Test测试用例的,是对项目进行自动化测试的另一种方式。
8 gitinore:这个文件用于将app模块内的指定的目录或文件排除在版本控制之外。
9 app.iml:自动生成的文件,不需要关心。
10 build.gradle:这是app模块的gradle构建脚本,这个文件中会指定很多项目构建相关的配置。
11 proguard-rules.pro:这个文件用于指定项目代码的混淆规则,当代码开发完成后打成安装包文件,如果不希望被别人破解,通常会将代码进行混淆,从而让破解者难以阅读。
3. 分析项目如何运行
1 打开AndroidMainifest.xml
这段代码表示对活动进行注册,没有在AndroidMainifest.xml里注册的活动是不能使用的。
2 分析活动代码
继承AppCompatActivity,这是一种向下兼容的Activity,可以将Activity在各个系统版本中增加的特性和功能最低兼容到Andriod2.1系统。Activity是Andriod系统的一个活动基类,项目中所有活动都必须继承它或者它的子类才能拥有活动的特性。onCreate这个方法是一个活动被创建时必定要执行的方法。
Android程序的设计讲究逻辑和视图分离,因此不推荐在活动中直接编写界面的,更加通用的一种做法是,在布局文件中编写界面,然后在活动中引入进来。
3 如何引用res目录下的内容
例如res/values/strings.xml。可以有两种方式进行引用。
jcenter和google
dependencies闭包中使用了classpath声明了一个Gradle插件。
app目录下的build.gradle
apply plugin应用了一个插件,一般有两种值可选:com.android.application,表示这是一个应用程序模块,com.android.library表示这是一个库文件。应用程序模块和库模块的最大区别在于,一个是可以直接运行的,一个只能作为代码库依附于别的应用程序模块来运行。
compileSdkVersion用于指定项目的编译版本。buildToolsVersion用于指定项目构建工具的版本。
在defaultConfig
闭包中可以对项目的更多细节进行配置。applicationId用于指定项目的包名。minSdkVersion用于指定项目最低兼容的Android系统版本。targetSdkVersion指定的值表示你在该目标版本上已经做过充分的测试。
在buildTypes
闭包中用于指定生成完整文件的相关配置,通常会有两个子闭包,一个是debug,一个是release。debug闭包用于指定生成测试版安装文件的配置,release闭包用于指定生成正式版安装文件的配置。debug闭包是可以忽略不写的release
闭包中minifyEnabled用于指定是否对项目的代码进行混淆,true代表混淆,false代表不混淆。proguardFiles用于指定混淆时使用的规则文件,这里指定了两个文件,第一个是proguard-android.txt是在Android SDK目录下的,里面是所有项目通用的混淆规则。第二个是proguard-rules.pro是在当前项目的根目录下的,里面可以编写当前项目特有的混淆规则。
Android中的日志工具是Log(android.util.Log),这个类提供了如下5个方法来供我们打印日志。
活动(Activity)是最容易吸引用户的地方,它是一种可以包含用户界面的组件,主要用于和用户进行交互。一个应用程序中可以包含零个或多个活动。
右键new -activity- empty activity。
勾选Generate Layout File表示会自动创建一个对应的布局文件。
勾选LauncherActivity表示会自动将新创建的活动设置为当前项目的主活动。
Android程序的设计讲究逻辑和视图分离,最好每一个活动都能对应一个布局,布局就是用来显示界面内容的。
右击app/src/main/res目录->New->Directory,会弹出一个新建目录的窗口,起名为layout。右键->new ->Layout resources file,新建布局资源文件。
Design是当前的可视化布局编辑器 ,在这里不仅可以预览当前的布局,还可以通过托放的方式编辑布局。而Text则是通过XML文件的方式来编辑布局。
<Button
android:text=" Button 1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/button_1"/>
match_parent表示让当前元素和父元素一样宽,wrap_content表示当前元素的高度只要能刚好包含里面的内容就行。
在活动里新增代码setContentView(R.layout.first_layout);
setContentView()方法来给当前的活动加载一个布局。项目中添加的任何资源都会在R文件中生成一个相应的资源id。
所有的活动都要在AndroidManifest.xml中进行注册才能生效。Idea会自动帮忙注册。但是现在仍然不能运行程序,程序当前仍然没有配置主活动。
配置主活动的方法
在标签的内部加入
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
Toast是Android系统提供的一种提醒方式,在程序中可以使用它将一些短小的信息通知给用户,这些信息会在一段时间内后自动消失,并且不会占用任何屏幕空间。
Button button1=(Button) findViewById(R.id.button_1);
button1.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
Toast.makeText(FirstActivity.this,"you clicked",
Toast.LENGTH_SHORT).show();
}
});
findViewById()可以获取到在布局文件中定义的元素,返回的是一个View对象,需要向下转型成Button对象。setOnClickListener()为按钮注册一个监听器,点击按钮时就会执行监听器的onClick()方法。
Toast的用法非常简单,通过静态方法makeText()创建出一个Toast对象,然后调用show()将Toast显示出来就可以。makeText()需要传入3个参数,第一个参数时Context,也就是Toast要求的上下文。第二个参数时Toast显示的文本内容,第三个参数时Toast显示的时长。
在res目录下新建一个menu文件夹,右键文件夹->new ->Menu resource file。
在新创建好的文件添加代码。
<item android:id="@+id/add_item" android:title="Add"/>
<item android:id="@+id/remove_item" android:title="Remove"/>
标签用来创建具体的某一个菜单项。
回到活动重写onCreateOptionsMenu()
方法
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main,menu);
return true;
}
getMenuInflater()方法可以获得MenuInflater对象,再调用它的inflate()方法就可以给当前活动创建菜单。inflate()方法接收两个参数,第一个参数用于指定我们通过哪一个资源文件来创建菜单。第二个参数用于指定菜单项将添加到哪一个Menu对象当中。返回值true,表示允许创建的菜单显示出来,如果返回false,创建的菜单将无法显示。
使用代码的方式:finish(),在活动中调用一下这个方法就可以销毁当前活动了。
前提:
需要创建多个活动,以及布局。
什么是Intent
Intent是Android程序中各组件之间进行交互的一种重要方式,它不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据。Intent一般可用于启动活动、启动服务以及发送广播。
Intent大致可以分为两种:显式Intent和隐式Intent。
显示Intent的用法
Intent有多个构造函数的重载,其中一个是Intent(Context packageContext,Class> cls)。第一个参数Context要求提供一个启动活动的上下文,第二个参数Class则是指定想要启动的目标活动。Activity类中提供了一个startActivity()方法,这个方法是专门用于启动活动的。
Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
startActivity(intent);
隐式Intent的用法
它并不明确的指出要启动哪一个活动,而是指定了一系列更为抽象的action和category等信息,然后交由系统分析Intent,找出合适的活动。
在AndroidManifest.xml
当中为要跳转的页面添加代码
<activity android:name=".SecondActivity">
<intent-filter>
<action android:name="com.example.activityTest.ACTION_START"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
只有和中的内容同时能够匹配上Intent中指定的action和category时,这个活动才能响应该Intent。
此处没有指定category的原因在于android.intent.category.DEFAULT是一种默认的category,在调用startActivity()方法时会自动将这个category添加到Intent中。
每个Intent中只能确定一个action,但却能指定多个category。
指定category的方法
Intent intent=new Intent("com.example.activityTest.ACTION_START");
intent.addCategory("com.example.activitytest.My");
startActivity(intent);
使用隐式Intent,不仅可以启动自己程序内的活动,还可以启动其他程序的活动,这使得Android多个应用程序之间的功能共享成为了可能。
Intent intent=new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("https://www.baidu.com/"));
startActivity(intent);
在标签中可以再配置一个标签,用于更精准的指定当前活动能够响应什么类型的数据。
Intent intent=new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
向下一个活动传递数据
在启动活动时传递的数据暂存在Intent中,启动了另一个活动后,只需要把这些数据再从Intent中取出就可以了。
在跳转前的活动页面当中
String data="Hello !";
Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
intent.putExtra("data",data);
putExtra()方法接收两个参数,第一个参数是键,用于后面从Intent中取值,第二个参数才是真正要传递的数据。
在跳转后的页面当中
Intent intent=getIntent();
String data=intent.getStringExtra("data");
Log.d("SecondActivity",data);
如果传递的是整型数据,则使用getIntExtra()方法,如果传递的是布尔型数据,则使用getBooleanExtra()方法
向上一个活动传递数据
Activity中还有一个startActivityForResult()方法也是用于启动活动的,但是这个方法期望在活动销毁的时候能够返回一个结果给上一个活动。
第一个活动的代码如下
Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
startActivityForResult(intent,1);
startActivityForResult()方法接收两个参数,第一个参数是Intent,第二个参数是请求码,用于在之后的回调中判断数据的来源。
第二个代码如下
@Override
public void onBackPressed() {
Intent intent=new Intent();
intent.putExtra("data_return","he");
setResult(RESULT_OK,intent);
finish();
}
当用户按下Back键,就会去执行onBackPressed()方法中的代码。setResult()是专门用于向上一个活动返回数据的。第一个参数用于向上一个活动返回处理结果,一般只使用RESULT_OK或RESULT_CANCELED这两个值,第二个参数则把带有数据的Intent
在第一个活动当中添加
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case 1:
if (resultCode == RESULT_OK) {
String returnedData = data.getStringExtra("data_return");
Log.d("FirstActivity", returnedData);
}
break;
default:
}
}
由于是使用startActivityForResult()方法来启动SecondActivity的,在SecondActivity被销毁之后会回调上一个活动的onActivityResult()方法。
返回栈
Android是使用任务(Task)来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈也被称为返回栈。在默认的情况下,每当新启动了一个活动,它就会在返回栈中入栈,并处于栈顶的位置。而每当按下Back键或调用finish()方法来销毁一个活动时,处于栈顶的活动会出栈。系统总是会显示处于栈顶的活动给用户。
活动状态
每个活动在其生命周期中最多会有4种状态。
onCreate()
。它会在活动第一次被创建的时候调用,在这个方法中完成活动的初始化操作,比如说加载布局,绑定事件等。onStart()
。这个方法在活动由不可见变为可见的时候调用。onResume()
。这个方法在活动准备好和用户进行交互时使用。此时的活动一定位于返回栈的栈顶。并且处于运行状态。onPause()
。这个方法在系统准备去启动或者恢复另一个活动的时候调用。通常会在这个方法中将一些消耗CPU的资源释放掉,以及保存一些关键数据。但这个方法所谓执行速度一定要块,不然会影响到新的栈顶活动的使用。onStop()
。这个方法会在活动完全不可见的时候调用。它和onPause()方法的主要区别在于,如果启动的新活动是一个对话框的式的活动,那么onPause()方法会得到执行,而onStop()方法并不会执行。onDestory()
。这个方法在活动被销毁之前调用,之后活动的状态将变为销毁状态。onRestart()
。这个方法在活动由停止状态变为运行状态之前调用,也就是活动被重新启动了。onCreate()
方法和onDestory()
方法之间所经历的,就是完整生存期。一般情况下一个活动会在onCreate()方法中完成各种初始化操作,而在onDestory方法中完成释放内存的操作。onStart()
方法和onStop()
方法之间所经历的,就是可见生存期。在可见生存期内,活动对于用户总是可见的,即便有可能无法和用户进行交互,仍然可以通过这两个方法,合理地管理对用户可见的资源。比如在onStart()方法中对资源进行加载,而在onStop()方法中对资源进行释放,从而保证处于停止状态的活动不会占用过多内存。onResume()
方法和onPause()
方法之间所经历的就是前台生存期。在前台生存期内,活动总是处于运行状态的,此时的活动是可以和用户进行交互的。Activity中还提供了一个onSaveInstanceState()
回调方法,这个方法可以保证在活动被回收之前一定会被调用。可以通过这个方法来解决活动被回收时临时数据得不到保存的问题。
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
String tempData="Something you just typed";
outState.putString("data_key",tempData);
}
onSaveInstanceState()方法会携带一个Bundle类型的参数,Bundle提供了一系列的方法用于保存数据,比如可以使用putString()方法保存字符串。
如何恢复保存下来的数据
onCreate()方法其实也有一个Bundle类型的参数,这个参数一般情况下都是null,但是如果在活动被系统回收之前有通过onSaveInstanceState()方法来保存数据的话,这个参数就会带有之前所保存的全部数据。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(savedInstanceState!=null)
{
String tempData=savedInstanceState.getString("data_key");
Log.d("tag",tempData);
}
}
Intent可以结合Bundle一起用于传递数据,可以把需要传递的数据都保存在Bundle对象中,然后再将Bundle对象存放在Intent里。到了目标活动之后再从Intent中取出Bundle。
活动的启动模式一共有4种,分别是standard、singleTop、singleTask和singleInstance,可以在AndroidManifest.xml中通过给标签指定android:launchMode属性来选择启动模式。
standard
standard是活动默认的启动模式,在不进行显式指定的情况下,所有的活动都会自动使用这种启动模式。在standard模式(即默认情况)下,每当启动一个新的活动,它就会在返回栈中入栈,并处于栈顶的位置。对于使用standard模式的活动,系统不会在乎这个活动是否已经在返回栈中存在,每次启动都会创建该活动的一个新的实例。
singleTop
当活动的启动模式指定为singleTop,在启动活动时如果发现返回栈的栈顶已经是该活动,则认为可以直接使用它,不会在创建新的活动实例。但当活动并未处于栈顶位置时,这时再启动活动,还是会创建新的实例的。
singleTask
当活动的启动模式指定为singleTask,每次启动该活动时系统首先会在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用该实例,并把这个活动之上的所有活动统统出栈,如果没有发现就会创建一个新的活动实例。
singleInstance
singleInstance模式的活动会启用一个新的返回栈来管理这个活动。程序中有一个活动是允许其他程序调用的,想实现其他程序和我们的程序可以共享这个活动的实例,可以使用singleInstance模式进行解决,这种模式下会有一个单独的返回栈来管理这个活动,不管是哪个应用程序来访问这个活动,都共用的同一个返回栈,也就解决了共享活动实例的问题。
知晓当前是在哪一个活动
新增一个基础类,该类是简单的java class而不是新增空白活动,代码如下。
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("BaseActivity",getClass().getSimpleName());
}
}
更改之前的activity的继承体系,改为继承BasicActivity。这样每当进入一个活动的界面,该活动的类名就会被打印出来。
随时随地退出程序
如果一个程序已经打开了许多活动,此时退出程序是非常不方便的,需要连按许多次Back,按Home只是把程序挂起,并没有退出程序。
需要一个专门的集合类对所有的活动进行管理。
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 finishAll(){
for(Activity activity:activities){
if(!activity.isFinishing())
activity.finish();
}
}
}
当onCreate
创建活动时使用addActivity
方法,当onDestory
销毁活动时调用removeActivity
方法。当想强制退出时调用finishAll
方法。
还可以在销毁所有活动的代码后面再加上杀掉当前进程的代码,以保证程序完全退出。
android.os.Process.killProcess(android.os.Process.myPid())
killProcess()方法用于杀掉一个进程,它接收一个进程id参数,myPid()方法用于获得当前程序的进程id。killProcess()方法只能用于杀掉当前程序的进程,不能使用这个方法去杀掉其他程序。
启动活动的最佳写法
public static void actionStart(Context context,String data1,String data2){
Intent intent=new Intent(context,SecondActivity.class);
intent.putExtra("param1",data1);
intent.putExtra("param2",data2);
context.startActivity(intent);
}
通过调用函数,能够较为直观的得知传递了什么参数。