第一行代码Android读书笔记

基本了解:

Android系统架构:

  • linux内核层:

  • 系统运行层:

    C/C++库,这一层还有Android运行时库,它主要提供了一些核心库,能够允许开发者使用Java语言来编写Android应用。另外,Android运行时库中还包含了Dalvik虚拟机(5.0系统之后改为ART运行环境),它使得每一个Android应用都能运行在独立的进程当中,并且拥有一个自己的Dalvik虚拟机实例。相较于Java虚拟机,Dalvik是专门为移动设备定制的,它针对手机内存、CPU性能有限等情况做了优化处理。

  • 应用框架层

  • 应用层

应用开发特色:

  • 四大组件:

    • 活动:活动是所有Android应用程序的门面,凡是在应用中你看得到的东西,都在活动中。
    • 服务:一直在后台默默地运行,即使用户退出了应用,服务仍然是可以继续运行的。
    • 广播接收器:允许你的应用接收来自各处的广播消息。
    • 内容提供器:为应用程序之间共享数据提供了可能。
  • 丰富的系统控件:

  • SQLite数据库

  • 强大的多媒体服务

  • 地理位置定位

创建第一个Android项目:

选择创建的模板:

项目配置:

在这里插入图片描述

模拟器:

在这里插入图片描述

界面友好,有手就行。那个锤子是用来编译项目用的。绿色的三角是运行按钮,点击之后就会自动将模拟器开启然后运行这个项目。

项目结构图:

在这里插入图片描述

app模块内的项目结构:

在这里插入图片描述

AndroidManifest.xml

没有在这个文件中进行注册的活动不能使用

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.activitytest">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Activitytest"><!--application的属性值 -->
        <activity
            android:name=".HelloWorldActivity"
            android:label="This is FirstActivity"><!--activity的属性值-->
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
				<!--这里表示了这个项目得主活动,点击图标后执行的第一个活动-->
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

标签中的android:name表示这个活动对应的是那个class(一个继承自activity的一个类)

  • 当一个活动被创建时一定会执行一个onCreate的方法。
  • Android程序的设计讲究逻辑和视图分离,因此是不推荐在活动中直接编写界面的,更加通用的一种做法是,在布局文件中编写界面,然后在活动中引入进来

res文件夹下的结构:

在这里插入图片描述

res/values/strings.xml中的代码如下:

<resources>
    <string name="app_name">Activitytest</string>
</resources>

在其他地方使用这些资源有两种方式:

  • 在代码中: R.string.app_name,其中string处填写的内容如果是在引用图片资源对应的就是drawable
  • 在xml中:@string/app_name

build.gradle文件

Gradle是一个非常先进的项目构建工具

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    
    
    repositories {
    
    
//        google()
//        jcenter()这里表示要把代码放到那个仓库
        maven{
    
     url 'https://maven.aliyun.com/repository/google' }
        maven{
    
     url 'https://maven.aliyun.com/repository/jcenter' }
        maven{
    
    url 'http://maven.aliyun.com/nexus/content/groups/public'}
    }
    dependencies {
    
    
        classpath 'com.android.tools.build:gradle:4.1.3'
	    //classpath中表示了一个插件,用来配合Android项目使用的gradle插件
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    
    
    repositories {
    
    
//        google()
//        jcenter()
        maven{
    
     url 'https://maven.aliyun.com/repository/google' }
        maven{
    
     url 'https://maven.aliyun.com/repository/jcenter' }
        maven{
    
    url 'http://maven.aliyun.com/nexus/content/groups/public'}
    }
}

在这里插入图片描述

在远程依赖库中,Gradle在构建项目时会首先检查一下本地是否已经有这个库的缓存,如果没有的话则会去自动联网下载,然后再添加到项目的构建路径当中。

除了远程依赖,本地依赖,还有一种库依赖

以上内容为《第一行代码第二版》的陈述,现在可能会有很多地方不一样。

日志工具:

  • Log.v()。用于打印那些最为琐碎的、意义最小的日志信息。对应级别verbose,是Android日志里面级别最低的一种。
  • Log.d()。用于打印一些调试信息,这些信息对你调试程序和分析问题应该是有帮助的。对应级别debug,比verbose高一级。
  • Log.i()。用于打印一些比较重要的数据,这些数据应该是你非常想看到的、可以帮你分析用户行为数据。对应级别info,比debug高一级
  • Log.w()。用于打印一些警告信息,提示程序在这个地方可能会有潜在的风险,最好去修复一下这些出现警告的地方。对应级别warn,比info高一级。
  • Log.e()。用于打印程序中的错误信息,比如程序进入到了catch语句当中。当有错误信息打印出来的时候,一般都代表你的程序出现严重问题了,必须尽快修复。对应级别error,比warn高一级。

上面这些都是可以用在代码中得语句,在IDE中有一个logcat也能方便查看log信息。还可以在旁边的输入栏中输入一定的字符串进行进一步筛除

在这里插入图片描述

活动

新建一个活动(empty activity)

在这里插入图片描述

  • 任何活动都需要重写onCreate方法。

添加资源

新建方法与新建活动类似,在res对应的文件夹下选择对应的文件夹,右键选择新建。

在这里插入图片描述

操作简单,有手就行。

layout文件代码解释:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"> <!--线性布局 -->

    <Button
        android:id="@+id/button_1"   
        android:layout_width="match_parent"
        android:layout_height="113dp"
        android:text="Button 1" />

</LinearLayout>

android:id=“@+id/button_1” 给按钮定义唯一标识符,@+id/button_1中的+表明此处是定义

android:layout_width=“match_parent” 按钮的宽

android:layout_height 按钮的高

android:text 按钮上的字

match_parent 表示当前元素和父元素一样宽。

wrap_content 表示将当前内容恰好包含的高度就可。

如何将资源引入代码中

注意:在对应的xml文件里不要使用另外一个xml里面定义的资源,就是一个activity用自己xml中的,而不是其他地方的xml控件之类的。

setContentView函数用来加载一个布局文件

项目中添加的任何资源都会在R文件中生成一个相应的资源id,因此我们刚才创建的first_layout.xml布局的id现在应该是已经添加到R文件中了。在代码中去引用布局文件的方法你也已经学过了,只需要调用R.layout.first_layout就可以得到first_layout. xml布局的id,然后将这个值传入setContentView()方法即可。

仅仅如此还不能将布局资源和活动联系起来,因为还没有在Manifest.xml中进行注册。——不过一般来说IDE都帮忙进行活动注册了,但是有时候还需要自己进行一些修改。

manifest中有关activity的代码

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.activitytest">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Activitytest">
        <activity android:name=".SecondActivity"></activity>
        <activity
            android:name=".FirstActivity"
            android:label="This is FirstActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
  • 首先activity标签是放在application标签中的
  • 其次使用android:name 来表示注册的是哪一个活动,.FirstActivity其实表示的是一个包路径
  • android:label 设置活动的标题栏显示的内容,如果这个活动还是主活动的话那么这个label还会成为程序在图标下面的名称比如qq这种。

在这里插入图片描述

如果程序没有设置任何一个活动为主活动,那么这个活动虽然可以正常安装,但是无法在启动器中看到这个程序或者启动这个程序。这样的程序一般是用来提供第三方服务的,就是那种不需要被别人看到的活动。

使用findViewById()函数来通过资源id查找,该函数返回一个view对象,一般使用的时候都需要再向下转型

onCreateOptionsMenu()函数创建一个选择菜单,如果函数返回值为true,则会将其显示在活动上。

使用定义的menu资源

    public boolean onCreateOptionsMenu(Menu menu) {
    
    
        getMenuInflater().inflate(R.menu.main, menu); // 返回一个menuinflater对象之后在使用inflate方法
       										  // 两个参数,一个是菜单资源,另一个是要将创建的菜单添加到哪个菜单对象中
        return true;
    }


    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
    
    
        switch (item.getItemId()){
    
    
            case R.id.add_item:
                Toast.makeText(this, "You clicked Add", Toast.LENGTH_SHORT).show();
                break;
            case R.id.remove_item:
                Toast.makeText(this, "you clicked Remove", Toast.LENGTH_SHORT).show();
                break;
            default:
        }
        return true;
    }

在活动中使用Toast(一种简单的消息提示框)

protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.first_layout);
        Button button1 = (Button)findViewById(R.id.button_1);
        button1.setOnClickListener(new View.OnClickListener(){
    
    //给按钮添加监听事件,onclick方法将会在按钮被点击时执行
            @Override
            public void onClick(View v) {
    
    
                Toast.makeText(FirstActivity.this, "You clicked the Button 1",
                        Toast.LENGTH_SHORT).show();
            }
        });
    }

makeText方法返回一个Toast对象,makeText需要传入3个参数。第一个参数是Context,也就是Toast要求的上下文,由于活动本身就是一个Context对象,因此这里直接传入FirstActivity.this即可。第二个参数是Toast显示的文本内容,第三个参数是Toast显示的时长,有两个内置常量可以选择Toast.LENGTH_SHORT和Toast.LENGTH_LONG

销毁一个活动

finish()函数,就是相当于点击了手机上的back键的效果。

intent的使用

如何从主活动跳转到其他活动

显式Intent:显式是指很明确的指明启动哪个活动。
button1.setOnClickListener(new View.OnClickListener(){
    
    
    @Override
    public void onClick(View v) {
    
    
        Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
        // SecondActivity.class是Java反射中的一个内容(好像是),第一个参数是上下文。
        startActivity(intent); //用来启动一个活动
    }
});
隐式Intent:给intent指定action
  • 给活动指定为一个字符串,即自己定义的活动,action

相比于显式Intent,隐式Intent则含蓄了许多,它并不明确指出我们想要启动哪一个活动,而是指定了一系列更为抽象的action和category等信息,然后交由系统去分析这个Intent,并帮我们找出合适的活动去启动。

通过在标签下配置的内容,可以指定当前活动能够响应的action和category

<activity android:name=".SecondActivity">
            <intent-filter>
                <action android:name="com.example.activitytest.ACTION_START"></action> 
                <!--表明这个可以活动可以响应对应的这个活动-->
                <category android:name="android.intent.category.DEFAULT"></category>
            </intent-filter>
</activity>

标签则包含了一些附加信息,更精确地指明了当前的活动能够响应的Intent中还可能带有的category。只有和中的内容同时能够匹配上Intent中指定的action和category时,这个活动才能响应该Intent。

每个Intent中只能指定一个action,但却能指定多个category。

public void onClick(View v) {
    Intent intent = new Intent("com.example.activitytest.ACTION_START");
    intent.addCategory("android.intent.category.MYY");
    startActivity(intent);
}

对应的xml,如果注册的activity中的category不能包含intent中的category,那么会因为匹配不到activity而报错

<activity android:name=".SecondActivity">
    <intent-filter>
        <action android:name="com.example.activitytest.ACTION_START"></action>
        <category android:name="android.intent.category.DEFAULT"></category>
<!--    <category android:name="android.intent.category.MYY"></category>-->

    </intent-filter>
</activity>

注册处为活动设置的category不一定要恰好匹配intent中所要求的category,但是一定包含其对应的所有

更多隐式Intent的用法:使用内置的action

比如:Intent.ACTION_VIEW:这是一个Android系统内置的动作(也就是说会有系统中的activity来响应这个intent),其常量值为android.intent.action.VIEW。然后通过Uri.parse()方法,将一个网址字符串解析成一个Uri对象,再调用Intent的setData()方法将这个Uri对象传递进去。

public void onClick(View v) {
    
    
    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.setData(Uri.parse("http://www.baidu.com"));
    //setData函数将一个uri对象作为参数传入intent中,作为intent要处理的对象。
    startActivity(intent);
}

中的标签(又多一个过滤条件)

当intent中的数据和下面data标签中的值完全一致时才会匹配到对应的活动。

data标签中可以包含的内容:

android:scheme。用于指定数据的协议部分,如上例中的http部分。

android:host。用于指定数据的主机名部分,如上例中的www.baidu.com部分。

android:port。用于指定数据的端口部分,一般紧随在主机名之后。

android:path。用于指定主机名和端口之后的部分,如一段网址中跟在域名之后的内容。

android:mimeType。用于指定可以处理的数据类型,允许使用通配符的方式进行指定。

如果想要自己写的activity也能够响应网页的intent,则对应在xml中最要进行设置以配得上intent的要求。

<activity android:name=".ThirdActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"></action>
        <!--将这个action标签设置为和系统内那个action同名,这样在寻找intent的时候就也会找到这个activity -->
        <category android:name="android.intent.category.DEFAULT"></category>
        <data android:scheme="http"></data>
    </intent-filter>
</activity>

现在介绍另外一个系统内的action:Intent.ACTION_DIAL,以下代码可以弹出一个拨号界面:

public void onClick(View v) {
    Intent intent = new Intent(Intent.ACTION_DIAL);
    intent.setData(Uri.parse("tel:10086"));
    //这里可以看出uri不仅仅是网址
    //此时如果想要自己写的activity也能够响应,则在data的andriod:scheme属性中应该写入tel
    startActivity(intent);
}

如何向下一个活动传递数据:

还是使用intent来进行传递数据

(就是自己先创建一个“包裹”,往里面以键值对的形式putExtra)

public void onClick(View v) {
    
    
    Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
    String message = "hello SencondActivity";
    intent.putExtra("mes", message);//将需要传递的信息使用键值对的方式保存到intent中
    startActivity(intent);
}

收到“包裹”的activity首先getIntent,然后getStringExtra就可以了。

protected void onCreate(Bundle savedInstanceState) {
    
    
    super.onCreate(savedInstanceState);
    setContentView(R.layout.second_layout);
    Intent intent = getIntent();//getIntent是Activity的一个静态方法
    String mesarecv = intent.getStringExtra("mes");//接收的是字符串就是用getStringExtra
    Log.d("SecondActivity: ", mesarecv);  
}

将数据返回给上一个活动:

==startActivityForResult()==方法也是用于启动活动的,但这个方法期望在活动销毁的时候能够返回一个结果给上一个活动

public void onClick(View v) {
    
    
    Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
    startActivityForResult(intent, 1);
    //其中的1表示的是requestcode请求码用来标识身份
    //startActivityForResult(intent2, 2)这样就可以分别对应不同的任务处理
}
@Override//每个活动所调用的返回值都会送到这个函数当中来,通过requestcode进行区分
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    
    
    super.onActivityResult(requestCode, resultCode, data);
    switch (requestCode){
    
    //按照请求码进行switch
        case 1:
            if(resultCode == RESULT_OK){
    
    
                String retData = data.getStringExtra("ret_data");
                Log.d("First: ", retData);
            }
            break;
        default:
    }
}
public void onClick(View v) {
    
    //点击按钮的方式结束活动,在这里设置返回值,同样是发送一个intent“包裹”
    Intent data = new Intent();
    data.putExtra("ret_data","hello first");
    setResult(RESULT_OK, data);
    //设置返回数据,第二个参数是intent,第一个是resultcode,RESULT_OK, RESULT_CANCEL来表示处理是否成功
    finish();
}
//这样书写代码只有在点击这个按钮时才会返回结果,如果是点击back销毁活动,那么就应该在重写方法onBackPressed中写入该函数逻辑

个人对intent的一个定位:

intent(意图)对应的是一个活动创建的内容,它代表将要创建什么样的活动。

活动之间的信使(设置活动内容的一种方式),在需要启动另外一个活动的时候,startActivity, startActivityForResult就把相关信息告诉信使,让它去找对应的activity然后就启动了另外一个活动

在需要进行信息传递的时候,会将信息使用putExtra()函数将信息包含在intent当中,等到传递过去的时候,对方activity可以在intent中进行提取。

在需要进行返回信息给上一个activity的时候,会雇佣一个intent将信息使用setResult()函数存放在intent那,之后intent会到达前一个activity进而完成信息传递。

不同部分扮演的角色:

intent对于一个activity来说相当于包裹(一个由某activity生成的包裹,想要发送给指定的activity(自定义的activity或者系统中本来就有的activity比如拨号)),activity类则对应了这个活动的方法和动作,activity在xml中定义的标签则相当于activity的一个标签牌(其中的intent-filter表明这个activity能够响应哪些intent)

活动的生命周期

返回栈(Task栈)

Android中的活动是可以层叠的。我们每启动一个新的活动,就会覆盖在原活动之上,然后点击Back键会销毁最上面的活动,下面的一个活动就会重新显示出来

在这里插入图片描述

活动的状态

每个活动在其生命周期最多可能会有4种运行状态

  • 运行状态:当一个活动位于返回栈的栈顶时,这个活动就属于运行状态。
  • 暂停状态:当一个活动不在处于栈顶位置,但依然肉眼可见,这时活动就进入暂停状态。不是栈顶活动,但还是有可能被看见的,比如说弹出一个对话框,对话框并没有占满整个屏幕。处于暂停状态的活动依然是完全存活的
  • 停止状态:当一个活动不再处于栈顶位置,并且完全不可见的时候,就进入了停止状态。系统仍然会为这种活动保存相应的状态和成员变量,但是这并不是完全可靠的,当其他地方需要内存时,处于停止状态的活动有可能会被系统回收
  • 销毁状态:当一个活动从返回栈中移除后就变成了销毁状态,系统往往倾向于回收处于这个状态的活动,从而保证手机的内存充足。

活动的生存期:

Activity类中定义了7个回调方法,覆盖了活动生命周期的每一个环节

  • onCreate():会在活动第一次被创建时调用,在这个方法中应该完成一些初始化操作
  • onStart():这个方法在活动由不可见变为可见时调用
  • onResume():这个方法在活动准备好和用户进行交互的时候调用。此时的活动一定位于返回栈的栈顶,并且处于运行状态
  • onPause():这个方法在系统准备去启动或者恢复另一个活动的时候调用。我们通常会在这个方法中将一些消耗CPU的资源释放掉,以及保存一些关键数据,但这个方法的执行速度一定要快,不然会影响到新的栈顶活动的使用。
  • onStop()。这个方法在活动完全不可见的时候调用。它和onPause()方法的主要区别在于,如果启动的新活动是一个对话框式的活动,那么onPause()方法会得到执行,而onStop()方法并不会执行。
  • onDestroy()。这个方法在活动被销毁之前调用之后活动的状态将变为销毁状态
  • onRestart()。这个方法在活动由停止状态变为运行状态之前调用,也就是活动被重新启动了。

以上7个方法中除了onRestart()方法,其他都是两两相对的,从而又可以将活动分为3种生存期

  • 完整生存期:活动在onCreate方法和onDestroy方法之间所经历的,就是完整生存期
  • 可见生存期:活动在onStart方法和onStop方法之间所经历的就是可见生存期。
  • 前台生存期:活动在onResume方法和onPause方法之间所经历的就是前台生存期

在这里插入图片描述

请添加图片描述

活动被回收了怎么办?

当一个活动处于停止状态时是有可能被系统回收的。如果一个活动在停止状态被回收了,而之后又通过back键使得该活动重新出现,那么这个时候并不会执行onRestart()方法,而是执行onCreate方法。这种情况下活动需要重新创建。

这样存在一个问题:

活动在由停止状态重新运行的时候,之前保存的一些变量也会跟着被重新建立。

这里有一个方法可以解决这个问题:onSaveInstanceState()

这个方法会携带一个Bundle类型的参数,而Bundle提供一系列的方法用于保存数据,比如:putString()保存字符串,每个保存方法需要传入两个参数,一个是键,一个是值。

// 在activity类中添加如下代码即可
@Override
protected void onSaveInstanceState(Bundle outState){
    
    
    super.onSaveInstanceState(outState);
    String tempData = "hello";
    outState.putString("data_key", tempData);
}

我们一直使用的onCreate()方法其实也有一个Bundle类型的参数。这个参数在一般情况下都是null但是如果在活动被系统回收之前有通过onSaveInstanceState()方法来保存数据的话,这个参数就会带有之前所保存的全部数据,我们只需要再通过相应的取值方法将数据取出即可。

String tempData = saveInstanceState.getString("data_key");

活动的启动模式

启动模式一共有4种,分别是standard、singleTop、singleTask和singleInstance,可以在AndroidManifest.xml中通过给标签指定android:launchMode属性来选择启动模式

standard:

在standard模式(即默认情况)下,每当启动一个新的活动,它就会在返回栈中入栈,并处于栈顶的位置。对于使用standard模式的活动,系统不在乎这个活动是否已经在返回栈中存在每次启动都会创建该活动的一个新的实例

在这里插入图片描述

singleTop:

这种启动方式,当发现想要启动的活动就位于栈顶,就直接使用它而不再创建新的实例。但是如果活动不在栈顶,调用时还是会创建新的实例。

singleTask:

这种启动方式会检查整个返回栈中有没有这个活动的实例。

如果栈中存在这个实例,则将这个活动上面的活动统统出栈(从下往上依次出栈)

如果没有就创建一个新的实例。

在这里插入图片描述

singleInstance:

这种启动方式会将启动的活动放到另外一个返回栈中,比如A singleinstance启动B,B启动C,那么C直接位于A之上,B单独位于一个返回栈中。从活动C开始按back键,C出栈显现A,再back,A出栈,栈空开始显现B。

在这里插入图片描述

tips:

学会自己创建一些类来辅助开发,比如书中的ActivityCollect和BaseActivity

也可以通过编写一些方法来达到优化代码的效果,比如活动的创建,就可以自己创建一个方法Second.actionStart()然后在这里面把创建语句进行结合。

UI开发

常见控件(记单词,缩写),和资源是两个不同的概念

Android控件的可见属性:

3种:

  • visible可见
  • invisible不可见但是占据空间
  • gone不可见不占据空间

Textview:

<TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/text_view"
        android:text="hello"
        android:gravity="center"
        android:textSize="24sp"
        android:textColor="#00ff00"
        />

Button:

<Button
        android:id="@+id/button_1"
        android:layout_width="match_parent"
        android:layout_height="113dp"
        android:text="Button 1"
        android:textAllCaps="false"/>
        <!--默认会把字母变成大写,上面的属性可以屏蔽-->      

EditText:

<EditText
        android:id="@+id/edit_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="I am fatw"
        android:maxLines="2"
        />

EditText通过实例化为对象之后,提供一个getText()(返回值为Editable类型)的方法获取其中输入的内容

ImageView:

<ImageView
        android:id="@+id/image_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/abc_vector_test"/>

控件定义好了之后,还可以在代码中动态的修改控件的属性。比如

ImageView imageView = (ImageView)findViewById(R.id.image_view);
imageView.setImageResource(R.drawable.img_1);

ProgressBar:

<ProgressBar
        android:id="@+id/progress_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        style="@style/Widget.AppCompat.ProgressBar.Horizontal"
        android:max="100"/>

在代码中可以通过findViewById之后的progress,然后对其值的变换,progress+=10来改变进度条的进度,当满足条件时就将其控件可见属性设置为gone

AlertDialog:

这个控件不是通过xml来定义的

public void onClick(View v) {
    
    
                AlertDialog.Builder alertDialog = new AlertDialog.Builder(FirstActivity.this);
                alertDialog.setTitle("This is Dialog");
                alertDialog.setMessage("Something important");
                alertDialog.setCancelable(false);//可否用back键关闭
                alertDialog.setPositiveButton("OK", new DialogInterface.OnClickListener() {
    
    
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
    
    

                    }
                });
                alertDialog.setNegativeButton("cancel", new DialogInterface.OnClickListener() {
    
    
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
    
    

                    }
                });
                alertDialog.show();
            }

ProgressDialog

这个和上面的类似,值得注意的是当加载部分完成,而且又设置不能通过back键来关闭该对话框时,记得自己调用dismiss函数来关闭这个控件,否则会一直出现在屏幕上。

4种基本布局

  • 线性布局
  • 相对布局
  • 帧布局
  • 百分比布局

布局和控件的关系:布局中还可以放布局

在这里插入图片描述

线性布局LinerLayout:

记个单词:

orientation:方向,控制布局中的控件以水平线性还是垂直线性进行排列。

  • android:layout_gravity

    控件在布局中的位置,是控件的属性控制该控件在其所在布局中的位置,布局也可以放在布局中,所以也有这个属性

  • android:gravity

    文字在控件中的位置

  • layout_weight(线性布局独有)

    通过比例的方式来进行大小的指定

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:orientation="horizontal">


   <EditText
       android:layout_width="0dp"
       android:layout_height="wrap_content"
       android:layout_weight="10"/>
<!--0dp是一种规范写法,当设置了layout_weight之后就不会在使用layout_width,但是不能把该属性删掉 -->
    <Button
        android:id="@+id/button_3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="send" />
<!--按比例一个是1/11,一个是10/11-->

</LinearLayout>

相对布局RelativeLayout:

明显和线性布局相比,相对布局的位置关系更加丰富(更加复杂)

记个单词:

align:排列

相对于父布局

  • alignParentTop=“True” 设置相对于父布局的位置
  • alignParentLeft=“True”

上面两个合起来就是该控件位于布局中的左上的位置

android:layout_centerInParent="true"
位于父布局中的中心位置

相对于布局中的控件

相对于控件整体的相对位置,上下左右

  • layout_above/below属性值为@id/button1这一类的
  • layout_toLeftOf
<Button
        android:id="@+id/button_4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:layout_weight="10"
        android:text="send4" />

    <Button
        android:id="@+id/button_5"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="10"
        android:layout_below="@id/button_4"
        android:layout_toRightOf="@id/button_4"
        android:text="send5" />

相对于控件的左右边缘

在这里插入图片描述

右边缘和右边缘对其

layout_alignRight

帧布局

应用场景较少,具体后续fragment处见

百分比布局

通过百分比来控制控件的大小

百分比布局属于新增布局,Android团队将百分比布局定义在了support库当中,只需要在项目的build.gradle中添加百分比布局库的依赖,就能保证百分比布局在Android所有系统版本上的兼容性了。

打开app/build.gradle文件,在dependencies闭包中添加如下内容:

implementation 'androidx.percentlayout:percentlayout:1.0.0'

百分比布局:

<?xml version="1.0" encoding="utf-8"?>
<androidx.percentlayout.widget.PercentFrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:layout_height="0dp"
        android:layout_width="0dp"
        android:id="@+id/btn1"
        android:layout_gravity="left|top"
        android:text="btn1"
        android:textAllCaps="false"
        app:layout_heightPercent="50%"
        app:layout_widthPercent="50%" />

    <Button
        android:layout_height="0dp"
        android:layout_width="0dp"
        android:id="@+id/btn2"
        android:layout_gravity="right|top"
        android:text="btn2"
        android:textAllCaps="false"
        app:layout_heightPercent="50%"
        app:layout_widthPercent="50%" />
    <Button
        android:layout_height="0dp"
        android:layout_width="0dp"
        android:id="@+id/btn3"
        android:layout_gravity="left|bottom"
        android:text="btn3"
        android:textAllCaps="false"
        app:layout_heightPercent="50%"
        app:layout_widthPercent="50%" />

    <Button
        android:layout_height="0dp"
        android:layout_width="0dp"
        android:id="@+id/btn4"
        android:text="btn4"
        android:layout_gravity="right|bottom"
        app:layout_widthPercent="50%"
        app:layout_heightPercent="50%"/>

</androidx.percentlayout.widget.PercentFrameLayout>

自定义控件

控件的继承结构

在这里插入图片描述

引入布局:

背景:有些时候会固定的使用某种控件的布局,但是有没有对应的控件可以直接使用,为了减少代码量,可以采用引入布局的设置

在使用自己设计的布局的时候就使用include语句将布局引入。

示例:

title.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="asda" />

</LinearLayout>

first_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="fdgdfg" />

    <include layout="@layout/title" />
</LinearLayout>

上述过程仅仅将布局引入,但是如果要对引入的布局中的控件编写响应事件的话,还得在不同的活动中在编写一次

可以通过编写自定义控件(就是写一个对应的类)来解决问题。

创建自定义控件

示例:

public class TitleLayout extends LinearLayout {
    
    
    public TitleLayout(Context context, AttributeSet attrs) {
    
    
        super(context, attrs);
        LayoutInflater.from(context).inflate(R.layout.title, this);
        // inflate方法可以动态加载一个布局文件,第二个参数表示
        
        //后续代码将组件中的事件都处理好
        Button titleBack = (Button)findViewById(R.id.title_back);
        titleBack.setOnClickListener(new OnClickListener(){
    
    
            public void onClick(View v){
    
    
                ((Activity)getContext()).finish();
            }
        })
    }
}
//创建一个自定义控件就是创建一个类,而相应的资源文件同样需要使用xml进行定义,在如上述代码所示对资源文件进行引用

在xml代码中

<com.example.uicustomviews.TitleLayout
             android:layout_width="match_parent"
             android:layout_height="wrap_content" />

ListView控件

<ListView
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
public class MainActivity extends AppCompatActivity {
    
    
    private String[] data = {
    
    "apple", "banana", "orange", "pear", "watermelon", "cherry", "mango",
            "pineapplej", "asdasd", "werwer", "trtrtr", "trdgfre", "fgdbvbm", "sdjflks", "dfdlkjfg",
    "sdfksjd"};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 数据并不能直接传入listview中,需要通过适配器
        ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(
                MainActivity.this, android.R.layout.simple_list_item_1, data);
        // android.R.layout.simple_list_item_1是ListView的子项布局,也就是每一栏是什么布局android.表示系统内置的
        ListView listView = (ListView) findViewById(R.id.list_view);
        listView.setAdapter(arrayAdapter); //将构建好的数据传入ListView当中。
    }
}

定制ListView界面,也就是定制ListView的子布局

// FruitAdapter.java
public class FruitAdapter extends ArrayAdapter<Fruit> {
    
    
    private int resourceId;

    public FruitAdapter(Context context, int textViewResourceId,
                        List<Fruit> objects) {
    
    
        super(context, textViewResourceId, objects);
        resourceId = textViewResourceId;
    }

    @Override // getView方法是当子项被滚进页面的时候就会调用的方法。
    public View getView(int position, View convertView, ViewGroup parent) {
    
    
        Fruit fruit = getItem(position); // 获取当前项的Fruit实例
        View view;
        ViewHolder viewHolder;
        if (convertView == null) {
    
     // conertView可以将已经加载的子项进行缓存,提高运行效率
            view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
            viewHolder = new ViewHolder();
            viewHolder.fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
            viewHolder.fruitName = (TextView) view.findViewById(R.id.fruit_name);
            view.setTag(viewHolder); // 将ViewHolder存储在View中
        } else {
    
    
            view = convertView;
            viewHolder = (ViewHolder) view.getTag(); // 重新获取ViewHolder
        }
        viewHolder.fruitImage.setImageResource(fruit.getImageId());
        viewHolder.fruitName.setText(fruit.getName());
        return view;
    }

    class ViewHolder {
    
    
        ImageView fruitImage;
        TextView fruitName;
    }
}
package com.example.listviewtest;

public class Fruit {
    
    

    private String name;

    private int imageId;

    public Fruit(String name, int imageId) {
    
    
        this.name = name;
        this.imageId = imageId;
    }

    public String getName() {
    
    
        return name;
    }

    public int getImageId() {
    
    
        return imageId;
    }

}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/fruit_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/fruit_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="10dp" />

</LinearLayout>

目前以了解与理解为主,对于需要靠记忆的控件部分的知识,理解就行。

对于新增的控件为了让所有Android版本都可以使用,Android团队将其定义在了support库中,需要在build.gradle中导入同步之后才能继续使用。

Nine-patch图片是一种特殊处理过的图片,能够指定哪些区域可以被拉伸,哪些区域不可以。

碎片(fragment)

碎片(Fragment)是一种可以嵌入在活动当中的UI片段,与活动十分相似。

碎片的使用方式

简单用法

新建xml来进行碎片的布局,和普通的活动的布局没有什么区别

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    android:background="@color/purple_500">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="hello fragment" />

</LinearLayout>

新建一个LeftFragment类,并让他继承Android内置的Fragment类或者support库中的Fragment类,默认情况下这个support库会被引入appcompat-v7库时一并引入

//RightFragment
public class RightFragment extends Fragment {
    
    
    @Nullable
    @Override
    // 重写onCreateView方法
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    
    
        View view = getLayoutInflater().inflate(R.layout.rightfragment, container, false);
        return view;
    }
}

接着在activity_main.xml中对两个碎片进行布局。使用fragment标签

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <fragment
        android:id = "@+id/left_fragment"
        android:layout_weight="1"
        android:layout_width = "0dp"
        android:name="com.example.fragmenttest.LeftFragment"
        android:layout_height="match_parent" />

    <fragment
        android:id="@+id/right_fragment"
        android:layout_weight="1"
        android:layout_width="0dp"
        android:name="com.example.fragmenttest.RightFragment"
        android:layout_height="match_parent" />

</LinearLayout>

mainactivity中正常加载mainactivity的布局即可

动态加载碎片

主要步骤为一下5步:

  • 创建待添加的碎片实例
  • 获取FragmentManager
  • 开启一个事务
  • 向容器中添加或替换碎片,一般使用replace方法实现,需要传入容器的id和待添加的碎片实例
  • 通过commit()提交事务
private void replaceFragment(Fragment fragment){
    
    
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.replace(R.id.right_layout, fragment);
        fragmentTransaction.commit();
}

在碎片中模拟返回栈

主要函数:FragmentTransaction中的addToBackStack()方法

private void replaceFragment(Fragment fragment){
    
    
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.replace(R.id.right_layout, fragment);
        fragmentTransaction.addToBackStack(null);// 在提交事务之前先将事务加入返回栈,这样旧的fragment就会压入返回栈中
        fragmentTransaction.commit();
}

这样之后的效果是,不断按返回键首先跳转到之前的rightFragment布局之后,右边布局消失,在之后才退出程序。(???怎么理解变成空白)

碎片与活动之间的通信

在活动中调用碎片中的方法:使用FragmentManager提供的findFragmentById就可以获得碎片的实例。

在碎片中调用活动里的方法:每个碎片都可以通过调用getActivity(返回一个context对象)方法来得到和当前碎片相关联的活动实例

碎片的生命周期

碎片的状态

  • 运行状态:

碎片可见并且它所关联的活动正处于运行状态

  • 暂停状态:

一个未占满屏幕的活动被添加到了栈顶,活动进入暂停状态,与之相关联的可见碎片就会进入暂停状态。

  • 停止状态:

当一个活动进入停止状态时,与它相关联的碎片就会进入到停止状态

  • 销毁状态:

碎片总是依附于活动而存在的,因此当活动被销毁时,与它相关联的碎片就会进入到销毁状态。


通过调用FragmentTransaction的remove()、replace()方法将碎片从活动中移除,如果事务提交之前并没有调用addToBackStack()方法,这时的碎片也会进入到销毁状态就是会执行onDetach。如果调用了addToBackStack方法则会进入停止状态仅执行到onDestoryView。

回调方法

碎片中比较独特的回调方法:

  • onAttach()。当碎片和活动建立关联的时候调用。

  • onCreateView()。为碎片创建视图(加载布局)时调用。

  • onActivityCreated()。确保与碎片相关联的活动一定已经创建完毕的时候调用。

  • onDestroyView()。当与碎片关联的视图被移除的时候调用。

  • onDetach()。当碎片和活动解除关联的时候调用。

下图为将Fragment加入返回栈中的情况

在这里插入图片描述

动态加载布局的技巧

限定符

在这里插入图片描述

红色框内就是一种限定符

在这里插入图片描述

系统根据当前设备的大小,自己认为应该加载large中的还是layout中的布局。

但是large并不明确,还有一种限定符layout-sw600dp这个表示(SmallWidth)当程序运行在大于等于600dp的设备上时会加载这个文件夹中的xml文件。

广播

简介

Android中每个程序都可以对自己感兴趣的广播进行注册,这样程序就只会接收到自己感兴趣的广播内容。应用程序可以自由的发送和接受广播。

广播是一种可以跨进程的通信。

广播主要有两种类型:

  • 标准广播

在广播发出之后,所有的广播接收器几乎都会在同一时刻接收到这条广播消息。这种方式效率比较高,但是无法截断。

在这里插入图片描述

  • 有序广播

在广播发出之后,同一时刻只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕后,广播才会继续传递。所以此时的广播接收器是有先后顺序的,优先级高的广播接收器就可以先收到广播消息,并且前面的广播接收器还可以截断正在传递的广播,这样后面的广播接收器就无法收到广播消息了。

在这里插入图片描述

接收系统广播

动态注册监听网络变化

注册广播的方式一般有两种:

  • 在代码中注册
  • 在AndroidManifest.xml中注册

广播接收器---->一个继承自Broadcast-Recevier的类

public class MainActivity extends AppCompatActivity {
    
    
    private IntentFilter intentFilter;
    private NetworkChangeReciver networkChangeReciver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        intentFilter = new IntentFilter();
        intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
        //将想要监听的广播信息通过action加入
        networkChangeReciver = new NetworkChangeReciver();
        registerReceiver(networkChangeReciver, intentFilter);
    }

    @Override
    protected void onDestroy() {
    
    
        super.onDestroy();
        unregisterReceiver(networkChangeReciver);
        // 动态注册的广播接收器需要在最后取消注册
    }

    class NetworkChangeReciver extends BroadcastReceiver {
    
    
        @Override
        public void onReceive(Context context, Intent intent) {
    
    
            Toast.makeText(context, "network changes", Toast.LENGTH_SHORT).show();
        }
    }
}

静态注册实现开机启动

使用Android Studio提供的快捷方式来创建一个广播接收器,右击com.example.broadcasttest包→New→Other→Broadcast Receiver

在这里插入图片描述

静态注册在AndroidManifest.xml中

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.broadcasttest">

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <!--需要提供权限 -->
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.BroadcastTest">
        
        <!--静态注册broadcastReciver与注册activity差不多 -->
        <receiver
            android:name=".BootCompleteReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <!--在action中的android:name决定要接收什么样的广播信息 -->
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
            </intent-filter>
        </receiver>

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

不要在onReceive()方法中添加过多的逻辑或者进行任何的耗时操作,因为在广播接收器中是不允许开启线程的,当onReceive()方法运行了较长时间而没有结束时,程序就会报错。因此广播接收器更多的是扮演一种打开程序其他组件的角色,比如创建一条状态栏通知,或者启动一个服务等

发送自定义广播

发送标准广播

protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST");
                intent.setComponent(new ComponentName(getPackageName(), "com.example.broadcasttest.MyBroadcastReceiver"));
                // Android8.0后的新特性,静态注册的话需要添加component,MyBroadcastReceiver是接收广播的一个类,通过静态注册
                sendBroadcast(intent);//发送标准广播
            }
        });
    }

发送有序广播

可以通过设置优先级来实现广播的有序播送,而且在优先级较高的广播接收器接收到广播之后,可以通过abortBroadcast()来截断广播的继续传播。

使用本地广播

简单的说就是发出的广播只能在应用程序内部进行传递,并且广播接收器只能够接收来本应用程序发出的广播。

本地广播无法通过静态注册。进行静态注册的广播能够在程序未启动的状态下接收广播信息

public class MainActivity extends AppCompatActivity {
    
    
    private IntentFilter intentFilter;
    private LocalReceiver localReceiver;
    private LocalBroadcastManager localBroadcastManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = (Button) findViewById(R.id.button);
        localBroadcastManager = LocalBroadcastManager.getInstance(this);
        button.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                Intent intent = new Intent("com.example.broadcasttest.LOCAL_BROADCAST");
                localBroadcastManager.sendBroadcast(intent);
            }
        });
        // 通过LocalBroadcastManager来进行本地的广播发送和管理,和动态注册的步骤差不多
        intentFilter = new IntentFilter();
        intentFilter.addAction("com.example.broadcasttest.LOCAL_BROADCAST");
        localReceiver = new LocalReceiver();
        localBroadcastManager.registerReceiver(localReceiver, intentFilter);
    }

    @Override
    protected void onDestroy() {
    
    
        super.onDestroy();
        localBroadcastManager.unregisterReceiver(localReceiver);
    }

    class LocalReceiver extends BroadcastReceiver{
    
    

        @Override
        public void onReceive(Context context, Intent intent) {
    
    
            Toast.makeText(context, "received local broadcast", Toast.LENGTH_SHORT).show();
        }
    }

}

数据存储

Android系统主要提供了3中数据化持久的方式

  • 文件存储
  • SharedPreferences
  • 数据库存储

文件存储

将数据存储到文件中

context类中提供了一个openFileOutput方法,方法接收两个参数:

  • 参数的文件名
  • 文件的操作模式
    • Context.MODE_PRIVATE:会覆盖原文件的内容
    • Context.MODE_APPEND:在原文件的内容的基础上附加
// 与Java中的数据存储到文件差不多
public void save(String inputText){
    
    
        FileOutputStream out = null;
        BufferedWriter writer = null;
        try {
    
    
            out = openFileOutput("data", Context.MODE_PRIVATE);
            writer = new BufferedWriter(new OutputStreamWriter(out));
            writer.write(inputText);
        } catch (FileNotFoundException e) {
    
    
            e.printStackTrace();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }finally {
    
    
            if(writer != null){
    
    
                try {
    
    
                    writer.close();
                } catch (IOException e) {
    
    
                    e.printStackTrace();
                }
            }
        }
    }

在app中得到的文件在虚拟设备文件系统,/data/data/com.example.filepersistencetest/files路径下,Android studio中查看方式如下

在这里插入图片描述

从文件中读取数据

openFileInput方法,返回一个FileInputStream对象(字节流)

public String Load() {
    
    
        FileInputStream in = null;
        BufferedReader reader = null;
        StringBuilder content = new StringBuilder();
        try {
    
    
            in = openFileInput("data");
            reader = new BufferedReader(new InputStreamReader(in));
            // in是FileInputStream是一个字节流,通过InputStreamReader实现字节流与字符流之间的桥梁的构建。
            String line = "";
            while ((line = reader.readLine()) != null) {
    
    
                content.append(line);
            }
        } catch (FileNotFoundException e) {
    
    
            e.printStackTrace();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }finally {
    
    
            if(reader != null){
    
    
                try {
    
    
                    reader.close();
                } catch (IOException e) {
    
    
                    e.printStackTrace();
                }
            }
        }
        return content.toString();
    }

SharedPreferences

该方式是使用键值对的方式来存储数据,还支持多种不同的数据类型的存储

SharedPreferences文件都是存放在/data/data//shared_prefs/目录下

数据存储

获取SharedPreferences对象的三种方法

  • Context类中的getSharedPreferences()。两个参数,一个是文件名,一个是操作模式(目前只有MODE_PRIVATE这一种指定操作模式,表示只有当前的应用程序才可以对这个文件进行读写)
  • Activity类中的getPreferences()。这个方法自动将活动名作为文件名
  • PreferencesManager类中的getDefaultSharedPreferences()

向SharedPreferences文件中存储数据:

  1. 调用SharedPreferences对象的edit方法来获取一个SharedPreferences.Editor对象
  2. 向Editor对象中添加数据,putString():表示添加一个字符串。格式(putxxx)
  3. 调用apply方法将添加的数据提交,从而完成数据存储操作
SharedPreferences.Editor editor = getSharedPreferences("data", MODE_PRIVATE).edit();
editor.putString("name", "Tom");
editor.putInt("age", 28);
editor.putBoolean("married", false);
editor.apply();

在这里插入图片描述

数据读取

SharedPreferences对象中提供了一系列的get方法,这些get方法与写入时的putxxx方法对应,方法有两个参数:存储时的键值,找不到对应键的时候返回什么默认值。

SharedPreferences pref = getSharedPreferences("data", MODE_PRIVATE);
String name = pref.getString("name", "");
int age = pref.getInt("age", 0);
boolean married = pref.getBoolean("married", false);

数据库存储

SQLite:轻量级关系型数据库

创建数据库

SQLiteOpenHelper帮助类(抽象类中)

两个抽象方法:

  • onCreate()------创建数据库
  • onUpgrade()--------更新数据库

两个实例方法:

  • getReadableDatabase()
  • getWritableDatabase()

这两个方法可以创建或打开一个现有的数据库。

不同的是,在磁盘空间已满的时候,readable那个方法将只以可读的方式打开数据库

writable方式则会出错

升级数据库

如果dbHelper = new MyDatabaseHelper(this, “BookStore.db”, null, 2);的第三的参数version变大时,就会调用upgrade方法

Android提供的数据库操作的方法

public void onCreate(Bundle savedInstanceState) {
    
    
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 2);

    Button createDatabase = (Button) findViewById(R.id.create_database);
    createDatabase.setOnClickListener(new View.OnClickListener() {
    
    
        @Override
        public void onClick(View v) {
    
    
            dbHelper.getWritableDatabase();
        }
    });

    Button addData = (Button) findViewById(R.id.add_data);
    addData.setOnClickListener(new View.OnClickListener() {
    
    
        @Override
        public void onClick(View v) {
    
    
            SQLiteDatabase db = dbHelper.getWritableDatabase();
            ContentValues values = new ContentValues();
            // 开始组装第一条数据
            values.put("name", "The Da Vinci Code");
            values.put("author", "Dan Brown");
            values.put("pages", 454);
            values.put("price", 16.96);
            db.insert("Book", null, values); // 插入第一条数据
            values.clear();
            // 开始组装第二条数据
            values.put("name", "The Lost Symbol");
            values.put("author", "Dan Brown");
            values.put("pages", 510);
            values.put("price", 19.95);
            db.insert("Book", null, values); // 插入第二条数据
        }
    });
	
    //update
    Button updateData = (Button) findViewById(R.id.update_data);
    updateData.setOnClickListener(new View.OnClickListener() {
    
    
        @Override
        public void onClick(View v) {
    
    
            SQLiteDatabase db = dbHelper.getWritableDatabase();
            ContentValues values = new ContentValues();
            values.put("price", 10.99);
            //后两个参数用来进行条件约束,?是一个占位符,第四个参数(字符数组)指定相应的内容
            db.update("Book", values, "name = ?", new String[] {
    
     "The Da Vinci Code" });
        }
    });

    // delete
    Button deleteButton = (Button) findViewById(R.id.delete_data);
    deleteButton.setOnClickListener(new View.OnClickListener() {
    
    
        @Override
        public void onClick(View v) {
    
    
            SQLiteDatabase db = dbHelper.getWritableDatabase();
            db.delete("Book", "pages > ?", new String[] {
    
     "500" });
        }
    });

    //查询
    Button queryButton = (Button) findViewById(R.id.query_data);
    queryButton.setOnClickListener(new View.OnClickListener() {
    
    
        @Override
        public void onClick(View v) {
    
    
            SQLiteDatabase db = dbHelper.getWritableDatabase();
            // 查询Book表中所有的数据
            Cursor cursor = db.query("Book", null, null, null, null, null, null);
            if (cursor.moveToFirst()) {
    
    
                do {
    
    
                    // 遍历Cursor对象,取出数据并打印
                    String name = cursor.getString(cursor.getColumnIndex("name"));
                    String author = cursor.getString(cursor.getColumnIndex("author"));
                    int pages = cursor.getInt(cursor.getColumnIndex("pages"));
                    double price = cursor.getDouble(cursor.getColumnIndex("price"));
                    Log.d("MainActivity", "book name is " + name);
                    Log.d("MainActivity", "book author is " + author);
                    Log.d("MainActivity", "book pages is " + pages);
                    Log.d("MainActivity", "book price is " + price);
                } while (cursor.moveToNext());
            }
            cursor.close();
        }
    });
}

使用sql操作数据库

db.exeSQL("insert into BOOK(name, author, pages, price) values(?,?,?,?)", new String[] {
    
    "The da vinci code", "Dan Brown", "454", "16.96"});

db.exeSQL("update Book set price = ? where name = ?",new String[]{
    
    "10.99","The davinci code"});

db.exeSQL("delete from Book where pages > ?", new String[]{
    
    "500"});

db.rawQuery("select * from Book", null);//null这个地方本来应该是string[]

使用LitePal操作数据库

LitePal是一款开源的数据库框架

配置开源库litepal

首先在app/build.gradle中dependencies闭包中添加引用

之后在src/main目录下创建一个assets目录(这个目录的大致功能与res差不多,但是在访问的时候需要通过AssetsManager来进行访问,并且他不会被编译到R类中)

在assets目录中定义litepal.xml

<?xml version="1.0" encoding="utf-8"?>
<litepal>
    <dbname value="BookStore" ></dbname>

    <version value="2" ></version>
	<!--list 指定映射关系,映射关系可以使我们使用面向对象的方式来操作数据库 -->
    <list>
        <mapping class="com.example.litepaltest.Book"></mapping>
        <mapping class="com.example.litepaltest.Category"></mapping>
    </list>
</litepal>

在litepal中的创建和升级数据库

// 创建一个类,让他与数据库中的表相对应,在litepalxml中有与之对应的映射关系
public class Book extends DataSupport {
    
    

    private int id;

    private String author;

    private double price;

    private int pages;

    private String name;

    private String press;

    public int getId() {
    
    
        return id;
    }

    public void setId(int id) {
    
    
        this.id = id;
    }

    public String getAuthor() {
    
    
        return author;
    }

    public void setAuthor(String author) {
    
    
        this.author = author;
    }

    public double getPrice() {
    
    
        return price;
    }

    public void setPrice(double price) {
    
    
        this.price = price;
    }

    public int getPages() {
    
    
        return pages;
    }

    public void setPages(int pages) {
    
    
        this.pages = pages;
    }

    public String getName() {
    
    
        return name;
    }

    public void setName(String name) {
    
    
        this.name = name;
    }

    public String getPress() {
    
    
        return press;
    }

    public void setPress(String press) {
    
    
        this.press = press;
    }

}

使用litepal创建数据库

public void onClick(View v){
    
    
    LitePal.getDatabase();//就完成了数据库创建,创建的内容一litepal中Mapping对应的类
}

使用litepal升级数据库的时候,不需要将之前有的表删除,可以直接在修改了版本号,mapping和对应的Java类之后调用litepal.getdatabase()

使用litepal进行增删改查

protected void onCreate(Bundle savedInstanceState) {
    
    
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Button createDatabase = (Button) findViewById(R.id.create_database);
    createDatabase.setOnClickListener(new View.OnClickListener() {
    
    
        @Override
        public void onClick(View v) {
    
    
            Connector.getDatabase();// LitePal.getDatabase()就是调用的这个函数
        }
    });
    
    Button addData = (Button) findViewById(R.id.add_data);
    addData.setOnClickListener(new View.OnClickListener() {
    
    
        @Override
        public void onClick(View v) {
    
    
            Book book = new Book();
            book.setName("The Da Vinci Code");
            book.setAuthor("Dan Brown");
            book.setPages(454);
            book.setPrice(16.96);
            book.setPress("Unknow");
            book.save(); //将数据添加到数据库中
        }
    });
    
    Button updateData = (Button) findViewById(R.id.update_data);
    updateData.setOnClickListener(new View.OnClickListener() {
    
    
        @Override
        public void onClick(View v) {
    
    
            Book book = new Book();
            book.setPrice(14.95);
            book.setPress("Anchor");
            //updataAll()中的参数可以添加约束条件,如果没有则代表对所有的列进行更新
            //book.setToDefault("pages");
            //book.updataAll();这两行代码则将pages列的数据恢复成默认值
            book.updateAll("name = ? and author = ?", "The Lost Symbol", "Dan Brown");
        }
    });
    
    Button deleteButton = (Button) findViewById(R.id.delete_data);
    deleteButton.setOnClickListener(new View.OnClickListener() {
    
    
        @Override
        public void onClick(View v) {
    
    
            DataSupport.deleteAll(Book.class, "price < ?", "15");
        }
    });
    
    Button queryButton = (Button) findViewById(R.id.query_data);
    queryButton.setOnClickListener(new View.OnClickListener() {
    
    
        @Override
        public void onClick(View v) {
    
    
            List<Book> books = DataSupport.findAll(Book.class);
            // 还有类似findFirst(Book.class);findLast(Book.class);
            for (Book book: books) {
    
    
                Log.d("MainActivity", "book name is " + book.getName());
                Log.d("MainActivity", "book author is " + book.getAuthor());
                Log.d("MainActivity", "book pages is " + book.getPages());
                Log.d("MainActivity", "book price is " + book.getPrice());
                Log.d("MainActivity", "book press is " + book.getPress());
            }
        }
    });
}

查询时litepal还提供了连续查询的方式,

List<Book> books = DataSupport.select("name", "author", "pages").where("pages > ?", "400").order("pages").limit(10).offset(10).find(Book.class);

也支持原生sql的查询方式

Cursor c = DataSupport.findBySQL("select * from Book where pages > ?","400");

内容提供器

实现不同程序之间的是数据共享

操作和SQLiteDatabase差不多就是定位资源的时候使用的是内容uri(毕竟不在同一个应用程序当中)

运行时权限

有些app在安装时就会要求获取权限,如果不给就不能安装。而现在Android提供一种运行时权限获取,对于那些比较敏感的权限,在需要时才会向用户发起请求。

Android将程序分为两类,一类是普通权限,一类是危险权限。(对于普通权限的申请系统会自动帮忙进行处理)。对于危险权限的申请需要进行运行时权限处理,普通权限就只要在AndroidManifest.xml中添加申明一下即可(就是在broadcast章节中使用的xml标签)

危险权限:

在这里插入图片描述

表中的每个危险权限属于一个权限组,用户一旦同意授权组中某一权限,该组中其他权限也会被授权。

在程序运行时申请权限

对于危险权限的申请如果只是在AndroidManifest.xml中进行了声明,而没有进行权限处理的话,程序会抛出异常。(捕获到的异常信息)

在这里插入图片描述

程序进行权限处理,主要做的工作就是查询是否已经授权,在未授权的情况下请求授权,并对不同的授权结果进行对应的处理。

protected void onCreate(Bundle savedInstanceState) {
    
    
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Button makecall = (Button) findViewById(R.id.make_call);
    makecall.setOnClickListener(new View.OnClickListener() {
    
    
        @Override
        public void onClick(View view) {
    
    
            if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
    
    
                ActivityCompat.requestPermissions(MainActivity.this, new String[]{
    
    Manifest.permission.CALL_PHONE}, 1);// 这个函数调用完之后会弹出一个对话框,之后无论是否授权都会调用onRequestPermissionResult方法,最后一个参数为请求码
            }else{
    
    
                call();
            }
        }
    });
}

private void call(){
    
    
    try {
    
    
        Intent intent = new Intent(Intent.ACTION_CALL);
        intent.setData(Uri.parse("tel:10086"));
        startActivity(intent);
    }catch (SecurityException e){
    
    
        e.printStackTrace();
    }
}

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    
    
    switch (requestCode){
    
    
        case 1:
            if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
    
    
                call();
            }else{
    
    
                Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show();
            }
            break;
        default:
    }
}

访问其他程序中的数据

通过其他程序提供的内容提供器访问其数据

ContentResolver类,这个类提供了一系列的CRUD操作

这个类接受一个uri参数,由authority和path组成

比如同一程序中有两个表,它对应的uri为:

content://com.example.app.provider/table1

content://com.example.app.provider/table2

content表示对应的协议

使用方法除了是参数是内容uri之外,其他的和SQLiteDatabase的操作差不多

创建自己的内容提供器

创建自己的内容提供器可以新建一个类去继承ContentProvider,该类中有6个抽象方法

  • onCreate:初始化内容提供器的时候调用,返回true则代表初始化成功
  • query:返回的为Cursor
  • insert:
  • update:
  • delete:
  • getType:返回MIME类型,

一个标准的内容URI写法如下:

content://com.example.app.provider/table1
# 但是后面还可以跟id来具体到某一条数据
content://com.example.app.provider/table1/1
# 则代表id为1的那一条数据

还可以使用通配符来构成URI。Uri.parse()可以将字符串形式,解析成Uri对象

content://com.example.app.provider/*

*匹配任意长度的字符,该uri表示可以匹配任何表

content://com.example.app.provider/table1/#

#匹配任意长度的数字,该uri表示可以匹配任何单项

// 四大组件有一个共同点就是要在AndroidManifest.xml中进行注册
// 在<provider>标签中进行注册 
// 内容提供器是根据外来程序传进来的URI进行数据库操作,所以在创建内容提供器的时候要创建一个DatabaseHelper用于之后连接数据库进行操作
public class MyProvider extends ContentProvider{
    
    
    public static final int TABLE1_DIR = 0;
    public static final int TABLE1_ITEM = 1;
    private static UriMatcher uriMatcher;
    private MyDatabaseHelper dbHelper;
    static{
    
    
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI("com.example.app.provider", "table1", TABLE1_DIR);
        uriMatcher.addURI("com.example.app.provider", "table1/#", TABLE1_ITEM);
    }
    
    public boolean onCreate(){
    
    
        dbHelper = new MydataHelper(getContext(), "BookStore.db", null, 2);
        return true;
    }
    
    public Cursor query(Uri uri, String[] project. String selection, String[] selectionArgs, String sortOrder){
    
    
        SQLiteDatabase db = dbHelper.getReadableDatabase();
        Cursor cursor = null;
        // 通过提供可以访问的uri来对可访问内容进行控制
        switch(uriMatcher.match(uri)){
    
     // 根据提供的uri进行匹配执行对应的操作
            case TABLE1_DIR:
                // query table1表中的所有数据
                break;
            case TABLE1_ITEM:
                // 通过解析Uri参数获取uri中的id值
                String bookId = Uri.getPathSegments().get(1);
                cursor = db.query("Book", projection, "id=?",new String[] {
    
    bookId},null,null,sortOrder);
                // query table1表中的单条数据
                break;
            default:
                break;
        }
    }
}

Android手机多媒体

通知(Notification)

就是下拉栏那一条条QQ消息提醒,新闻推送之类的

通知既可以在活动中创建(没啥必要毕竟通知主要是为了程序在后台运行时进行通知),也可以在广播接收器和服务中创建

NotificationManager manager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
// 新建一个manager来管理通知
// 新建一个notification,并对其内容进行设置,最后使用build进行创建
Notification notification = new NotificationCompat.Builder(this)
        .setContentTitle("This is content title")
        .setContentText("This is content text")
        .setWhen(System.currentTimeMillis())
        .setSmallIcon(R.mipmap.ic_launcher)
        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
        .build();
manager.notify(1, notification);// 显示通知

以上只能显示出通知,但是点击之后没有反应。需要借助PendingIntent类来实现打开一个intent

Intent intent = new Intent(this, NotificationActivity.class);
PendingIntent pi = PendingIntent.getActivity(this,0,intent,0);
// getBroadcast(), getService()
NotificationManager manager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
Notification notification = new NotificationCompat.Builder(this)
        .setContentTitle("This is content title")
        .setContentText("This is content text")
        .setWhen(System.currentTimeMillis())
        .setSmallIcon(R.mipmap.ic_launcher)
        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
    	.setContentIntent(pi)
    // 这个接收一个pendingintent作为参数表示点击之后打开一个活动(广播,服务)
    	.setAutoCancel(true)
    // 这个表示在点击通知栏之后会自动取消通知栏上的那个图标,也可以通过manager.cancel将显示通知时设置的id作为参数传入进行取消
        .build();
manager.notify(1, notification);

进行通知设置的时候还可以setSound,setLights,setVibrate(震动)

还可以在通知栏中显示长文字和大图片,通过setStyle方法,比如设置大图片:

.setStyle(new NotificationCompat.BigPictureStyle().bigPicture(BitmapFactory.decodeResource(getResources(), R.drawable.big_image)))

还可以设置通知的优先级,一共有5个常量值可选:

  • PRIORITY_DEFAULT表示默认的重要程度,和不设置效果是一样的;
  • PRIORITY_MIN表示最低的重要程度,系统可能只会在特定的场景才显示这条通知,比如用户下拉状态栏的时候;
  • PRIORITY_LOW表示较低的重要程度,系统可能会将这类通知缩小,或改变其显示的顺序,将其排在更重要的通知之后;
  • PRIORITY_HIGH表示较高的重要程度,系统可能会将这类通知放大,或改变其显示的顺序,将其排在比较靠前的位置;
  • PRIORITY_MAX表示最高的重要程度,这类通知消息必须要让用户立刻看到,甚至需要用户做出响应操作(QQ消息就是这种)
.setPriority(NotificationCompat.PRIORITY_MAX)

调用摄像头和相册

调用摄像头

就是传递一个intent开启一个活动

Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);// imageUri表示拍摄的图片File所转成的Uri
startActivityForResult(intent, TAKE_PHOTO);

打开相册

传递一个intent

Intent intent = new Intent("android.intent.action.GET_CONTENT");
intent.setType("image/*");
startActivityForResult(intent, CHOOSE_PHOTO);

播放多媒体文件

播放音频文件

在这里插入图片描述

在一首歌播放完的时候应该调用的是reset方法。stop方法被调用后MediaPlayer将无法在播放音频

播放视频文件

与播放音频类似有一个对应的类:VideoView

在这里插入图片描述

使用网络技术

WebView的用法

WebView控件(Button也是一种控件)类似于一个小小的浏览器

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
	
    <!--在布局文件中进行定义 -->
    <WebView
        android:id="@+id/web_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>
protected void onCreate(Bundle savedInstanceState) {
    
    
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    // 获取Webview实例
    WebView webView = (WebView) findViewById(R.id.web_view);
    // getSettings()方法可以去设置一些浏览器的属性
    webView.getSettings().setJavaScriptEnabled(true);
    // 表示需要从一个网页跳转到另一个网页时,使目标网页仍在当前WebView中显示,而不是打开系统浏览器
    webView.setWebViewClient(new WebViewClient());
    webView.loadUrl("http://www.baidu.com");
}

使用HTTP协议访问网络

WebView对网络请求进行了较好的封装。现在使用HttpURLConnection来手动进行网络访问

使用HttpURLConnection

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    
    
    TextView responseText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button sendRequest = (Button) findViewById(R.id.send_request);
        responseText = (TextView) findViewById(R.id.response_text);
        sendRequest.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
    
    
        if(view.getId()==R.id.send_request){
    
    
            sendRequestWithHttpURIConnection();
        }
    }

    private void sendRequestWithHttpURIConnection(){
    
    
        new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                HttpURLConnection connection = null;
                BufferedReader reader = null;
                try {
    
    
                    URL url = new URL("https://www.baidu.com");
                    connection = (HttpURLConnection) url.openConnection();
                    connection.setRequestMethod("GET");
                    connection.setConnectTimeout(8000);
                    connection.setReadTimeout(8000);
                    InputStream in = connection.getInputStream();
                    reader = new BufferedReader(new InputStreamReader(in));
                    StringBuilder response = new StringBuilder();
                    String line;
                    while((line = reader.readLine())!=null){
    
    
                        response.append(line);
                    }
                    showResponse(response.toString());
                } catch (MalformedURLException e) {
    
    
                    e.printStackTrace();
                } catch (IOException e) {
    
    
                    e.printStackTrace();
                }finally {
    
    
                    if (reader != null){
    
    
                        try {
    
    
                            reader.close();
                        } catch (IOException e) {
    
    
                            e.printStackTrace();
                        }
                    }
                    if (connection != null){
    
    
                        connection.disconnect();
                    }
                }
            }
        }).start();
    }
    private void showResponse(final String response){
    
    
        // Android是不允许在子线程中进行UI操作的,需要通过这个方法将线程切换到主线程,然后再更新UI元素
        runOnUiThread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                responseText.setText(response);
            }
        });
    }
}

使用OkHttp开源库

public void run() {
    
    
    try {
    
    
        OkHttpClient client = new OkHttpClient();
        // 创建request来发起http请求
        Request request = new Request.Builder()
                .url("http://www.baidu.com")
                .build();
        // 发送请求并接收响应数据
        Response response = client.newCall(request).execute();
        String responseData = response.body().toString();
        showResponse(responseData);
    } catch (IOException e) {
    
    
        e.printStackTrace();
    }
}



// 如果是发起post请求,先要构造好POST数据内容,之后构造好请求内容,之后再发送出去
RequestBody requestBody = new FormBody.Builder()
    		.add("username", "admin")
    		.add("password", "123456")
    		.build();

Request request = new Request.Builder()
    		.url("http://www.baidu.com")
    		.post(requestBody)
    		.build();

解析XML格式数据

<apps>
	<app>
    	<id>1</id>
        <name>Google Maps</name>
        <version>1.0</version>
    </app>
    <app>
    	<id>2</id>
        <name>Chrome</name>
        <version>2.1</version>
    </app>
    <app>
    	<id>3</id>
        <name>Google Play</name>
        <version>2.3</version>
    </app>
</apps>

使用pull方式

private void parseXMLWithPull(String xmlData) {
    
    
    try {
    
    
        XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
        XmlPullParser xmlPullParser = factory.newPullParser();
        xmlPullParser.setInput(new StringReader(xmlData));
        int eventType = xmlPullParser.getEventType();
        String id = "";
        String name = "";
        String version = "";
        while (eventType != XmlPullParser.END_DOCUMENT) {
    
    
            String nodeName = xmlPullParser.getName();
            switch (eventType) {
    
    
                // 开始解析某个结点, xml以树状的形式存储,每个app是一个节点
                case XmlPullParser.START_TAG: {
    
    
                    if ("id".equals(nodeName)) {
    
    
                        id = xmlPullParser.nextText();
                    } else if ("name".equals(nodeName)) {
    
    
                        name = xmlPullParser.nextText();
                    } else if ("version".equals(nodeName)) {
    
    
                        version = xmlPullParser.nextText();
                    }
                    break;
                }
                // 完成解析某个结点
                case XmlPullParser.END_TAG: {
    
    
                    if ("app".equals(nodeName)) {
    
    
                        Log.d("MainActivity", "id is " + id);
                        Log.d("MainActivity", "name is " + name);
                        Log.d("MainActivity", "version is " + version);
                    }
                    break;
                }
                default:
                    break;
            }
            eventType = xmlPullParser.next();
        }
    } catch (Exception e) {
    
    
        e.printStackTrace();
    }
}

使用SAX方式

public class ContentHandler extends DefaultHandler {
    
    

    private String nodeName;

    private StringBuilder id;

    private StringBuilder name;

    private StringBuilder version;

    @Override// 开始解析xml文件时调用的方法
    public void startDocument() throws SAXException {
    
    
        id = new StringBuilder();
        name = new StringBuilder();
        version = new StringBuilder();
    }

    @Override// 开始解析某个节点时调用的方法
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
    
    
        // 记录当前结点名
        nodeName = localName;
    }

    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
    
    
        // 根据当前的结点名判断将内容添加到哪一个StringBuilder对象中
        if ("id".equals(nodeName)) {
    
    
            id.append(ch, start, length);
        } else if ("name".equals(nodeName)) {
    
    
            name.append(ch, start, length);
        } else if ("version".equals(nodeName)) {
    
    
            version.append(ch, start, length);
        }
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
    
    
        if ("app".equals(localName)) {
    
    
            Log.d("ContentHandler", "id is " + id.toString().trim());
            Log.d("ContentHandler", "name is " + name.toString().trim());
            Log.d("ContentHandler", "version is " + version.toString().trim());
            // 最后要将StringBuilder清空掉
            id.setLength(0);
            name.setLength(0);
            version.setLength(0);
        }
    }

    @Override
    public void endDocument() throws SAXException {
    
    
        super.endDocument();
    }

}

解析JSON格式数据

[{
    
    "id":"5", "version":"5.5","name":"Clash of Clans"},
 {
    
    "id":"6", "version":"7.0","name":"Boom Beach"},
 {
    
    "id":"7","version":"3.5","name":"Clash Royale"}]

使用JSONObject

private void parseJSONWithJSONObject(String jsonData) {
    
    
    try {
    
    
        JSONArray jsonArray = new JSONArray(jsonData);
        for (int i = 0; i < jsonArray.length(); i++) {
    
    
            JSONObject jsonObject = jsonArray.getJSONObject(i);
            String id = jsonObject.getString("id");
            String name = jsonObject.getString("name");
            String version = jsonObject.getString("version");
            Log.d("MainActivity", "id is " + id);
            Log.d("MainActivity", "name is " + name);
            Log.d("MainActivity", "version is " + version);
        }
    } catch (Exception e) {
    
    
        e.printStackTrace();
    }
}

使用GSON

可以将一段JSON格式的字符串自动映射成一个对象。如果需要解析的是一段JSON数组会稍微麻烦一点,需要借助TypeToken将期望解析成的数据类型传入到fromJson()方法中。

使用Gson解析上面的json数据

首先新建一个类

package com.example.networktest;

public class App {
    
    

    private String id;

    private String name;

    private String version;

    public String getId() {
    
    
        return id;
    }

    public void setId(String id) {
    
    
        this.id = id;
    }

    public String getName() {
    
    
        return name;
    }

    public void setName(String name) {
    
    
        this.name = name;
    }

    public String getVersion() {
    
    
        return version;
    }

    public void setVersion(String version) {
    
    
        this.version = version;
    }
}
private void parseJSONWithGSON(String jsonData) {
    
    
    Gson gson = new Gson();
    List<App> appList = gson.fromJson(jsonData, new TypeToken<List<App>>() {
    
    }.getType());
    for (App app : appList) {
    
    
        Log.d("MainActivity", "id is " + app.getId());
        Log.d("MainActivity", "name is " + app.getName());
        Log.d("MainActivity", "version is " + app.getVersion());
    }
}

Android异步消息处理机制

  • Message(消息的载体)

Message是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据。上一小节中我们使用到了Message的what字段,除此之外还可以使用arg1和arg2字段来携带一些整型数据,使用obj字段携带一个Object对象。

  • Handler

主要是用于发送处理消息的。发送消息一般是使用Handler的sendMessage()方法,而发出的消息经过一系列地辗转处理后,最终会传递到Handler的handleMessage()方法中。

  • MessageQueue

主要用于存放所有通过Handler发送的消息。这部分消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个MessageQueue对象。

  • Looper

Looper是每个线程中的MessageQueue的管家,调用Looper的loop()方法后,就会进入到一个无限循环当中,然后每当发现MessageQueue中存在一条消息,就会将它取出,并传递到Handler的handleMessage()方法中。每个线程中也只会有一个Looper对象。

服务

服务并不是运行在一个独立的进程当中的,而是依赖于创建服务时所在的应用程序进程。当某个应用程序进程被杀掉时,所有依赖于该进程的服务也会停止运行。

基本用法

启动和停止服务

startService与stopService分别对应启动和停止服务。与启动活动类似,首先要构建一个Intent对象,再将其作为参数传入对应的方法当中。

  • onCreate()方法会在服务创建的时候调用
  • onStartCommand()方法会在每次服务启动的时候调用
  • onDestroy()方法会在服务销毁的时候调用

活动与服务之间进行通信

onBind()-------bind(绑定)

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
    
    private MyService.DownloadBinder downloadBinder;
    private ServiceConnection connection = new ServiceConnection() {
    
    
        @Override
        // 在服务与活动成功绑定时进行调用
        public void onServiceConnected(ComponentName componentName, IBinder service) {
    
    
            downloadBinder = (MyService.DownloadBinder) service;
            downloadBinder.startDownload();
            downloadBinder.getProgress();
        }
		
        // 在解绑时进行调用
        @Override
        public void onServiceDisconnected(ComponentName componentName) {
    
    

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button startService = (Button) findViewById(R.id.start_service);
        Button stopService = (Button) findViewById(R.id.stop_service);
        startService.setOnClickListener(this);
        stopService.setOnClickListener(this);
        Button bindService = (Button) findViewById(R.id.bind_service);
        Button unbindService = (Button) findViewById(R.id.unbind_service);
        bindService.setOnClickListener(this);
        unbindService.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
    
    
        switch (view.getId()) {
    
    
            case R.id.start_service:
                Intent startIntent = new Intent(this, MyService.class);
                startService(startIntent);
                break;
            case R.id.stop_service:
                Intent stopIntent = new Intent(this, MyService.class);
                stopService(stopIntent);
                break;
            case R.id.bind_service:
                Intent bindIntent = new Intent(this, MyService.class);
                // 通过调用bindService进行绑定,最后一个参数表示在连接成功时自动进行服务创建,但是没有启动服务。
                // bindIntent表示绑定那个服务,connetion可以在其中设置连接时调用的方法。
                bindService(bindIntent, connection, BIND_AUTO_CREATE);
                break;
            case R.id.unbind_service:
                unbindService(connection);
                break;
            default:
                break;
        }
    }
}
public class MyService extends Service {
    
    
    private DownloadBinder mBinder = new DownloadBinder();

    class DownloadBinder extends Binder {
    
    
        public void startDownload(){
    
    
            Log.d("MyService", "startDownload executed");
        }
        public int getProgress(){
    
    
            Log.d("MyService", "getProgress executed");
            return 0;
        }
    }

    public MyService() {
    
    
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
    
    
        Log.d("MyService", "onStartCommand executed");
        return super.onStartCommand(intent, flags, startId);

    }

    @Override
    public void onDestroy() {
    
    
        super.onDestroy();
        Log.d("MyService", "onDestroy executed");
    }

    @Override
    public void onCreate() {
    
    
        super.onCreate();
        Log.d("MyService", "onCreate executed");
    }

    @Override
    public IBinder onBind(Intent intent) {
    
    
        // TODO: Return the communication channel to the service.
       return mBinder;
    }
}

以上面的程序为例:

任何一个服务在整个应用程序范围内都是通用的,即MyService不仅可以和MainActivity绑定,还可以和任何一个其他的活动进行绑定,而且在绑定完成后它们都可以获取到相同的DownloadBinder实例

生命周期

虽然每调用一次startService()方法,onStartCommand()就会执行一次,但实际上每个服务都只会存在一个实例。所以不管你调用了多少次startService()方法,只需调用一次stopService()或stopSelf()方法,服务就会停止下来了

Android系统的机制,一个服务只要被启动或者被绑定了之后,就会一直处于运行状态,必须要让以上两种条件同时不满足,服务才能被销毁。所以,这种情况下要同时调用stopService()和unbindService()方法,onDestroy()方法才会执行

前台服务

服务的系统优先级还是比较低的,当系统出现内存不足的情况时,就有可能会回收掉正在后台运行的服务。如果你希望服务可以一直保持运行状态,而不会由于系统内存不足的原因导致被回收,就可以考虑使用前台服务

使用IntentService

应该在服务的每个具体的方法里开启一个子线程,然后在这里去处理那些耗时的逻辑。

服务一旦启动之后,就会一直处于运行状态,必须调用stopService()或者stopSelf()方法才能让服务停止下来。

为了解决程序员可能忘记上述两种情况,Android提供了IntentService

 switch (view.getId()) {
        case R.id.start_service:
            Intent startIntent = new Intent(this, MyService.class);
            startService(startIntent);
            break;
        case R.id.stop_service:
            Intent stopIntent = new Intent(this, MyService.class);
            stopService(stopIntent);
            break;
        case R.id.bind_service:
            Intent bindIntent = new Intent(this, MyService.class);
            // 通过调用bindService进行绑定,最后一个参数表示在连接成功时自动进行服务创建,但是没有启动服务。
            // bindIntent表示绑定那个服务,connetion可以在其中设置连接时调用的方法。
            bindService(bindIntent, connection, BIND_AUTO_CREATE);
            break;
        case R.id.unbind_service:
            unbindService(connection);
            break;
        default:
            break;
    }
}

}


```java
public class MyService extends Service {
    private DownloadBinder mBinder = new DownloadBinder();

    class DownloadBinder extends Binder {
        public void startDownload(){
            Log.d("MyService", "startDownload executed");
        }
        public int getProgress(){
            Log.d("MyService", "getProgress executed");
            return 0;
        }
    }

    public MyService() {
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("MyService", "onStartCommand executed");
        return super.onStartCommand(intent, flags, startId);

    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("MyService", "onDestroy executed");
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("MyService", "onCreate executed");
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
       return mBinder;
    }
}

以上面的程序为例:

任何一个服务在整个应用程序范围内都是通用的,即MyService不仅可以和MainActivity绑定,还可以和任何一个其他的活动进行绑定,而且在绑定完成后它们都可以获取到相同的DownloadBinder实例

生命周期

虽然每调用一次startService()方法,onStartCommand()就会执行一次,但实际上每个服务都只会存在一个实例。所以不管你调用了多少次startService()方法,只需调用一次stopService()或stopSelf()方法,服务就会停止下来了

Android系统的机制,一个服务只要被启动或者被绑定了之后,就会一直处于运行状态,必须要让以上两种条件同时不满足,服务才能被销毁。所以,这种情况下要同时调用stopService()和unbindService()方法,onDestroy()方法才会执行

前台服务

服务的系统优先级还是比较低的,当系统出现内存不足的情况时,就有可能会回收掉正在后台运行的服务。如果你希望服务可以一直保持运行状态,而不会由于系统内存不足的原因导致被回收,就可以考虑使用前台服务

使用IntentService

应该在服务的每个具体的方法里开启一个子线程,然后在这里去处理那些耗时的逻辑。

服务一旦启动之后,就会一直处于运行状态,必须调用stopService()或者stopSelf()方法才能让服务停止下来。

为了解决程序员可能忘记上述两种情况,Android提供了IntentService

猜你喜欢

转载自blog.csdn.net/weixin_46287316/article/details/129077741