Android入门知识全套笔记

安装apk:

开发完成之后,需要buildAPK,再次发送才能运行。

adb install -r (apk完整路径) 安装在第三方app

系统级app需要把打包好的apk,浦西

卸载apk:adb uninstall (apk包名)

卸载app但保留数据和缓存文件:adb uninstall -k (apk包名)

列出手机装的所有app包名:adb shell pm list packages

线性布局:LinearLayout

在这里插入图片描述

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

    <LinearLayout
        android:id="@+id/tzs_1"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:orientation="vertical"
        android:background="#000000"
        android:paddingLeft="20dp"
        android:paddingRight="20dp"
        android:paddingTop="20dp"
        android:paddingBottom="20dp"
        android:layout_marginBottom="20dp">

        <View
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#FF0033">
        </View>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:background="#0066FF"
        android:orientation="horizontal"
        android:layout_marginLeft="15dp"
        android:layout_marginRight="15dp">

        <View
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:background="#000000"
            android:layout_weight="1">
        </View>
        <View
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:background="#FF0033"
            android:layout_weight="1">
        </View>
        <View
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:background="#55AA99"
            android:layout_weight="1">
        </View>
	</LinearLayout>
    
</LinearLayout>

在这里插入图片描述

针对于不同的地方使用不同的gravity,其效果不同。

<?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"
    android:gravity="center_vertical|left">
    <!--android:gravity:规范整个布局内的元素进行规范-->
    <TextView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_gravity="right"
        android:gravity="left|bottom"
        android:text="@string/tv_test1" />
    <!--android:layout_gravity:可以进行让布局内元素改变
        android:gravity:规范字体的布局-->
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="第二行"/>
</LinearLayout>

相对布局:RelativeLayout

在这里插入图片描述

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

    <View
        android:id="@+id/view_1"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="#000000"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"/>

    <View
        android:id="@+id/view_2"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="#00ff00"绿/>

    <View
        android:id="@+id/view_3"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="#FF0033"
        android:layout_below="@id/view_2"/>

    <LinearLayout
        android:id="@+id/view_4"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:layout_below="@+id/view_3"
        android:background="#0066FF"
        android:orientation="horizontal"
        android:padding="15dp">

        <View
            android:layout_width="100dp"
            android:layout_height="match_parent"
            android:background="#FF0033"/>
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#000000"
            android:padding="15dp">

            <View
                android:id="@+id/view_5"
                android:layout_width="100dp"
                android:layout_height="match_parent"
                android:background="#FF9900"/>

            <View
                android:id="@+id/view_6"
                android:layout_width="100dp"
                android:layout_height="match_parent"
                android:layout_marginLeft="10dp"
                android:layout_toRightOf="@id/view_5"
                android:background="#FF9900"/>

        </RelativeLayout>

    </LinearLayout>


</RelativeLayout>

TextView:

文字大小、颜色

显示不下使用…

文字+icon

中划线、下划线

跑马灯

在这里插入图片描述
在这里插入图片描述
项目中新添加一个TextViewActivity

设置一个Button按钮—activity_main.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:id="@+id/btn_textview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="TextView"/>

</LinearLayout>

MainActivity:

public class MainActivity extends AppCompatActivity {

    //1.申明Button
    private Button mBtnTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //2.寻找到Button
        mBtnTextView = findViewById(R.id.btn_textview);
        //3.设置点击事件
        mBtnTextView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //跳转到TextView演示界面
                Intent intent = new Intent(MainActivity.this, TextViewActivity.class);
                startActivity(intent);
            }
        });
     }
}

activity_text_view.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"
    android:padding="20dp">
    <TextView
        android:id="@+id/tv_1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/tv_test1"
        android:textColor="#000000"
        android:textSize="24sp"/>

    <TextView
        android:id="@+id/tv_2"
        android:layout_width="120dp"
        android:layout_height="wrap_content"
        android:maxLines="1"
        android:ellipsize="end"
        android:text="@string/tv_test1"
        android:textColor="#000000"
        android:textSize="24sp"
        android:layout_marginTop="10dp"/>

    <TextView
        android:id="@+id/tv_3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:drawablePadding="5dp"
        android:drawableRight="@drawable/move_down"
        android:text="筛选"
        android:textColor="#000000"
        android:textSize="24sp" />

    <TextView
        android:id="@+id/tv_4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/tv_test1"
        android:layout_marginTop="10dp"
        android:textColor="#000000"
        android:textSize="24sp" />

    <TextView
        android:id="@+id/tv_5"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/tv_test1"
        android:layout_marginTop="10dp"
        android:textColor="#000000"
        android:textSize="24sp"/>

    <TextView
        android:id="@+id/tv_6"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:textColor="#000000"
        android:textSize="24sp" />

    <TextView
        android:id="@+id/tv_7"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="谭章竦在奔跑谭章竦在奔跑谭章竦在奔跑谭章竦在奔跑谭章竦在奔跑"
        android:layout_marginTop="10dp"
        android:textColor="#000000"
        android:textSize="24sp"
        android:singleLine="true"
        android:ellipsize="marquee"
        android:marqueeRepeatLimit="marquee_forever"
        android:focusable="true"
        android:focusableInTouchMode="true"
        android:clickable="true"/>

</LinearLayout>

TextViewActivity:

public class TextViewActivity extends AppCompatActivity {

    //1.申明textView
    //  设置中划线
    private TextView mTv4;
    //  设置下划线
    private TextView mTv5;
    //  设置下划线
    private TextView mTv6;
    //  设置跑马灯
    private TextView mTv7;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_text_view);
        mTv4 = findViewById(R.id.tv_4);
        //设置中划线
        mTv4.getPaint().setFlags(Paint.STRIKE_THRU_TEXT_FLAG);
        //去除线锯齿
        mTv4.getPaint().setAntiAlias(true);

        //设置下划线
        mTv5 = findViewById(R.id.tv_5);
        mTv5.getPaint().setFlags(Paint.UNDERLINE_TEXT_FLAG);

        //设置下划线
        mTv6 = findViewById(R.id.tv_6);
        mTv6.setText(Html.fromHtml("<u>谭章竦学习中</u>"));

        //设置跑马灯
        mTv7 = findViewById(R.id.tv_7);
        mTv7.setSelected(true);
    }
}

UI控件跳转与显示点击

实现跳转和显示点击都有两种方式:

①实现接口implements View.OnClickListener,重写onClick方法

②匿名内部类new View.OnClickListener()

//匿名内部类,目前局限为只能设置一个事件。
public class MainActivity extends AppCompatActivity {

    //1.申明图片或按钮变量
    private ImageView ivQq;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_lldemo2);
        
        //2.寻找到图片或按钮,给变量赋值
        ivQq = findViewById(R.id.image_1);
        //	修改图片
        ivQq.setImageResource(R.drawable.move_down);
        //3.设置点击显示语句事件
        ivQq.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
            	//跳转到TextView演示界面
                Intent intent = new Intent(MainActivity.this, TextViewActivity.class);
                startActivity(intent);
            }
        });
    }
}
public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    //1.创建一个控件的对象作为成员变量
    private ImageView ivQq;
    private ImageView ivWeibo,ivWechat,ivSale;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_lldemo2);

        //2.寻找到图片或按钮,给变量赋值
        ivQq = findViewById(R.id.image_1);
        ivWeibo = findViewById(R.id.image_2);
        ivWechat = findViewById(R.id.image_3);
        ivSale = findViewById(R.id.image_4);
		//4.将控件和接口利用setOnClickListener绑定
        ivQq.setOnClickListener(this);
        ivWeibo.setOnClickListener(this);
        ivWechat.setOnClickListener(this);
        ivSale.setOnClickListener(this);
    }

    //3.实现OnClick Listener的接口,重写onClick方法
    @Override
    public void onClick(View view) {
        //单个直接设置
        //Toast.makeText(MainActivity.this, "点击了QQ", Toast.LENGTH_SHORT);
        switch (view.getId()){
            case R.id.image_1:
                //Activity是context的子类,所以可以直接this
                Toast.makeText(MainActivity.this, "点击了QQ", Toast.LENGTH_SHORT).show();
                break;
            case R.id.image_2:
                Toast.makeText(MainActivity.this, "点击了微博", Toast.LENGTH_SHORT).show();
                break;
            case R.id.image_3:
                Toast.makeText(MainActivity.this, "点击了微信", Toast.LENGTH_SHORT).show();
                break;
            case R.id.image_4:
                Toast.makeText(MainActivity.this, "点击了Sale", Toast.LENGTH_SHORT).show();
                break;
        }
    }
}

排查运行错误最快方式:在这里插入图片描述

实现登录页面与圆角背景

在这里插入图片描述
res—drawable—创建sharp_bg.xml(渐变圆角矩形模板)

<?xml version="1.0" encoding="utf-8"?>
    <!--shape设置形状-->
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
    <!--填充色-->
    <solid android:color="@color/design_default_color_primary"/>
    <!--边框的颜色,粗细-->
    <stroke android:color="@color/black" android:width="3dp"/>
    <!--圆角的弧度-->
    <corners android:radius="20dp"/>
    <!--紫白渐变色,设置会覆盖填充色-->
    <gradient android:startColor="@color/white" android:endColor="@color/purple_200"/>
</shape>

activity_main.xml

<?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"
    android:gravity="center"
    android:padding="20dp">
    <ImageView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:src="@mipmap/ruantong" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:layout_marginTop="20dp"
        android:orientation="vertical"
        android:background="@drawable/sharp_bg">

        <!--        <ImageView-->
        <!--            android:layout_width="wrap_content"-->
        <!--            android:layout_height="wrap_content"-->
        <!--            android:layout_margin="10dp"-->
        <!--            android:src=""-->

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center_vertical">

            <ImageView
                android:layout_width="20dp"
                android:layout_height="20dp"
                android:layout_margin="10dp"
                android:src="@mipmap/user" />

<!--            android:textColor="10sp"-->
            <EditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="请输入用户名:"
                android:background="@null" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="@color/black">

        </LinearLayout>
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center_vertical"
            android:orientation="horizontal">

            <ImageView
                android:layout_width="20dp"
                android:layout_height="20dp"
                android:layout_margin="10dp"
                android:src="@mipmap/pwd"/>

            <EditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="请输入密码:"
                android:background="@null"/>
        </LinearLayout>
    </LinearLayout>


    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="忘记密码?"
        android:layout_gravity="right"
        android:layout_margin="10dp"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="立即登录"
        android:background="@drawable/sharp_bg"
        android:gravity="center"
        android:textColor="@color/white"
        android:textSize="15dp"/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="没有账号?立即注册账号"
        android:gravity="center"
        android:padding="15dp"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:layout_marginTop="10dp">
        <View
            android:layout_width="0dp"
            android:layout_height="1dp"
            android:layout_weight="1"
            android:background="@color/black"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="其他账号登录"
            android:layout_gravity="center"/>
        <View
            android:layout_width="0dp"
            android:layout_height="1dp"
            android:layout_weight="1"
            android:background="@color/black"/>
    </LinearLayout>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp">

        <ImageView
            android:id="@+id/qq_login"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:layout_centerHorizontal="true"
            android:src="@mipmap/tengxun" />

        <ImageView
            android:id="@+id/weibo_login"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toLeftOf="@+id/qq_login"
            android:layout_centerHorizontal="true"
            android:layout_marginRight="20dp"
            android:src="@mipmap/weibo"/>

        <ImageView
            android:id="@+id/weixin_login"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toRightOf="@+id/qq_login"
            android:layout_centerHorizontal="true"
            android:layout_marginLeft="20dp"
            android:src="@mipmap/weixin"/>
    </RelativeLayout>

</LinearLayout>

实现圆角按钮,按压变色、效果

res—drawable—创建selector_btn.xml(不按压时红色填充黑色边框,按压时紫色填充黑色边框,不可点击时白色填充)

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

    <!--按压效果-->
    <item android:state_pressed="true">
        <shape>
            <solid android:color="#FF00FF"/>
            <stroke android:color="@color/black" android:width="3dp"/>
            <corners android:radius="20dp"/>
        </shape>
    </item>
    <!--不可点击时效果-->
    <item android:state_enabled="false">
        <shape>
            <solid android:color="#FFFFFF"/>
        </shape>
    </item>
    <!--默认效果-->
    <item>
        <shape>
            <!--填充色-->
            <solid android:color="#FF0000"/>
            <!--边框的颜色,粗细-->
            <stroke android:color="@color/black" android:width="3dp"/>
            <!--圆角的弧度-->
            <corners android:radius="20dp"/>
        </shape>
    </item>
</selector>

layout—activity_main.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:gravity="center">

        <Button
            android:id="@+id/btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!"
            android:textSize="20dp"
            android:background="@drawable/selector_btn"/>

</LinearLayout>

MainActivity,实现不可点击时效果

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Button button;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        button = findViewById(R.id.btn);
        button.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        button.setEnabled(false);
    }
}

注意:res—values—themes.xml,需要进行更改。

默认为:
<style name="Theme.AdapterDemo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
    
修改为:
<style name="Theme.UiDemo2" parent="Theme.MaterialComponents.DayNight.NoActionBar.Bridge">

实现国际化

在Android,values下创建strings.xml(中国图标)文件
在这里插入图片描述

strings.xml(中国图标)

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">UI示例</string>
    <string name="title">谭章竦</string>
</resources>

strings.xml

<resources>
    <string name="app_name">UiDemo2</string>
    <string name="title">Shroud</string>
</resources>

ArrayaAdapter与简单的ListView组合

在这里插入图片描述

activity_main.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">

    <ListView
        android:id="@+id/lv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
    </ListView>

</LinearLayout>

MainActivity

public class MainActivity extends AppCompatActivity {
    //1.申明
    private ListView listView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //要显示的数据列表
        String[] strings = {"篮球","足球","排球","棒球"};
        //引用SDK自带的列表效果:android.;引用本地布局R.
        //SDK不光提供了许多jar包,还提供了很多工具、xml、布局、图片,一个大的开发包
        ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,strings);

        //2.获取ListView对象,
        listView = findViewById(R.id.lv);
        //3.通过调用setAdapter方法为ListView设置Adapter适配器
        // 传入ListAdapter,arrayAdapter是它的子类(只能展示一行文字)
        listView.setAdapter(arrayAdapter);
    }
}

BaseAdapter抽象类

//from(类方法)静态方法;	inflate是LayoutInflater对象方法
//三个参数:
//	1、子布局	2、给子布局指定一个根视图	3、是否加载到根视图
convertView = LayoutInflater.from(context).inflate(R.layout.list_layout, parent, false);

inflate的三个参数:

1.如果root为null,attach ToRoot将失去作用,设置任何值都没有意义。layoutId的最外层的控件(R.layout.list_layout)的宽高是没有效果的

2.如果root不为null,attach ToRoot设为tue,则会给加载的布局文件的指定一个父布局。即root。会调用 addView(View, LayoutParams)的方法。当Adapter和fragment有自己的类似addView的实现的时候,不能设置该值为true,

否则报异常:android.view.InflateException: Binary XML file line #2: addView(View, LayoutParams) is not supported in AdapterView;

3.如果root不为null,attach ToRoot设为false。则会将布局文件最外层的所有layout属性进行设置。当该view被添加到父View当中时,这些layout属性会自动生效。设置的布局参数会生效!

4.在不设置attach ToRoot参数的情况下,如果root不为nulls,attach ToRoot参数默认为true。

注意点:

1、如果知道root(parent),一定要传,尽里避免传null

2、当不需要将布局文件生成的View添加到组件中时(组件有其自身的添加逻辑),attach ToRoot设置成false。

3、当View已经添加到一个root中时,attachToRoot设置成false.

4、自定义组件应该将attach ToRoot设置成true.

Android LayoutInflater

public abstract class LayoutInflater extends Object

在实际开发中LayoutInflater这个抽象类的作用,类似于findViewById()

不同点:

LayoutInflater是用来找动态加载res/layout/下的xml布局文件,并且实例化。

而findViewById()是找xml布局文件下的具体widget控件(如Button、TextView等)。

具体作用:

1、对于一个没有被载入或者想要动态载入的界面,都需要使用Layoutflater.inflate()来载入

2、对于一个已经载入的界面,就可以使用Activiyt.findViewById()方法来获得其中的界面元素。

获得LayoutInflater实例的三种方式:

LayoutInflater inflater = LayoutInflater.from(context);//在任何地方都能使用,Activity中使用this代替context
LayoutInflater inflater = getLayoutInflater();//调用Activity的getLayoutInflater()
LayoutInflater inflater = context.getSystemService;//前面两种方式,追溯底层都是它
	动态加载布局;.inflate()调用动态布局
	三个参数:子布局 父布局 是否附加到根部局上面

ListView点击事件的实现

在这里插入图片描述

activity_main.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">

    <ListView
        android:id="@+id/lv"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

list_layout.xml:

<?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"
    android:padding="20dp">

    <ImageView
        android:id="@+id/iv_icon"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:focusable="false"/>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:orientation="vertical">
        <TextView
            android:id="@+id/tv_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="30sp"/>
        <TextView
            android:id="@+id/tv_price"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="30sp"/>
    </LinearLayout>

</LinearLayout>

MainActivity:

public class MainActivity extends AppCompatActivity implements AdapterView.OnItemClickListener {

    private ListView listView;
    private List<Fruit> data;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        listView = findViewById(R.id.lv);
        data = new ArrayList<>();
        data.add(new Fruit(R.mipmap.apple,"苹果","3.5元/KG"));
        data.add(new Fruit(R.mipmap.orange,"橘子","6.8元/KG"));
        data.add(new Fruit(R.mipmap.mango,"芒果","5.8元/KG"));
        data.add(new Fruit(R.mipmap.strawberry,"草莓","6.5元/KG"));
        data.add(new Fruit(R.mipmap.banana,"香蕉","7.5元/KG"));
        data.add(new Fruit(R.mipmap.watermelon,"西瓜","4.3元/KG"));

        BaseAdapter baseAdapter = new optimizeBaseAdapter(this,data);
        listView.setAdapter(baseAdapter);

        //点击事件有关
        listView.setOnItemClickListener(this);
    }

    //点击事件有关
    @Override
    public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
        Toast.makeText(this,data.get(position).getName(),Toast.LENGTH_SHORT).show();
    }
}

optimizeBaseAdapter:

public class optimizeBaseAdapter extends BaseAdapter {

    //目的:与MainActivity关联上
    private Context context;
    //目的:把数据源进行绑定
    private List<Fruit> data;
    //private Layout layout;

    public optimizeBaseAdapter() {
    }

    public optimizeBaseAdapter(Context context, List<Fruit> list) {
        this.context = context;
        this.data = list;
    }

    @Override
    public int getCount() {
        return data.size();
    }

    @Override
    public Object getItem(int position) {
        return data.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    static class ViewHolder{
        ImageView ivIcon;
        TextView tvName;
        TextView tvPrice;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        ViewHolder viewHolder = null;

        if (convertView == null) {
            //动态加载布局;.inflate()调用动态布局
            //三个参数:子布局 父布局 是否附加到根部局上面
            //(子视图)加载给谁(root父布局/根视图)
            convertView = LayoutInflater.from(context).inflate(R.layout.list_layout, parent, false);
            viewHolder = new ViewHolder();
            viewHolder.ivIcon = convertView.findViewById(R.id.iv_icon);
            viewHolder.tvName = convertView.findViewById(R.id.tv_name);
            viewHolder.tvPrice = convertView.findViewById(R.id.tv_price);
            /*使用一种称为“ViewHolder”的技术,缓存convertView中的View组件,
             使用convertView的setTag()和getTag(),每次当ListView需要创建Item的View时候,
             调用convertView的getTag()找出ViewHolder,重新复用。具体的使用方法如代码*/
            convertView.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }

        viewHolder.ivIcon.setImageResource(data.get(position).getIcon());
        viewHolder.tvName.setText(data.get(position).getName());
        viewHolder.tvPrice.setText(data.get(position).getPrice());
        return convertView;
    }
}

listview内部控件的点击事件

实现效果:内部按钮、文字、图片点击效果。注意需要解决点击冲突。

MainActivity只能设置每个ListView的点击事件,对于单独的图片点击事件,获取不到数据,所以需要在optimizeBaseAdapter2中设置图片、内容的点击事件

activity_main.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">

    <ListView
        android:id="@+id/lv"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

list_layout.xml:

<?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"
    android:padding="20dp">

    <ImageView
        android:id="@+id/iv_icon"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:focusable="false"
        android:src="@mipmap/apple"/>
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="100dp"
        android:orientation="vertical"
        android:layout_weight="1">
        <TextView
            android:id="@+id/tv_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="30sp"
            android:text="苹果"/>
        <TextView
            android:id="@+id/tv_price"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="30sp"
            android:text="价格"/>
    </LinearLayout>
    <Button
        android:id="@+id/btn_buy"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="right"
        android:layout_marginRight="10dp"
        android:text="购买"
        android:textSize="20sp"
        android:focusable="false"/>
<!--    item根节点设置focusable,防止button于listView点击冲突,导致listView无效-->
</LinearLayout>

list_head.xml(list_foot.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:background="@color/cardview_shadow_start_color"
    android:gravity="center">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="表头"
        android:textSize="50sp" />

</LinearLayout>

MainActivity

public class MainActivity extends AppCompatActivity implements AdapterView.OnItemClickListener {

    private ListView listView;
    private List<Fruit> data;
    private BaseAdapter baseAdapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        listView = findViewById(R.id.lv);

        //设置表头表尾
        View headView = LayoutInflater.from(this).inflate(R.layout.list_head, listView, false);
        View footView = LayoutInflater.from(this).inflate(R.layout.list_foot, listView, false);
        listView.addHeaderView(headView);
        listView.addFooterView(footView);

        data = new ArrayList<>();
        data.add(new Fruit(R.mipmap.apple,"苹果","3.5元/KG"));
        data.add(new Fruit(R.mipmap.orange,"橘子","6.8元/KG"));
        data.add(new Fruit(R.mipmap.mango,"芒果","5.8元/KG"));
        data.add(new Fruit(R.mipmap.strawberry,"草莓","6.5元/KG"));
        data.add(new Fruit(R.mipmap.banana,"香蕉","7.5元/KG"));
        data.add(new Fruit(R.mipmap.watermelon,"西瓜","4.3元/KG"));

        baseAdapter = new optimizeBaseAdapter2(this,data);
        listView.setAdapter(baseAdapter);

        //点击列表事件有关
        listView.setOnItemClickListener(this);
    }

    //点击事件有关
    //当加了表头和表尾之后,会占据原有索引位置,索引需要-1或+1
    @Override
    public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
        
        //点击listView添加一列
        data.add(0,new Fruit(R.mipmap.pear,"梨","3.5元/KG"));
        //提醒更新listView,需要从baseAdapter提取数据,将其设置为全局变量
        baseAdapter.notifyDataSetChanged();
        
        if (position == 0) {
            Toast.makeText(this,"表头",Toast.LENGTH_SHORT).show();
        } else if (data.size() - position == -1){
            Toast.makeText(this,"表尾",Toast.LENGTH_SHORT).show();
        } else if (position > 0){
            Toast.makeText(this,data.get(position-1).getName(),Toast.LENGTH_SHORT).show();
        }
    }
}

optimizeBaseAdapter2

/*
* 实现adapter的功能
* 实现ListView内部控件的点击事件
* 内部点击事件的两种实现方式:
* 1、在getView中采用匿名内部类,好处可以直接调用position属性
* 2、实现OnClickListener接口,需要依托view作为桥梁,使用getTag共享position
* */
public class optimizeBaseAdapter2 extends BaseAdapter implements View.OnClickListener{

    //目的:与MainActivity关联上
    private Context context;
    //目的:把数据源进行绑定
    private List<Fruit> data;
    //private Layout layout;

    public optimizeBaseAdapter2() {
    }

    public optimizeBaseAdapter2(Context context, List<Fruit> list) {
        this.context = context;
        this.data = list;
    }

    @Override
    public int getCount() {
        return data.size();
    }

    @Override
    public Object getItem(int position) {
        return data.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    static class ViewHolder{
        ImageView ivIcon;
        TextView tvName;
        TextView tvPrice;
        Button btnBuy;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        ViewHolder viewHolder = null;

        if (convertView == null) {
            //动态加载布局;.inflate()调用动态布局
            //三个参数:子布局 父布局 是否附加到根部局上面
            convertView = LayoutInflater.from(context).inflate(R.layout.list_layout, parent, false);
            viewHolder = new ViewHolder();
            viewHolder.ivIcon = convertView.findViewById(R.id.iv_icon);
            viewHolder.tvName = convertView.findViewById(R.id.tv_name);
            viewHolder.tvPrice = convertView.findViewById(R.id.tv_price);
            viewHolder.btnBuy = convertView.findViewById(R.id.btn_buy);
            /*使用一种称为“ViewHolder”的技术,缓存convertView中的View组件,
             使用convertView的setTag()和getTag(),每次当ListView需要创建Item的View时候,
             调用convertView的getTag()找出ViewHolder,重新复用。具体的使用方法如代码*/
            convertView.setTag(viewHolder);

        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }

        //第二种方法,绑定点击事件
        viewHolder.ivIcon.setOnClickListener(this);
        viewHolder.tvName.setOnClickListener(this);
        viewHolder.tvPrice.setOnClickListener(this);
        viewHolder.btnBuy.setOnClickListener(this);
        //共享数据position
        viewHolder.ivIcon.setTag(R.id.iv_icon,position);
        viewHolder.tvName.setTag(R.id.tv_name,position);
        viewHolder.tvPrice.setTag(R.id.tv_price,position);
        viewHolder.btnBuy.setTag(R.id.btn_buy,position);

        //第一种,绑定单击事件(匿名内部类)
//        viewHolder.ivIcon.setOnClickListener(new View.OnClickListener() {
//            @Override
//            public void onClick(View view) {
//                Toast.makeText(context, "内部测试"+ data.get(position).getName(), Toast.LENGTH_SHORT).show();
//            }
//        });

        viewHolder.ivIcon.setImageResource(data.get(position).getIcon());
        viewHolder.tvName.setText(data.get(position).getName());
        viewHolder.tvPrice.setText(data.get(position).getPrice());

        return convertView;
    }
    @Override
    public void onClick(View view) {
        // view.getId() 得到点击的控件的id
        switch (view.getId()) {

            case R.id.iv_icon:
                int position1 = (Integer) view.getTag(R.id.iv_icon);
                Toast.makeText(context, data.get(position1).getIcon()+"图片", Toast.LENGTH_SHORT).show();
                break;
            case R.id.tv_name:
                int position2 = (Integer) view.getTag(R.id.tv_name);
                Toast.makeText(context, data.get(position2).getName()+"名字", Toast.LENGTH_SHORT).show();
                break;
            case R.id.tv_price:
                int position3 = (Integer) view.getTag(R.id.tv_price);
                Toast.makeText(context, data.get(position3).getPrice()+"价格", Toast.LENGTH_SHORT).show();
                break;
            case R.id.btn_buy:
                int position4 = (Integer) view.getTag(R.id.btn_buy);
                Toast.makeText(context, "购买了"+ data.get(position4).getName(), Toast.LENGTH_SHORT).show();
                break;
        }
    }
}

设置表头表尾:

1、新建list_head/list_foot,表头表尾布局文件。

2、LayoutInflater.from(this)获得LayoutInflater实例,.inflate将子布局加载到根部局中

//设置表头表尾
//LayoutInflater是用来找动态加载res/layout/下的xml布局文件,并且实例化。
//三个参数:
//	1、子布局	2、给子布局指定一个根视图	3、是否加载到根视图
View headView = LayoutInflater.from(this).inflate(R.layout.list_head, listView, false);
View footView = LayoutInflater.from(this).inflate(R.layout.list_foot, listView, false);
listView.addHeaderView(headView);
listView.addFooterView(footView);

listview数据更新:

//点击listView添加一列
data.add(0,new Fruit(R.mipmap.pear,"梨","3.5元/KG"));
//提醒更新listView,需要从baseAdapter提取数据,将其设置为全局变量
baseAdapter.notifyDataSetChanged();

Activity

1. Activity用于显示用户界面,用户通过Activity交互完成相关操作 2. 一个App允许有多个Activity

Activity的概念与Activity的生命周期图:

在这里插入图片描述

总结:

  1. 程序一运行:onCreate、onStart、onResume

  2. 点击home键或多窗口模式(挂起状态):onPause、onStop

  3. 从后台或继续点击程序,再次回到页面:onRestart、onStart、onResume

  4. 点击返回:onPause、onStop、onDestroy

  5. 重启程序:onCreate、onStart、onResume

  6. 挂起状态,X关闭掉,不会调用任何属性、

  7. 另外,横切竖,竖切横,生命周期都会完整的走一遍,除了onRestart

    onPause、onStop、onDestroy、onCreate、onStart、onResume

启动一个Activity的几种方式

1. 显式启动:通过包名来启动,写法如下:

**①最常见的:**只能启动自己app内部的其他Act

startActivity(new Intent(当前Act.this,要启动的Act.class));

②通过Intent的ComponentName:能启动自己的app,也能跨app启动其他Act;注意:跨域启动其他app的非主Act要设置权限

ComponentName cn = new ComponentName("当前Act的全限定类名","启动Act的全限定类名");
Intent intent = new Intent();
intent.setComponent(cn);
startActivity(intent);

③初始化Intent时指定包名:

Intent intent = new Intent("android.intent.action.MAIN");
intent.setClassName("当前Act的全限定类名","启动Act的全限定类名");
startActivity(intent);

2.隐式启动:通过Intent-filter的Action,Category或data来实现 这个是通过Intent的 intent-filter**来实现的,这个Intent那章会详细讲解! 这里知道个大概就可以了!

3. 另外还有一个直接通过包名启动apk的:

Intent intent = getPackageManager().getLaunchIntentForPackage
("apk第一个启动的Activity的全限定类名");
if(intent != null) startActivity(intent);

Service

没有界面的Act

Service的生命周期图

在这里插入图片描述

启动服务第一种:

1)StartService()启动Service
2)BindService()启动Service

MyService

public class MyService extends Service {
    //定义TAG为类名
    private static final String TAG=MyService.class.getName();

    public MyService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
        Log.i(TAG,"onBind");
    }

    //Service被创建时调用
    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG,"onCreate");

    //Service被启动时调用
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG,"onStartCommand intent="+intent+",flags="+flags+",startId="+startId);
        return super.onStartCommand(intent,flags,startId);
    }

    //Service被关闭之前回调
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(TAG,"onDestroy");
    }
}

AndroidManifest.xml完成Service注册

<service
    android:name=".MyService"
    android:enabled="true"
    android:exported="false"></service>

MainActivity

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Button btnStart;
    private Button btnStop;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btnStart = findViewById(R.id.btnStart);
        btnStop = findViewById(R.id.btnStop);

        btnStart.setOnClickListener(this);
        btnStop.setOnClickListener(this);
    }

    //为两个按钮设置点击事件,分别是启动与停止service  
    @Override
    public void onClick(View view) {
        //创建启动Service的Intent,以及Intent属性
        Intent intent = new Intent(MainActivity.this,MyService.class);
        switch (view.getId()){
            case R.id.btnStart:
                //启动服务
                startService(intent);
                break;
            case R.id.btnStop:
                //停止服务
                stopService(intent);
                break;
        }
    }
}

运行结果:
在这里插入图片描述

启动服务第二种:

​ 绑定Service,解除绑定

activity_main.xml

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

    <Button
        android:id="@+id/btnStart"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="启动服务"/>

    <Button
        android:id="@+id/btnStop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="40dp"
        android:text="停止服务" />

    <Button
        android:id="@+id/bindService"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="40dp"
        android:text="绑定服务" />

    <Button
        android:id="@+id/unbindService"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="40dp"
        android:text="解除绑定" />

    <Button
        android:id="@+id/callServiceMethod"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="40dp"
        android:text="调用服务内部方法" />

</LinearLayout>

MyService

public class MyService extends Service {
    //定义TAG为类名
    private static final String TAG=MyService.class.getName();

    //内部类
    //定义onBinder方法所返回的对象
    class InnerBinder extends Binder{
        public void callServiceInnerMethod(){
            sayHello();
        }
    }

    //返回一个Binder
    //必须实现的方法,绑定该Service时回调该方法
    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG,"onBind...");
        return new InnerBinder();
    }

    //Service被创建时调用
    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG,"onCreate...");
    }

    //Service被启动时调用
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG,"onStartCommand intent="+intent+",flags="+flags+",startId="+startId);
        return super.onStartCommand(intent,flags,startId);
    }

    //Service被关闭之前回调
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(TAG,"onDestroy");
    }
    
    private void sayHello(){
        Toast.makeText(this, "Hello,树哥", Toast.LENGTH_SHORT).show();
    }
}

AndroidManifest.xml完成Service注册

<service
    android:name=".MyService"
    android:enabled="true"
    android:exported="false"></service>

MainActivity

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    //定义TAG为类名
    private static final String TAG=MyService.class.getName();
    private Button btnStart,btnStop;
    private Button bindService,unbindService,callServiceMethod;
    private ServiceConnection conn;
    private MyService.InnerBinder mRemoteBinder;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btnStart = findViewById(R.id.btnStart);
        btnStop = findViewById(R.id.btnStop);
        bindService = findViewById(R.id.bindService);
        unbindService = findViewById(R.id.unbindService);
        callServiceMethod = findViewById(R.id.callServiceMethod);

        btnStart.setOnClickListener(this);
        btnStop.setOnClickListener(this);
        bindService.setOnClickListener(this);
        unbindService.setOnClickListener(this);
        callServiceMethod.setOnClickListener(this);
    }
    
    private ServiceConnection conn = new ServiceConnection(){
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            Log.i(TAG,"onServiceConnected...");
            mRemoteBinder = (MyService.InnerBinder) iBinder;
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            Log.i(TAG,"onServiceDisconnected...");
            mRemoteBinder = null;
        }
    };

    @Override
    public void onClick(View view) {
        Intent intent = new Intent(MainActivity.this,MyService.class);
        switch (view.getId()){
            case R.id.btnStart:
                //启动服务
                startService(intent);
                break;
            case R.id.btnStop:
                //停止服务
                stopService(intent);
                break;
            case R.id.bindService:
                //绑定服务
                //BIND_AUTO_CREATE没有创建自己创建,已经创建了就不创建
                bindService(intent,conn,BIND_AUTO_CREATE);
                break;
            case R.id.unbindService:
                //解除绑定
                if (conn != null){
                    unbindService(conn);
                }
                break;
            case R.id.callServiceMethod:
                //调用服务内部方法
                Log.i(TAG,"调用了callServiceMethod");
                mRemoteBinder.callServiceInnerMethod();
        }
    }
}

在这里插入图片描述

总结:

总结:启动服务、停止服务(startService、stopService)

​ 第一次点击启动服务:onCreate、onStartCommand

​ 超过第二次点击启动服务:onStartCommand

​ 停止服务:onDestory

onBind()方法并没有被调用,多次点击启动Service,只会重复地调用onStartCommand 方法;无论启动多少次Service,一个stopService就会停止Service

总结:绑定服务、解除绑定、调用服务内部方法(bindService、unbindService)

​ 点击绑定服务:onCreate、onBind、onServiceConnected

​ 继续点击绑定服务:没任何变化

​ 解除绑定:onUnbind、onDestroy

  • ServiceConnection对象:监听访问者与Service间的连接情况,如果成功连接,回调 onServiceConnected(),如果异常终止或者其他原因终止导致Service与访问者断开连接则回调onServiceDisconnected方法,调用unBindService()不会调用该方法!
  • onServiceConnected方法中有一个IBinder对象,该对象即可实现与被绑定Service(自定义MyService)之间的通信!我们再开发Service类时,默认需要实现IBinder onBind()方法,该方法返回的 IBinder对象会传到ServiceConnection对象中的onServiceConnected的参数iBinder,我们就可以在这里通过这个IBinder与Service进行通信!

**Step 1:**在自定义的Service中继承Binder,实现自己的IBinder对象
**Step 2:**通过onBind( )方法返回自己的IBinder对象
**Step 3:**在绑定该Service的类中定义一个ServiceConnection对象,重写两个方法,onServiceConnected和onDisconnected!然后直接读取IBinder传递过来的参数即可!

使用BindService绑定Service,依次调用onCreate()、onBind()方法, 我们可以在onBind()方法中返回自定义的IBinder对象;

再接着调用的是 ServiceConnection的onServiceConnected()方法该方法中可以获得 IBinder对象,从而进行相关操作;

当Service解除绑定后会自动调用 onUnbind和onDestroyed方法,当然绑定多客户端情况需要解除所有 的绑定才会调用onDestoryed方法进行销毁

启动服务startService可以保证服务长期在后台运行;但是不能进行相互通讯

绑定服务bindService不能保证服务长期在后台进行运行;可以进行相互通讯

Activity与Service通信

我们前面的操作都是通过Activity启动和停止Service,假如我们启动的是一个下载的后台Service,而我们想知道Service中下载任务的进度!那么这肯定是需要Service 与Activity进行通信的,而他们之间交流的媒介就是Service中的onBind()方法! 返回一个我们自定义的Binder对象!

基本流程如下:

  • 1.自定义Service中,自定义一个Binder类,然后将需要暴露的方法都写到该类中!
  • 2.Service类中,实例化这个自定义Binder类,然后重写onBind()方法,将这个Binder对象返回!
  • 3.Activity类中实例化一个ServiceConnection对象,重写onServiceConnected()方法,然后 获取Binder对象,然后调用相关方法即可!

ANR:IntentService的使用

使用IntentService解决因为耗时产生的ANR问题

ANR最常见的产生原因:

​ 1、系统内存不足的时候

​ 怎么定位是内存不足产生的。需要查看系统kennel的日志,main.log的日志。

​ 2、主线程耗时引起ANR,这种问题必须要解决

​ Activity的线程常称为UI线程,实际他就是主线程。

​ Service的线程也是主线程

Service在20秒 broradcastreciver在10秒 UI线程在5秒

工作流程:

客户端通过startService(Intent)来启动IntentService;我们并不需要手动地区控制IntentService,当任务执行完后,IntentService会自动停止;可以启动IntentService多次,每个耗时操作会以工作队列的方式在IntentService的 onHandleIntent回调方法中执行,并且每次只会执行一个工作线程,执行完一,再到二这样!

在这里插入图片描述

模拟ANR在Service中,这里把代码写一块了,都在IntentService中:

MyIntentService

public class MyIntentService extends IntentService {

    private final String TAG = "MyIntentService";

    //必须实现父类的构造方法
    public MyIntentService() {
        super("MyIntentService");
    }

    //必须重写的核心方法
    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        Log.i(TAG,"onHandleIntent...");
        //Intent是从Activity发过来的,携带识别参数,根据参数不同执行不同的任务
        try {
            Thread.sleep(30000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Log.i(TAG,"onHandleIntent over...");
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG,"onBind...");
        return super.onBind(intent);
    }

    @Override
    public void onCreate() {
        Log.i(TAG,"onCreate...");
        super.onCreate();
        // 模拟主线程耗时操作
        // 主线程不能做耗时操作,如果做耗时操作就会产生ANR
        //try {
        //    Thread.sleep(10000);
        //} catch (InterruptedException e) {
        //    e.printStackTrace();
        //}
    }

    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        Log.i(TAG,"onStartCommand...");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        Log.i(TAG,"onDestroy...");
        super.onDestroy();
    }

}

AndroidManifest.xml注册下Service

<service android:name=".MyIntentService"></service>

MainActivity

public class MainActivity extends AppCompatActivity {

    private Button btnStart,btnStop;
    private static final String TAG = MainActivity.class.getName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Log.i(TAG,"Activity ThreadName = "+Thread.currentThread().getName());
//        try {
//            Thread.sleep(30000);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }

        btnStart = findViewById(R.id.btn_start);
        btnStop = findViewById(R.id.btn_stop);

        Intent intent = new Intent(this, MyIntentService.class);
        btnStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                startService(intent);
            }
        });
        btnStop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                stopService(intent);
            }
        });
    }
}

启动服务:onCreate、onStartCommand、onHandleIntent

​ 过了睡眠时间:自定义onHandleIntent over、onDestroy

中途停止服务会立马执行onDestroy,但后续睡眠时间完后,还是执行onHandleIntent over

  • IntentService是继承与Service并处理异步请求的一个类,IntentService有一个工作线程来处理耗时操作,请求的Intent记录会加入队列。就类似开辟一个子线程来处理这些耗时操作,从而解决这些问题。
    • IntentService启动服务一次性把所有生命周期跑完,开辟子线程,在onHandleIntent核心方法中做耗时操作。
  • 即使服务提前结束,线程该做的事情还是得做完。但如果线程有依赖服务,提前停止服务会报空指针异常。

BroadcastReceiver

两种广播类型:

在这里插入图片描述

接收系统广播

1)两种注册广播的方式

在这里插入图片描述
在这里插入图片描述

动态注册广播

监控电量变化与USB拔插

MainActivity

/*
* 动态注册广播,节省资源,不用一直监听着。
* */
public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    private TextView mBatteryLevelText;
    private BatteryLeveReceiver mBatteryLeveReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mBatteryLevelText = findViewById(R.id.battery_level);
        registerBatteryReceiver();
    }

    private void registerBatteryReceiver() {
        //第二步:
        //我们要收听的频道是:电量变化
        IntentFilter intentFilter = new IntentFilter();
        //第三步:
        //设置频道:电量变化
        intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
        //设置频道:USB拔插
        intentFilter.addAction(Intent.ACTION_POWER_DISCONNECTED);
        intentFilter.addAction(Intent.ACTION_POWER_DISCONNECTED);
        //第四步:
        mBatteryLeveReceiver = new BatteryLeveReceiver();
        //第五步:
        //注册广播,动态注册
        this.registerReceiver(mBatteryLeveReceiver,intentFilter);
    }

    /*
    * 第一步,就是创建一个广播接收者,继承BroadcastReceiver
    * */
    private class BatteryLeveReceiver extends BroadcastReceiver{

        @Override
        public void onReceive(Context context, Intent intent) {
            //接收的action,就是MainActivity里addAction
            String action = intent.getAction();
            if (Intent.ACTION_BATTERY_CHANGED.equals(action)){
                Log.d(TAG,"onReceive收到了电量变化的广播,action = " + action);
                int currentLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
                Log.d(TAG,"当前电量:" + currentLevel);
                //需要判断,因为如果先注册,在找控件,直接set会报空指针
                if (mBatteryLevelText != null){
                    mBatteryLevelText.setText("当前电量:" + intent.getIntExtra(BatteryManager.EXTRA_LEVEL,0));
                }
                int maxLevel = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
                //拿到当前的电量以后,再除以最大值
                float percent = currentLevel*1.0f / maxLevel * 100;
                Log.d(TAG,"当前的电量百分比是:" + percent + "%");
            }else if (Intent.ACTION_POWER_CONNECTED.equals(action)){
                Log.d(TAG,"USB连接上了...");
            }else if (Intent.ACTION_POWER_DISCONNECTED.equals(action)){
                Log.d(TAG,"USB断开了...");
            }
        }
    }

    //第六步
    @Override
    protected void onDestroy() {
        super.onDestroy();
        //取消广播注册,否则会导致内存泄漏
        if (mBatteryLeveReceiver != null){
            unregisterReceiver(mBatteryLeveReceiver);
        }
    }
}

AndroidManifest.xml中配置权限

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

    <!--设置权限-动态-电量相关-->
    <uses-permission android:name="android.permission.BATTERY_STATS"
        tools:ignore="ProtectedPermissions" />
静态注册

监听开机完成

BootCompleteReceiver

/*
* 第一步,就是创建一个广播接收者,继承自BroadcastReceiver
* */
public class BootCompleteReceiver extends BroadcastReceiver {

    private static final String TAG = "BootCompleteReceiver";

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        Log.d(TAG,"action = " + action);

        Log.d(TAG,"开机完成...");
        Toast.makeText(context, "收到开机完成的广播", Toast.LENGTH_SHORT).show();
    }
}

AndroidManifest.xml中配置权限与receiver|intent-filter

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

    <!--设置权限-静态-开机完成-->
    <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.BroadcastDemo">
        <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>

        <!--第二步,其实就是和我们动态注册设置action一样-->
        <receiver android:name=".BootCompleteReceiver"
            android:exported="true">
        <!--是否接收其他app访问-->
            <!--意图过滤-->
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
            </intent-filter>
        </receiver>
    </application>
</manifest>
静态注册

监听应用安装与卸载

AppStateChangeReceiver

public class AppStateChangeReceiver extends BroadcastReceiver {

    private static final String TAG = "AppStateChangeReceiver";

    @Override
    public void onReceive(Context context, Intent intent) {

        String action = intent.getAction();
        if (intent.ACTION_PACKAGE_ADDED.equals(action)){
            Log.d(TAG,"应用安装了,他的相关信息是:" + intent.getData());
        }else if (intent.ACTION_PACKAGE_REMOVED.equals(action)){
            Log.d(TAG,"应用卸载了,他的相关信息是:" + intent.getData());
        }
    }
}

AndroidManifest.xml中配置receiver|intent-filter

<receiver android:name=".AppStateChangeReceiver"
    android:exported="true">
    <intent-filter>
        <!--应用安装-->
        <action android:name="android.intent.action.PACKAGE_ADDED"/>
        <!--应用安装-->
        <action android:name="android.intent.action.PACKAGE_REMOVED"/>
        <!--约束-->
        <data android:scheme="package"/>
    </intent-filter>
</receiver>

2)自定义发送广播

以动态注册的形式发送

点击Button按钮,会拿到EditText内容信息,之后在Act单击事件里设置SetAction和使用intent传数据,调用sendBroadcast发送广播,定义广播接收者类,继承BroadcastReceiver就能够通过监听action接收到发送的内容。

activity_main.xml

<EditText
    android:id="@+id/send_InputMsg"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:hint="请输入要发送的广播内容"/>

<Button
    android:id="@+id/send_BroadcastMsg"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="发送一条广播"/>

MainActivity

public class MainActivity extends AppCompatActivity{

    private EditText mInputBox;
    private Button btnSentBroadcast;
    private MessageReceiver messageReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mInputBox = findViewById(R.id.send_InputMsg);
        btnSentBroadcast = findViewById(R.id.send_BroadcastMsg);

        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction("MY_Action");

        messageReceiver = new MessageReceiver();
        registerReceiver(messageReceiver,intentFilter);

        btnSentBroadcast.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //被调用以后,我们就去发广播
                String mInputBoxString = mInputBox.getText().toString();
                Intent intent = new Intent();
                //若api8.0以上
                //MainActivity.this, MessageReceiver.class
                intent.setAction("MY_Action");
                intent.putExtra("msg",mInputBoxString);
                //发送广播
                sendBroadcast(intent);
            }
        });
    }

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

MessageReceiver

public class MessageReceiver extends BroadcastReceiver {

    private static final String TAG = "MessageReceiver";

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        Log.i(TAG, "action = " + action);
        String msg = intent.getStringExtra("msg");
        Log.i(TAG,"mInputBoxString = " + msg);
        Toast.makeText(context, "收到了" + msg, Toast.LENGTH_SHORT).show();
    }
}

3)有序广播

MainActivity

public class MainActivity extends AppCompatActivity {

    private Button btnSentOrder;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btnSentOrder = findViewById(R.id.btn_SentOrder);

        btnSentOrder.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent();
                intent.setAction("ORDER_BAROADCAST");
                //使用bundle传输数据
                Bundle bundle = new Bundle();
                bundle.putCharSequence("context","我是被发送的广播内容...");
                sendOrderedBroadcast(intent,null,null,null, Activity.RESULT_OK,null,bundle);
            }
        });
    }
}

AndroidManifest.xml中配置receiver|intent-filter

<receiver android:name=".HighLevelBroadcastReceiver"
    android:exported="true">
    <!--priority表示等级,值范围-1000~1000,默认为0-->
    <intent-filter android:priority="1000">
        <action android:name="ORDER_BAROADCAST"/>
    </intent-filter>
</receiver>

<receiver android:name=".DefaultLevelBroadcastReceiver"
    android:exported="true">
    <intent-filter android:priority="0">
        <action android:name="ORDER_BAROADCAST"/>
    </intent-filter>
</receiver>

<receiver android:name=".LowLevelBroadcastReceiver"
    android:exported="true">
    <!--priority表示等级,值范围-1000~1000,默认为0-->
    <intent-filter android:priority="-1000">
        <action android:name="ORDER_BAROADCAST"/>
    </intent-filter>
</receiver>

HighLevelBroadcastReceiver

public class HighLevelBroadcastReceiver extends BroadcastReceiver {

    private static final String TAG = "HighLevelBroadcastReceiver";

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "high action = " + intent.getAction());
        //广播到达这里时,终止往下传达。
        //abortBroadcast();
        //广播到达这里时,修改广播的内容,以下的优先级收到的都是这个内容
        Bundle resultExtras = getResultExtras(true);
        String context1 = resultExtras.getCharSequence("context").toString();
        Log.d(TAG,"context = " + context1);
        resultExtras.putCharSequence("context","High修改过广播内容...");
        setResultExtras(resultExtras);
    }
}

DefaultLevelBroadcastReceiver

public class DefaultLevelBroadcastReceiver extends BroadcastReceiver {

    private static final String TAG = "DefaultLevelBroadcastReceiver";

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "default action = " + intent.getAction());
        //获取修改广播的内容
        Bundle resultExtras = getResultExtras(true);
        String context1 = resultExtras.getCharSequence("context").toString();
        Log.d(TAG,"context = " + context1);
    }
}

LowLevelBroadcastReceiver

public class LowLevelBroadcastReceiver extends BroadcastReceiver {

    private static final String TAG = "LowLevelBroadcastReceiver";

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "low action = " + intent.getAction());
        //修改广播的内容
        Bundle resultExtras = getResultExtras(true);
        String context1 = resultExtras.getCharSequence("context").toString();
        Log.d(TAG,"context = " + context1);
    }
}

结果:

com.shroud.orderlybroadcast D/HighLevelBroadcastReceiver: high action = ORDER_BAROADCAST
com.shroud.orderlybroadcast D/HighLevelBroadcastReceiver: context = 我是被发送的广播内容...
com.shroud.orderlybroadcast D/DefaultLevelBroadcastReceiver: default action = ORDER_BAROADCAST
com.shroud.orderlybroadcast D/DefaultLevelBroadcastReceiver: context = High修改过广播内容...
com.shroud.orderlybroadcast D/LowLevelBroadcastReceiver: low action = ORDER_BAROADCAST
com.shroud.orderlybroadcast D/LowLevelBroadcastReceiver: context = High修改过广播内容...

总结:有序广播可以通过设置接收权限大小,按照权限大小进行接收,可拦截广播往下传播:abortBroadcast(); ,可以将广播内容进行修改再往下传播:sendOrderedBroadcast(Act),Receiver中拿到数据进行修改就行了,这里使用的Bundle传输数据。

发送的广播谁能接收

研究对象发送者

第一步:在app1发送者中,AndroidManifest.xml配置定义权限:包名 + 权限名

<!--定义权限-->
<permission android:name="com.shroud.orderlybroadcast.ORDER_PERMISSION"/>

将定义好的权限,在发送时写入参数中。

//发送广播时,带上自己定义的权限(参数二)Manifest.permission.ORDER_PERMISSION
sendOrderedBroadcast(intent,Manifest.permission.ORDER_PERMISSION,null,null, Activity.RESULT_OK,null,bundle);

在这里插入图片描述

第二步:app2接收者想要接收到带权限的广播,必须声明权限:

<!--另一个app想要接收到带权限的,必须申明权限-->
<uses-permission android:name="com.shroud.orderlybroadcast.ORDER_PERMISSION"/>

**要注意:**这里使用的都是静态注册,第二个app2中注册的时候,action的值,一定要与app1里面的Act中的intent.setAction(“ORDER_BAROADCAST”);设置一致。

app1中:MainActivity

public class MainActivity extends AppCompatActivity {

    private Button btnSentOrder;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btnSentOrder = findViewById(R.id.btn_SentOrder);

        btnSentOrder.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent();
                intent.setAction("ORDER_BAROADCAST");
                //使用bundle传输数据
                Bundle bundle = new Bundle();
                bundle.putCharSequence("context","我是被发送的广播内容...");
                //发送广播时,带上自己定义的权限(参数二)Manifest.permission.ORDER_PERMISSION
                sendOrderedBroadcast(intent,Manifest.permission.ORDER_PERMISSION,null,null, Activity.RESULT_OK,null,bundle);
            }
        });
    }
}

app2中:BroadcastReceiver

public class OrderBroadcastReceiver extends BroadcastReceiver {
    private static final String TAG = "app2 OrderBroadcastReceiver";

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.i(TAG,"app2接收到了来自app的广播内容" + intent.getAction());
    }
}

谁可以给我发广播

研究对象接收者

第一步:在app2接收者中,AndroidManifest.xml配置定义权限:包名 + 权限名,并在receiver中声明permission

<permission android:name="com.shroud.orderlybroadcast.WHO_CAN_SEND_ME"/>
......

    <receiver android:name=".OrderBroadcastReceiver"
              android:permission="com.shroud.orderlybroadcast.WHO_CAN_SEND_ME"
              android:exported="true">
        <intent-filter>
            <action android:name="ORDER_BAROADCAST"/>
        </intent-filter>
    </receiver>

第二步:app1发送者想要给接收者发送广播,必须声明权限:

<uses-permission android:name="com.shroud.orderlybroadcast.WHO_CAN_SEND_ME"/>

**要注意:**这里使用的都是静态注册,第二个app2中注册的时候,action的值,一定要与app1里面的Act中的intent.setAction(“ORDER_BAROADCAST”);设置一致。

app2中:AndroidManifest.xml中配置permission|receiver permission

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

    <!--另一个app想要接收到带权限的,必须申明权限-->
<!--    <uses-permission android:name="com.shroud.orderlybroadcast.ORDER_PERMISSION"/>-->
    
    <permission android:name="com.shroud.orderlybroadcast.WHO_CAN_SEND_ME"/>

    <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.PermissionBroadcast">
        <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>

        <receiver android:name=".OrderBroadcastReceiver"
            android:permission="com.shroud.orderlybroadcast.WHO_CAN_SEND_ME"
            android:exported="true">
            <intent-filter>
                <action android:name="ORDER_BAROADCAST"/>
            </intent-filter>
        </receiver>

    </application>
</manifest>

Fragment

Fragment是Android3.0后引入的一个新的API,他出现的初衷是为了适应大屏幕的平板电脑, 当然现在他仍然是平板APP UI设计的宠儿,而且我们普通手机开发也会加入这个Fragment, 我们可以把他看成一个小型的Activity,又称Activity片段

使用Fragment 我们可以把屏幕划分成几块,然后进行分组,进行一个模块化的管理!从而可以更加方便的在 运行过程中动态地更新Activity的用户界面!

Fragment不能单独使用,需要嵌套在Activity 中使用,尽管他拥有自己的生命周期,但是还是会受到宿主Activity的生命周期的影响,比如Activity 被destory销毁了,他也会跟着销毁!不建议在Fragment里面嵌套Fragment,因为嵌套在里面的Fragment生命周期不可控。

总结:1、Fragment有自己的生命周期

​ 2、Fragment不能单独使用,需要依赖于Activity(但是不继承Act)

​ 3、Fragment和Activity是多对多的关系,他们能进行信息交互

​ 4、Fragment通过getActivity()可以获取所在的Activity;

​ Activity通过FragmentManager的findFragmentById() 或 findFragmentByTag获取Fragment

Fragment的生命周期图

在这里插入图片描述

ViewPager

ViewPager就是一个简单的页面切换组件,我们可以往里面填充多个View,然后我们可以左 右滑动,从而切换不同的View

两个Fragment 专用的Adapter:FragmentPageAdapterFragmentStatePagerAdapter 他们的区别:

  • FragmentPageAdapter:不销毁,和PagerAdapter一样,只会缓存当前的Fragment以及左边一个,右边 一个,即总共会缓存3个Fragment而已,假如有1,2,3,4四个页面:
    处于1页面:缓存1,2
    处于2页面:缓存1,2,3
    处于3页面:销毁1页面,缓存2,3,4
    处于4页面:销毁2页面,缓存3,4
    更多页面的情况,依次类推~
  • FragmentStatePagerAdapter:当Fragment对用户不见得时,整个Fragment会被销毁, 只会保存Fragment的状态!而在页面需要重新显示的时候,会生成新的页面!

总结:FragmentPageAdapter适合固定的页面较少的场合;

而FragmentStatePagerAdapter则适合于页面较多或者页面内容非常复杂(需占用大量内存)的情况!

PagerAdapter的使用

  • getCount():获得viewpager中有多少个view
  • destroyItem():移除一个给定位置的页面。适配器有责任从容器中删除这个视图。 这是为了确保在finishUpdate(viewGroup)返回时视图能够被移除。

而另外两个方法则是涉及到一个key的东东:

  • instantiateItem(): ①将给定位置的view添加到ViewGroup(容器)中,创建并显示出来 ②返回一个代表新增页面的Object(key),通常都是直接返回view本身就可以了,当然你也可以 自定义自己的key,但是key和每个view要一一对应的关系
  • isViewFromObject(): 判断instantiateItem(ViewGroup, int)函数所返回来的Key与一个页面视图是否是代表的同一个视图(即它俩是否是对应的,对应的表示同一个View),通常我们直接写 return view == object!

使用示例1:最简单用法

运行效果图
在这里插入图片描述

layout1.xml、layout2.xml、layout3.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:background="#ff00ffff"
    android:orientation="vertical">

    <TextView
        android:textSize="30sp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="layout1" />

</LinearLayout>

自定义MyAdapter,继承PagerAdapter

public class MyAdapter extends PagerAdapter {

    private List<View> mListView;

    //通过构造器将数据传过来
    public MyAdapter(List<View> mListView) {
        this.mListView = mListView;
    }

    @Override
    public int getCount() {
        return mListView.size();
    }

    @NonNull
    @Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        //添加view;会给被添加的View指定一个索引
        container.addView(mListView.get(position),0);
        //通常之间返回view本身就可以了
        return mListView.get(position);
    }

    @Override
    public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
        return view==object;
    }

    //删除不用的页卡
    @Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        //删除当前不用的页卡
        container.removeView(mListView.get(position));
    }
}

MainActivity

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        LayoutInflater layoutInflater = LayoutInflater.from(this);
        View view1 = layoutInflater.inflate(R.layout.layout1, null);
        View view2 = layoutInflater.inflate(R.layout.layout2, null);
        View view3 = layoutInflater.inflate(R.layout.layout3, null);

        ArrayList<View> viewList = new ArrayList<>();
        viewList.add(view1);
        viewList.add(view2);
        viewList.add(view3);

        ViewPager viewPager = findViewById(R.id.vp);
        MyAdapter myAdapter = new MyAdapter(viewList);
        viewPager.setAdapter(myAdapter);
        
        

        //ViewPager viewPager = findViewById(R.id.vp);

        //LayoutInflater layoutInflater = LayoutInflater.from(this);
        //View view1 = layoutInflater.inflate(R.layout.layout1, viewPager,false);
        //View view2 = layoutInflater.inflate(R.layout.layout2, viewPager,false);
        //View view3 = layoutInflater.inflate(R.layout.layout3, viewPager,false);

        //ArrayList<View> viewList = new ArrayList<>();
        //viewList.add(view1);
        //viewList.add(view2);
        //viewList.add(view3);

        //MyAdapter myAdapter = new MyAdapter(viewList);
        //viewPager.setAdapter(myAdapter);
    }
}

ViewPager2

RecyclerView的封装,底层实际上就是RecyclerView,自带懒加载,

ViewPager2最显著的特点是基于RecyclerView实现,RecyclerView是目前Android端最成熟的AdapterView解决方案,

viewPager2.registerOnPagerChangeCallback()实现

基本使用 实现左右滑动。

具体步骤:

1、build.gradle中添加ViewPager2的依赖,在activity_main定义ViewPager2

2、在MainAct中获取viewPager2.setAdapter,之后为ViewPager构建Adapter

3、ViewPagerAdapter,创建Adapter类并继承RecyclerView.Adapter

4、构建ViewPager要展示页面的ViewHolder,实现必须方法


build.gradle中添加依赖

dependencies {
    //添加依赖
    implementation 'androidx.viewpager2:viewpager2:1.0.0'
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/viewPager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="Hello World!"
        android:background="@color/purple_500"/>

</androidx.constraintlayout.widget.ConstraintLayout>

创建子布局item_pager.xml

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

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"

        android:id="@+id/tvTitle"
        android:layout_centerInParent="true"
        android:text="hello,Shroud"
        android:textSize="32sp"
        android:textColor="#FF4532"/>

</RelativeLayout>

MainActivity,为ViewPager构建Adapter

public class MainActivity extends AppCompatActivity {

    private ViewPager2 viewPager2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        viewPager2 = findViewById(R.id.viewPager);

        ViewPagerAdapter viewPagerAdapter = new ViewPagerAdapter();
        //使用Adapter适配数据
        viewPager2.setAdapter(viewPagerAdapter);
    }
}

ViewPagerAdapter,创建Adapter类并继承RecyclerView.Adapter

构建ViewPager要展示页面的ViewHolder,实现必须方法

//源码需要<VH extends ViewHolder>
public class ViewPagerAdapter extends RecyclerView.Adapter<ViewPagerAdapter.ViewPagerHolder> {

    private List<String> titles = new ArrayList<>();
    private List<Integer> colors = new ArrayList<>();

    public ViewPagerAdapter() {
        titles.add("hello");
        titles.add("Shroud");
        titles.add("谭章竦");
        titles.add("帅哥");

        colors.add(R.color.black);
        colors.add(R.color.red);
        colors.add(R.color.white);
        colors.add(R.color.blue);
    }

    //修改返回值为继承ViewHolder的自定义类
    //原来返回值为RecyclerView.ViewHolder
    @NonNull
    @Override
    public ViewPagerHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {

        //对于一个没有被载入或者想要动态载入的界面,都需要使用LayoutInflater.inflate()来载入
        // 动态加载布局;.inflate()调用动态布局
        // 三个参数:子布局 父布局 是否附加到根部局上面
        return new ViewPagerHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_pager,parent,false));
    }

    //用于展示不同的数据,更改子布局中的TextView内容
    @Override
    public void onBindViewHolder(@NonNull ViewPagerHolder holder, int position) {
        holder.mTv.setText(titles.get(position));
        holder.mRl.setBackgroundResource(colors.get(position));
    }

    //设置滚动页面的数量
    @Override
    public int getItemCount() {
        return 4;
    }

    //自定义内部类,继承RecyclerView.ViewHolder,将上面所有的都做对应修改
    //目的:为了解析item_pager.xml(包含RelativeLayout、TextView)
    class ViewPagerHolder extends RecyclerView.ViewHolder{

        private RelativeLayout mRl;
        private TextView mTv;

        //当调用时,传入的View,此时就是item_pager.xml的根部局
        public ViewPagerHolder(@NonNull View itemView) {
            super(itemView);
            //找到里面包含的子控件
            mRl = itemView.findViewById(R.id.container);
            mTv = itemView.findViewById(R.id.tvTitle);
        }
    }
}

数据存储

如果是配置信息,保存到SP中。例如 记住密码,自动登录;

如果是列表数据,保存到数据库中。例如 联系人列表数据。

SP:sharedpreference 首选项

保存用户偏好参数

存储的内容:自动登录、记住密码、主题记录、是否在Wifi下才能联网等等;很小,很简单的数据,可以保存到首选项SP里面去

首选项不能存在太多的信息。 (SP xml 格式 Map(Key—value键值对))

SharedPreferences也是使用xml文件, 然后类似于Map集合,使用键-值的形式来存储数据;我们只需要调用SharedPreferences的getXxx(name), 就可以根据键获得对应的值

特点:当程序运行,首选项里面的数据会全部加载进内容

简单使用:这里只是做简单使用效果,使用了直接定义类的方式实现单击事件

  • 第一步、获取首选项SP;getSharedPreferences(SP的名字保存xml的名字,SP保存的时候用的模式)
  • 第二步、sp.edit()获取编辑器,之后进行put数据
  • 第三步、sp.edit().apply()才会写入到xml配置文件里面。

效果:

在这里插入图片描述

数据根据sp.edit().apply()方法之后)才会写入到xml配置文件里面,直接存储到data/data/全包名/shared_prefs下的xml中,存放的就是保存的键值对相对应的xml文件。

在这里插入图片描述

MainActivity

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    /**
     * public SharedPreferences getSharedPreferences(String name, int mode) {
     *         throw new RuntimeException("Stub!");}
     *
     * 参数1:SP的名字
     * 参数2:SP保存的时候用的模式 常规(每次都会更新覆盖)AAA == Context.MODE_PRIVATE
     *                        追加(每次保存都追加到后面)ShroudAAA == Context.MODE_APPEND
     */

    //保存到SP
    public void saveToSP(View view) {
        //获取首选项SP
        SharedPreferences sp = getSharedPreferences("SPShroudName", Context.MODE_PRIVATE);
        //sp.edit()获取编辑器,apply()才会写入到xml配置文件里面。
        sp.edit().putString("ShroudKey","谭章竦").apply();
        sp.edit().commit();
    }

    //获取SP的数据
    public void getSpData(View view) {
        //获取首选项SP
        SharedPreferences sp = getSharedPreferences("SPShroudName", Context.MODE_PRIVATE);
        //第二个参数用途,假设ShroudKey获取的值为空,则使用第二个参数默认值。
        String value = sp.getString("ShroudKey", "默认值");

        Toast.makeText(this, ""+ value, Toast.LENGTH_SHORT).show();
    }
}

模拟登录页面,SP真正的使用场景

注意:			开发过程中,key最好都定义为常量
 
			  此代码存在许多BUG,尚未解决,若取消选中点击登录未进行编码!

效果:

在这里插入图片描述

activity_main2.xml

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

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="用户名"
        android:textSize="15sp"/>
    <EditText
        android:id="@+id/et_name"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:hint="请输入用户名"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="密码"
        android:textSize="15sp"/>
    <EditText
        android:id="@+id/et_pwd"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:hint="请输入密码"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <CheckBox
            android:id="@+id/cb_remeberpwd"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="记住密码"/>
        <CheckBox
            android:id="@+id/cb_autologin"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="自动登录"/>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <Button
            android:id="@+id/bt_register"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="注册"/>
        <Button
            android:id="@+id/bt_login"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:text="登录"/>
    </LinearLayout>

</LinearLayout>

MainActivity2,这里是将MainAct2设置为主启动页面

public class MainActivity2 extends AppCompatActivity {

    private SharedPreferences sp;
    private EditText EtName,EtPwd;
    private CheckBox CbRemeberPwd,CbAutologin;
    private Button BtRegister,BtLogin;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);

        //获取首选项SP
        sp = getSharedPreferences("config", Context.MODE_PRIVATE);

        //调用初始化
        initView();

        //TODO 回显数据 读取sp数据
        // 第二次 或 第N次 打开的时候,从SP中获取之前保存的数据,进行画面同步
        boolean remeberpwd = sp.getBoolean("remeberpwd", false);//如果为空,则返回默认值
        boolean autologin = sp.getBoolean("autologin", false);// 如果为空,则返回默认值

        //记住密码 之前选中之后
        if (remeberpwd){
            //获取SP里的name和pwd,并保存到EditText
            String name = sp.getString("name", "");
            String pwd = sp.getString("pwd", "");
            EtName.setText(name);
            EtPwd.setText(pwd);
            CbRemeberPwd.setChecked(true);
        }

        //自动登录 之前选中之后
        if (autologin){
            CbAutologin.setChecked(true);

            //模拟自动登录业务
            Toast.makeText(this, "我自动登录了", Toast.LENGTH_SHORT).show();
        }
    }

    //初始化
    private void initView() {
        //找到控件
        EtName = findViewById(R.id.et_name);
        EtPwd = findViewById(R.id.et_pwd);
        CbRemeberPwd = findViewById(R.id.cb_remeberpwd);
        CbAutologin = findViewById(R.id.cb_autologin);
        BtRegister = findViewById(R.id.bt_register);
        BtLogin = findViewById(R.id.bt_login);

        //设置监听
        MyOnClickListener MyOnClick = new MyOnClickListener();
        BtRegister.setOnClickListener(MyOnClick);
        BtLogin.setOnClickListener(MyOnClick);
    }

    private class MyOnClickListener implements View.OnClickListener{

        @Override
        public void onClick(View view) {
            switch (view.getId()){
                //注册按钮 流程
                case R.id.bt_register:
                    break;
                //登录按钮 流程
                case R.id.bt_login:
                    //登录操作
                    String name = EtName.getText().toString().trim();
                    String pwd = EtPwd.getText().toString().trim();
                    if (TextUtils.isEmpty(name) || TextUtils.isEmpty(pwd)){
                        Toast.makeText(MainActivity2.this, "用户名或密码为空", Toast.LENGTH_SHORT).show();
                    }else {
                        //记住密码 是否选中
                        if (CbRemeberPwd.isChecked()){
                            //用户名和密码都需要保存 同时 记住密码的状态 也要保存
                            SharedPreferences.Editor editor = sp.edit();
                            editor.putString("name",name);
                            editor.putString("pwd",pwd);
                                //记住密码的状态
                            editor.putBoolean("remeberpwd",true);
                            editor.apply();
                        }

                        //自动登录 是否选中
                        if (CbAutologin.isChecked()){
                            SharedPreferences.Editor editor = sp.edit();
                                //自动登录的状态
                            editor.putBoolean("autologin",true);
                            editor.apply();
                        }
                    }
                    break;
            }
        }
    }
}

SQLite数据库

SQLite数据库,和其他的SQL数据库不同,并不需要在手机上另外安装一个数据库软件,Android系统已经集成了这个数据库。

嵌入式关系型的数据库,通过文件来保存数据库,体积小几十Kb、功能强大、嵌入式设备上:计算器 手表、iso:SQLite数据库

SQLite3支持五种数据类型NULL,INTEGER,REAL(浮点数),TEXT(字符串文本)、BLOB(二进制对象) 虽然只有五种,但实际上也支持varchar(n),char(n),decimal(p,s)等数据类型;因为SQLite3有个最大的特点: 你可以各种数据类型的数据保存到任何字段中,而不用关心字段声明的数据类型,比如,可以在Integer类型的字段中存放字符串,但是声明为主键PRIMARY KEY的一般要求是_id(id),字段只能够存储64位INTEGER 类型的整数 另外, SQLite 在解析CREATE TABLE 语句时, 会忽略 CREATE TABLE 语句中跟在字段名后面的数据类型信息如下面语句会忽略 name字段的类型信息: CREATE TABLE person (personid integer primary key autoincrement, name varchar(20))

**主键:**要求1 _id为标准写法,id也行,要求2 主键只能是Integer类型 **其他字段:**无论是什么数据类型,最终内部都会转变为text格式

**特点:**1、SQLite是一个轻量级的关系型数据库,运算速度快,占用资源少,很适合在移动设备上使用, 不仅支持标准SQL语法,还遵循ACID(数据库事务)原则,无需账号

​ 2、android里面的数据库是由底层的sqlite.c的代码来动态生成的,而MySQL用户需要手动创建

例子:增删改查

涉及到的类:

  • SQLiteOpenHelper:作为工具类,抽象类,通过继承该类,然后重写数据库创建以及更新的方法, 我们还可以通过该类的对象获得数据库实例,或者关闭数据库!
  • SQLiteDatabase:数据库访问类:我们可以通过该类的对象来对数据库做一些增删改查的操作
  • Cursor:游标,有点类似于JDBC里的resultset,结果集!可以简单理解为指向数据库中某 一个记录的指针!

MySQLiteOpenHelper作为工具类

/**
 *  MySQLiteOpenHelper 作为工具类
 *  使用单例模式(1.构造函数私有化,不让外界访问  2.向外提供访问该类对象的方法)
 */
public class MySQLiteOpenHelper extends SQLiteOpenHelper {

    // 2.向外提供访问该类对象的方法
    // 保存该类对象的一个实例,懒汉式的作法即在声明的同时并不初始化对象
    private static SQLiteOpenHelper mInstance = null;
    //构造器需要Context对象,所以添加Context对象
    public static synchronized SQLiteOpenHelper getInstance(Context context) {
        if (mInstance == null) {
            // 以后想要数据库升级 修改成2  再升级修改为3
            mInstance = new MySQLiteOpenHelper(context,"shroudDB.db",null,1);
        }
        return mInstance;
    }

    // 1.构造函数私有化
    private MySQLiteOpenHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }

    //  数据库初始化时用    创建表;
    //  注意:表数据初始化,数据库第一次创建的时候调用;
    //       第二次若有了,则不会重复创建;意味着:此函数只会执行一次
    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        //  创建表:主键(primary key唯一)最好为_id,并且只能为Integer类型
        //  自动增长关键字:autoincrement
        String sql = "create table persons(_id integer primary key autoincrement, name text)";

        sqLiteDatabase.execSQL(sql);
    }

    //  数据库升级时用
    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {

    }
}

修改数据遇到错误:java.lang.IllegalStateException: Could not execute method for android:onClick,首先检查sql语句

在这里插入图片描述

MainActivity

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    // 生成DB文件
    public void createDB(View view) {

        // 单例模式不能通过构造器实例化对象,通过调用类的方法进行实例化对象
        SQLiteOpenHelper helper = MySQLiteOpenHelper.getInstance(this);

        // helper.getReadableDatabase()/helper.getWritableDatabase()
        // databases 文件夹的创建,依托这两句话
        SQLiteDatabase readableDatabase = helper.getReadableDatabase();

    }

    //  查询DB文件
    public void queryDB(View view) {

        SQLiteOpenHelper helper = MySQLiteOpenHelper.getInstance(this);

        //读操作
        SQLiteDatabase rdb = helper.getReadableDatabase();

        // 数据库打开成功 返回true 进入if
        if (rdb.isOpen()) {
            //返回为游标
            Cursor cursor = rdb.rawQuery("select * from persons", null);

            //迭代游标  往下面移动来遍历数据
            while (cursor.moveToNext()) {
                // 偷懒的写法
                //int _id = cursor.getInt(0);
                //String name = cursor.getString(1);

                // 规范写法:通过游标去获取查询数据
                @SuppressLint("Range") int _id = cursor.getInt(cursor.getColumnIndex("_id"));
                @SuppressLint("Range") String name = cursor.getString(cursor.getColumnIndex("name"));

                Log.d("shroud","query: _id:" + _id + " name:" + name);
            }

            // 一定记得关闭游标 否则耗费性能 规范写法
            cursor.close();
            // 数据库也要关闭 规范写法
            rdb.close();
        }

    }

    //  插入数据不需要游标;需要使用写。
    public void insertDB(View view) {

        SQLiteOpenHelper helper = MySQLiteOpenHelper.getInstance(this);

        //写操作
        SQLiteDatabase wdb = helper.getWritableDatabase();

        // 数据库打开成功 返回true 进入if
        if (wdb.isOpen()) {
            // 插入语句
            String sql = "insert into persons(name) values('Shroud老师')";
            //执行sql语句
            wdb.execSQL(sql);

            // 关闭数据库
            wdb.close();
        }
    }

    //  修改数据
    public void updateDB(View view) {

        SQLiteOpenHelper helper = MySQLiteOpenHelper.getInstance(this);

        //写操作
        SQLiteDatabase wdb = helper.getWritableDatabase();

        //数据库打开成功 返回true
        if (wdb.isOpen()) {
            // 修改语句
            String sql = "update persons set name=? where _id=?";

            //执行sql语句,顺便填充占位符
            wdb.execSQL(sql,new Object[]{"谭章竦",3});

            // 关闭数据库
            wdb.close();
        }
    }

    // 删除数据
    public void deleteDB(View view) {

        SQLiteOpenHelper helper = MySQLiteOpenHelper.getInstance(this);

        //写操作
        SQLiteDatabase wdb = helper.getWritableDatabase();

        //数据打开成功 进入
        if (wdb.isOpen()) {
            String sql = "delete from persons where _id=?";
            wdb.execSQL(sql,new Object[]{4});

            //关闭数据库
            wdb.close();
        }
    }
}

Room数据库

Room是SQLite数据库的抽象,流畅易用的访问数据库

SQLite:需要帮助类、SQL语句、执行SQL、创建表等等,繁琐

Room最大的好处就是,省去了写sql语句或增删改查的代码,利用注解自动生成,然后通过依赖中的包自动去查询

Room(SQLite的封装)

  • @Entity:用来封装实际数据的实体类,它和数据库中的表一一对应,有几张表就建立几个实体类,建立好Entity后,表中各列会自动生成。
  • @DAO:数据操作对象,将对数据库的各项操作都封装在这里,逻辑层直接和Dao打交道,不直接操作数据库。
  • @Database数据库:定义数据库的关键信息,如版本号,包含的实体类,以及提供Dao层的访问实例。必须是扩展 RoomDatabase 的抽象类

例子:学生数据库

创建一个学生数据库(StudentDB), 这个数据库有一张 学生表(StudentEntity),StudentDao 用于提供对学生表的各种增删查改

在这里插入图片描述

build.gradle导入依赖

// Room的API支持
implementation "androidx.room:room-runtime:2.4.2"
// Room的注解处理器
annotationProcessor "androidx.room:room-compiler:2.4.2"

Student.class创建Entity类

类前加上@Entity来标识这是一个数据库实体,在编译时,系统会根据这个类的设定来创建数据库。

如果Entity()的参数为空,系统在创建数据库时,会把类名作为数据库的表名,如果要自定义表名,可以直接在Entity()里输入参数:@Entity(tableName = “yourTableName”)

@Entity // 一张表(主键唯一 主键自动增长, name,age)
public class Student {

    // 主键唯一 主键自动增长
    @PrimaryKey(autoGenerate = true)
    private int id;
    //@ColumnInfo(name = )
    private String name;
    private int age;

    public Student(String name, int age) {this.name = name;this.age = age;}

    public int getId() {return id;}

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

    public String getName() {return name;}

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

    public int getAge() {return age;}

    public void setAge(int age) {this.age = age;}

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

StudentDao.java定义DAO接口

这里定义的是一个接口,提供了对数据库的各项操作,如增删改查等功能,可以按照自己的需求定制

@Dao //Database access object == 对表进行 增删改查
public interface StudentDao {

    // 增
    //可变形参 可以理解为 传入String[] students
    @Insert
    void insertStudents(Student ... students);

    // 改
    @Update
    void updateStudents(Student... students);

    // 删除 带条件
    @Delete
    void deleteStudents(Student... students);

    // 删所有  @Delete单个条件删除
    // 这里是删除操作,但是用的是Query注解,因为Delete注解不能加sql语句
    @Query("DELETE FROM Student")
    void deleteAllStudents();

    // 查所有
    @Query("SELECT * FROM Student ORDER BY ID DESC")
    List<Student> getAllStudent();
}

StudentDatabase.java创建database类

将创建database类,通过继承改写room的database,把Student.classStudentDao.class联系在一起,组成一个完整的数据库。

对于获取BookDao实例的方法,我们也只需要声明方法即可,底层会自动完成。

/**
 *  数据库 关联 表与数据库信息(Entity与DAO)
 *  不重写里面的方法,直接让他变为抽象类
 * entities:传入所有Entity的class对象;
 * version:数据库版本号。
 * exportSchema:设置是否导出数据库schema,默认为true
 */
@Database(entities = {Student.class},version = 1,exportSchema = false)
public abstract class StudentDatabase extends RoomDatabase {

    // Database()中,已经把Entity连接到了database中,
    // 在Dao类中,需要把XxxDao类连接到数据库中,就需要实例化一个XxxDao类
    // 用户只需要操作Dao,则需要暴露Dao,用户拿到Dao,就能做数据库增删改查
    public abstract StudentDao getStudentDao();

    // 使用单例模式 返回DB,完成数据库的构建
    private static StudentDatabase INSETANCE;
    public static synchronized StudentDatabase getInstance(Context context) {
        if (INSETANCE == null) {
            //context:为了保持数据库环境一致.get
            //class:@database标记的类
            //name:数据库保存之后文件的文件名
            INSETANCE = Room.databaseBuilder
                    (context.getApplicationContext(),StudentDatabase.class,"student_database")
                    //强制要求它在主线程执行,默认是异步线程
                    //慎用:强制开启主线程也可以操作数据库(测似可用,真实环境下别用,会引发卡顿)
                    //.allowMainThreadQueries()
                    //构建
                    .build();
        }
        return INSETANCE;
    }
}

DBEngine为DB的引擎,里面完成异步的操作

// DB的引擎
public class DBEngine {

    //我只需要拿到dao,就能够对数据库 增删改查
    private StudentDao studentDao;

    public DBEngine(Context context) {
        StudentDatabase studentDatabase = StudentDatabase.getInstance(context);
        studentDao = studentDatabase.getStudentDao();
    }

    // dao 增删改查:

    // insert 插入
    public void insertStudents(Student... students) {
        new InsertAsyncTack(studentDao).execute(students);
    }
    // update 更新
    public void updateStudents(Student... students) {
        new UpdateAsyncTack(studentDao).execute(students);
    }
    // delete 有条件删除
    public void deleteStudents(Student... students) {
        new DeleteAsyncTack(studentDao).execute(students);
    }
    // delete 全部删除
    public void deleteAllStudents() {
        new DeleteAllAsyncTack(studentDao).execute();
    }
    // select 全部查询
    public void queryStudents() {
        new QueryAllAsyncTack(studentDao).execute();
    }

    //不适用强制主线程,使用异步操作
    // insert 插入异步操作
    static class InsertAsyncTack extends AsyncTask<Student, Void, Void> {

        private StudentDao dao;

        public InsertAsyncTack(StudentDao studentDao) {
            this.dao = studentDao;
        }
        @Override
        protected Void doInBackground(Student... students) {
            dao.insertStudents(students);
            return null;
        }
    }

    // update 更新异步操作
    static class UpdateAsyncTack extends AsyncTask<Student, Void, Void>{

        private StudentDao dao;

        public UpdateAsyncTack(StudentDao studentDao) {
            this.dao = studentDao;
        }

        @Override
        protected Void doInBackground(Student... students) {
            dao.updateStudents(students);
            return null;
        }
    }

    // Delete 有条件删除异步操作
    static class DeleteAsyncTack extends AsyncTask<Student, Void, Void>{

        private StudentDao dao;

        public DeleteAsyncTack(StudentDao studentDao) {
            this.dao = studentDao;
        }

        @Override
        protected Void doInBackground(Student... students) {
            //参数传递students,带条件删除
            dao.deleteStudents(students);
            return null;
        }
    }

    // Delete 全部删除异步操作
    static class DeleteAllAsyncTack extends AsyncTask<Void, Void, Void>{

        private StudentDao dao;

        public DeleteAllAsyncTack(StudentDao studentDao) {
            this.dao = studentDao;
        }

        @Override
        protected Void doInBackground(Void... voids) {
            dao.deleteAllStudents();
            return null;
        }
    }

    // select 全部查询异步操作
    static class QueryAllAsyncTack extends AsyncTask<Void, Void, Void> {

        private StudentDao dao;

        public QueryAllAsyncTack(StudentDao studentDao) {
            this.dao = studentDao;
        }

        @Override
        protected Void doInBackground(Void... voids) {
            List<Student> allStudent = dao.getAllStudent();
            // 遍历查询结果
            for (Student student : allStudent) {
                Log.d("DBEngine", "doInBackground: 全部查询每一项:" + student.toString());
            }
            return null;
        }
    }
}

RoomActivity中,直接调用DBEngine.方法就行了

//Room操作
public class RoomActivity extends AppCompatActivity {

    private DBEngine dbEngine;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_room);

        // 初始化DBEngine
        dbEngine = new DBEngine(this);
    }

    // 插入
    public void insertAction(View view) {
        Student student1 = new Student("张三", 23);
        Student student2 = new Student("李四", 26);
        Student student3 = new Student("王五", 29);
        dbEngine.insertStudents(student1,student2,student3);
    }

    // 修改
    public void updateAction(View view) {
        Student student = new Student("谭章竦", 23);
        //修改id为3的
        student.setId(3);
        dbEngine.updateStudents(student);
    }

    // 条件删除
    public void deleteAction(View view) {
        Student student = new Student(null,0);
        //删除id为3的
        student.setId(3);
        dbEngine.deleteStudents(student);
    }

    // 全部删除
    public void deleteAllAction(View view) {
        dbEngine.deleteAllStudents();
    }

    // 全部查询
    public void queryAllAction(View view) {
        dbEngine.queryStudents();
    }
}

流行框架

OKHttp

OkHttp是一个优秀的网络请求框架,是一个处理网络请求的开源项目,Android使用最广泛的网络框架。

OKHttp的基本使用

1、导入添加OkHttp的依赖(build.gradle)

//1、导入OKHttp依赖
implementation("com.squareup.okhttp3:okhttp:4.9.0")

2、网络请求需要在清单文件中进行权限注册(AndroidManifest.xml)

<!--2、加入权限注册-->
<uses-permission android:name="android.permission.INTERNET"/>

3、简单布局(activity_main.xml)

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical"
    android:gravity="center">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="getSync"
        android:text="GET同步请求"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="getAsync"
        android:text="GET异步请求"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="postSync"
        android:text="POST同步请求"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="postAsync"
        android:text="POST异步请求"/>

</LinearLayout>

4、编写get请求(同步|异步)、post请求(同步|异步)

public class MainActivity extends AppCompatActivity {

    private OkHttpClient okHttpClient;
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //创建OKHttpClient对象
        okHttpClient = new OkHttpClient();
    }

    // 1、GET同步请求
    public void getSync(View view) {
        //4、在android中,想要完成同步请求,需要放在子线程,所以要放入线程中.
        new Thread(){
            @Override
            public void run() {
                //1、GET请求通过url传输参数,最后调用build进行传输
                Request request = new Request
                        .Builder()
                        .url("http://www.httpbin.org/get?a=1&b=2")
                        //可写可省略,默认就是get请求
                        .get()
                        .build();
                //2、将请求传递给okhttpClient,准备好请求的Call对象
                Call call = okHttpClient.newCall(request);
                try {
                    //3、同步请求调用execute(),得到响应,OKHttp帮我们封装在里面
                    Response response = call.execute();
                    //打印响应体(字符串)
                    Log.i(TAG, "getSync: "+response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }

    // 2、GET异步请求
    public void getAsync(View view) {
        //4、异步请求不需要放在线程中,enqueue内部会创建子线程,
        //      同步执行execute会进行阻塞所以需要
        //1、GET请求通过url传输参数,最后调用build进行传输
        Request request = new Request
                .Builder()
                .url("http://www.httpbin.org/get?a=1&b=2")
                .get()
                .build();
        //2、将请求传递给okhttpClient,准备好请求的Call对象
        Call call = okHttpClient.newCall(request);
        //3、异步请求调用enqueue(),传入Callback对象
        call.enqueue(new Callback() {
            //请求失败调用
            @Override
            public void onFailure(@NonNull Call call, @NonNull IOException e) {
            }

            //请求结束调用
            // 异步请求得到了请求,无论响应码是什么(200\500\404),都会回调onResponse
            // onResponse并不意味着Http通信成功,只是表明服务器的通信是成功的,服务器处理http数据不一定成功。
            @Override
            public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
                //判断response是否是成功的,响应码200..299
                if (response.isSuccessful()) {
                    //打印响应体(字符串)
                    Log.i(TAG, "getAsync: " + response.body().string());
                }
            }
        });
    }

    // 3、POST同步请求
    public void postSync(View view) {
        //2.1、使用form表单存储请求体,传入给post()方法
        FormBody formBody = new FormBody
                .Builder()
                .add("a", "1").add("b", "2")
                .build();

        //5、在android中,想要完成同步请求,需要放在子线程,所以要放入线程中.
        new Thread() {
            @Override
            public void run() {
                //1、POST请求通过url传输参数,最后调用build进行传输
                Request request = new Request
                        .Builder()
                        .url("http://www.httpbin.org/post")
                        //2、post请求,需要传入请求体
                        .post(formBody)
                        .build();
                //3、将请求传递给okhttpClient,准备好请求的Call对象
                Call call = okHttpClient.newCall(request);

                try {
                    //4、同步请求调用execute(),得到响应,OKHttp帮我们封装在里面
                    Response response = call.execute();
                    //打印响应体(字符串)
                    Log.i(TAG, "postSync: "+response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }

    // 4、POST异步请求
    public void postAsync(View view) {
        //2.1、使用form表单存储请求体,传入给post()方法
        FormBody formBody = new FormBody
                .Builder()
                .add("a", "1").add("b", "2")
                .build();

        //1、POST请求通过url传输参数,最后调用build进行传输
        Request request = new Request
                .Builder()
                .url("http://www.httpbin.org/post")
                //2、post请求,需要传入请求体
                .post(formBody)
                .build();

        //3、将请求传递给okhttpClient,准备好请求的Call对象
        Call call = okHttpClient.newCall(request);

        //4、异步请求调用enqueue(),传入Callback对象
        call.enqueue(new Callback() {
            //请求失败调用
            @Override
            public void onFailure(@NonNull Call call, @NonNull IOException e) {
            }

            //请求结束调用
            // 异步请求得到了请求,无论响应码是什么(200\500\404),都会回调onResponse
            // onResponse并不意味着Http通信成功,只是表明服务器的通信是成功的,服务器处理http数据不一定成功。
            @Override
            public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
                //判断response是否是成功的,响应码200..299
                if (response.isSuccessful()) {
                    //成功打印请求体
                    Log.i(TAG, "postAsync: " + response.body().string());
                }
            }
        });

    }
}

若出现异常:java.lang.IllegalStateException: Could not execute method for android:onClick

先检查url地址知否正确,是否与get、post请求匹配

POST请求

协议规定POST提交的数据必须放在请求体中,但协议并没有规定数据必须使用什么编码方法,

而常用的数据编码方式有:

https://www.runoob.com/http/http-content-type.html

  • Content-Type:application/x-www-form-urlencoded

​ 数据被编码为名称/键值对,默认类型;

  • Content-Type:multipart/form-data

​ 数据编码为一条消息,一般用于文件上传;

  • Content-Type:application/form-data

​ 提交二进制数据,如果用于文件上传,只能上传一个文件;

  • Content-Type:application/json

    ​ 提交json数据

测试类中实现。

public class UploadFileUnitTest {

    // 提交多个文件application/form-data,上传
    @Test
    public void uploadFileTest() throws IOException {
        OkHttpClient okHttpClient = new OkHttpClient();

        // 参数2:文件名
        File fileA = new File("C:\\Users\\Shroud\\Desktop\\1.txt");
        File fileB = new File("C:\\Users\\Shroud\\Desktop\\2.txt");

        // 参数3: 文件、MediaType.parse(文件格式),create 构建
        RequestBody requestBodyA = RequestBody.create(fileA, MediaType.parse("text/plain"));
        RequestBody requestBodyB = RequestBody.create(fileA, MediaType.parse("text/plain"));

        //使用MultipartBody的方式提交
        MultipartBody multipartBody = new MultipartBody.Builder()
                // 参数1:定义服务器接收的key名 参数2:文件名
                .addFormDataPart("file1", fileA.getName(), requestBodyA)
                .addFormDataPart("file2", fileB.getName(), requestBodyB)
                .addFormDataPart("a","1")
                .build();

        Request request = new Request
                .Builder()
                .url("http://www.httpbin.org/post")
                .post(multipartBody)
                .build();

        Call call = okHttpClient.newCall(request);
        // 同步提交/异步提交
        // 这里没有做处理,是因为在Java代码层面,操作的是电脑的txt文件
        Response response = call.execute();
        System.out.println(response.body().string());
    }

    // 提交一个文件application/octet-stream
    @Test
    public void jsonTest() throws IOException {
        OkHttpClient okHttpClient = new OkHttpClient();

        // 上传json数据,直接将json数据指定给create第一个参数
        RequestBody requestBody = RequestBody.create("{\"a\":1,\"b\":2}", MediaType.parse("application/json"));

        Request request = new Request.Builder().url("http://www.httpbin.org/post").post(requestBody).build();
        Call call = okHttpClient.newCall(request);
        Response response = call.execute();
        System.out.println(response.body().string());
    }
}

Builder构建者

OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
拦截器
OkHttpClient okHttpClient = new OkHttpClient.Builder().addInterceptor(new Interceptor()).build();
OkHttpClient okHttpClient = new OkHttpClient.Builder().addNetworkInterceptor(new Interceptor()).build();
OkHttp中addInterceptor和addNetworkInterceptor的区别:
1、addInterceptor
	# 有⽆⽹络都会被调⽤到。
	# 拦截器只会被调⽤⼀次,调⽤chain.proceed()得到的是重定向之后最终的响应信息,
	# 不会通过chain.connection() 获得中间过程的响应信息。
	# 允许短路,并且允许不去调⽤chain.proceed()请求服务器数据,可通过缓存来返回数据。
2、addNetworkInterceptor
	# ⽆⽹络时不会被调⽤。
	# 可以显⽰更多的信息,⽐如OkHttp为了减少数据的传输时间以及传输流量⽽⾃动添加的
	# 请求头Accept-Encoding: gzip,从⽽希望服务器能返回经过压缩过的响应数据。
	# chain.connection()返回不为空的Connection对象,可以查询到客户端所连接的服务器的IP地址以及TLS配置信息。

addInterceptor可以添加很多个拦截器,拦截器的执行顺序按照配置的顺序执行。

​ addInterceptor:一定在前面执行/或者按照配置执行

​ addNetwordInterceptor:一定在addInterceptor后面执行;

如果是添加了interceptor的OKHttpClient。

​ 在请求的过程中,会回调一次intercept方法,在方法中就能请求之前的前置处理,或者请求之后的后置处理。

​ 例如:需要统一要为请求头 添加版本号、平台名;

public class InterceptorUnitTest {

    /**
     * 如果是添加了interceptor的OKHttpClient,
     * 在请求的过程中,会回调一次intercept方法,
     * 在方法中就能请求之前的前置处理,或者请求之后的后置处理。
     * 例如:需要统一要为请求头 添加版本号、平台名、
     */
    @Test
    public void interceptorTest(){
        // 如果想要自己添加拦截器的OKHttpClient
        // 则new OkHttpClient.Builder().addInterceptor()
        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                // 开启缓存操作,参数1:缓存文件存放的位置;参数2:大小
                //.cache(new Cache(new File("C:\\Users\\Shroud\\Desktop"),1024*1024))
                .addInterceptor(new Interceptor() {
            @NonNull
            @Override
            public Response intercept(@NonNull Chain chain) throws IOException {
                // 前置处理
                // 这里就好比:将下面发起的request再去newBuilder创建一个一样的request对象
                Request request = chain.request().newBuilder()
                        // 添加请求头,
                        .addHeader("os", "android")
                        .addHeader("version", "1.0")
                        .build();
                // 发送给服务器,就不需要在每一个request上添加请求头,
                // 所有使用自己定义okHttpClient的请求之前都会做前置处理
                Response response = chain.proceed(request);
                return response;
            }
        }).addNetworkInterceptor(new Interceptor() {
                    @NonNull
                    @Override
                    public Response intercept(@NonNull Chain chain) throws IOException {
                        String version = chain.request().header("version");
                        //打印版本号
                        System.out.println("version:" + version);
                        //调用Chain的proceed()方法,让其继续下去
                        Response response = chain.proceed(chain.request());
                        return response;
                    }
                }).build();

        // 不管get、post、同步、异步,正常写,使用的okHttpClient是自己定义带拦截器的
        Request request = new Request.Builder().url("http://www.httpbin.org/get?a=1&b=2").get().build();
        // 准备好请求的call对象
        Call call = okHttpClient.newCall(request);
        // 同步请求调用execute方法
        try {
            Response response = call.execute();
            System.out.println(response.body().string());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
缓存

OkHttp按照Http协议规则实现了缓存的处理,缓存是比如:当我们发起第一次请求之后,如果后续还需要进行同样的请求,此时如果符合缓存规则,则可以减少与服务器的网络通信,直接从本地文件缓存中读取响应返回给请求者。但是默认情况下,OkHttp的缓存是关闭状态,需要我们手动开启。

//pathname: 缓存文件地址
//maxSize: 缓存最大容量字节

OkHttpClient okHttpClient = new OkHttpClient.Builder()
        // 开启缓存操作,参数1:缓存文件存放的位置/path/cache;参数2:大小
        .cache(new Cache(new File("C:\\Users\\Shroud\\Desktop"),1024*1024)).build();
Cookie

Cookie是某些网站为了辨别用户身份,进行会话跟踪(比如确定登录状态、判断VIP权限用户),而存储在用户本地终端上的数据(通常经过加密),由用户客户端计算机暂时或永久保存的信息。

Cookie可以根据场景选择保存在文件里面-持久化cookie,或者 内存(集合\变量)-临时cookie

CookieUnitTest测试类

public class CookieUnitTest {

    // 1、定义全局变量,用于只在内存中保存Cookie,最好使用Map集合
    Map<String, List<Cookie>> cookies = new HashMap<>();

    @Test
    public void cookieTest() {

        // new cookieJar,拿到Cookie数据,以便以后进行登录
        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .cookieJar(new CookieJar() {
                    // 将Cookie保存,封装在List集合中
                    @Override
                    public void saveFromResponse(@NonNull HttpUrl httpUrl, @NonNull List<Cookie> list) {
                        // 利用全局变量cookies来接收,以网站作为key,来保存cookie
                        cookies.put(httpUrl.host(), list);
                    }

                    // httpUrl会接收到url与接口
                    @NonNull
                    @Override
                    public List<Cookie> loadForRequest(@NonNull HttpUrl httpUrl) {
                        // 通过HttpUrl对象获取cookie
                        List<Cookie> cookieList = cookies.get(httpUrl.host());
                        return cookieList == null ? new ArrayList<>() : cookieList;
                    }
                }).build();

        // 正常的请求方法编写
        //2.1、使用form表单存储请求体,传入给post()方法
        FormBody formBody = new FormBody
                .Builder()
                .add("username", "还装起来了")
                .add("password", "tzs101699.")
                .build();

        //1、POST请求通过url传输参数,最后调用build进行传输
        Request request = new Request
                .Builder()
                .url("https://www.wanandroid.com/user/login")
                //2、post请求,需要传入请求体
                .post(formBody)
                .build();
        //3、将请求传递给okhttpClient,准备好请求的Call对象
        Call call = okHttpClient.newCall(request);

        try {
            //4、同步请求调用execute(),得到响应,OKHttp帮我们封装在里面
            Response response = call.execute();
            // 这里登录的结果可以通过response拿到
            System.out.println(response.body().string());
        } catch (IOException e) {
            e.printStackTrace();
        }


        // 2、根据拿到的Cookie,为登录状态,获取收藏列表显示
        // 如果没有.cookieJar(new CookieJar())操作,会提示请先登录!
        // 因为想要请求收藏列表显示,必须是登录的状态
        request = new Request.Builder().url("https://www.wanandroid.com/lg/collect/list/0/json")
                .get().build();

        //将请求传递给okhttpClient,准备好请求的Call对象
        call = okHttpClient.newCall(request);

        try {
            Response response = call.execute();
            System.out.println(response.body().string());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Retrofit

Retrofit就是对OkHttp的封装。不是网络请求框架,是封装框架
使用的时候,不需要自己创建:RequestBody、Request
它利用注解帮助我们封装好自动生成请求和请求体

我们只用拿到接口对象之后,调用接口相关的方法get、post请求,就可以完成对Http的请求。

解决了:OkHttp在开发中的一些痛点。具体的来说:

  1. 使用过程中接口配置繁琐,OkHttp中每发起一个请求都要新建一个Request,当要配置复杂请求(body,请求头,参数)
    时尤其复杂。
  2. 需要用户拿到responseBody后自己手动解析,解析工作应该是可以封装的。
  3. 无法适配自动进行线程切换。
  4. 嵌套网络请求会陷入“回调陷阱”

基本使用

在这里插入图片描述

1、引入retrofit依赖

//引入retrofit依赖,封装了OkHttp,不用引入OkHttp也能用OkHttp
implementation 'com.squareup.retrofit2:retrofit:2.9.0'

2、AndroidManifest.xml加入权限

<!--2、加入权限注册-->
<uses-permission android:name="android.permission.INTERNET"/>

3、HttpbinService,一、根据接口(图片内容)创建Java接口

// 一、根据接口创建Java接口
public interface HttpbinService {

    /**
     * @Query("username")与@Field("username")里面的参数
     * 最终就是访问地址上的参数,例如:https://httpbin.org/post  username=values
     */
    @GET("get")
    Call<ResponseBody> get(@Query("username") String userName,@Query("password") String pwd);

    // FormUrlEncoded提交方式,以Form表单
    @POST("post")
    @FormUrlEncoded
    Call<ResponseBody> post(@Field("username") String userName,@Field("password") String pwd);
}

4、MainActivity中:二、创建Retrofit对象,并生成接口实现类对象 三、接口实现类对象调用对应方法获得响应

public class MainActivity extends AppCompatActivity {

    private Retrofit retrofit;
    private HttpbinService httpbinService;

    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 二、创建Retrofit对象,并生成接口实现类对象
        // .baseUrl("https://www.httpbin.org")传入服务器域名
        retrofit = new Retrofit.Builder().baseUrl("https://www.httpbin.org").build();
        httpbinService = retrofit.create(HttpbinService.class);
    }

    // POST异步请求
    public void postAsync(View view) {

        // 三、接口实现类对象调用对应方法获得响应
        Call<ResponseBody> call = httpbinService.post("lance", "123");
        call.enqueue(new Callback<ResponseBody>() {
            //请求结束调用
            // 异步请求得到了请求,无论响应码是什么(200\500\404),都会回调onResponse
            // onResponse并不意味着Http通信成功,只是表明服务器的通信是成功的,服务器处理http数据不一定成功。
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                try {
                    Log.i(TAG, "onResponse: " + response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            //请求失败调用
            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
            }
        });
    }
}

Retrofit的注解

方法注解:@GET,@POST,@PUT,@DELETE,@PATH,@HEAD,@OPTIONS,@HTTP

标记注解:@FormUrlEncoded,@Multipart,@Streaming

参数注解:@Query,@QueryMap,@Body,@Field,@FieldMap,@Part,@PartMap

其他注解:@Path,@Header,@Headers,@Url

1、引入retrofit依赖

//引入retrofit依赖,封装了OkHttp,不用引入OkHttp也能用OkHttp
implementation 'com.squareup.retrofit2:retrofit:2.9.0'

2、AndroidManifest.xml加入权限

<!--2、加入权限注册-->
<uses-permission android:name="android.permission.INTERNET"/>

3、HttpbinService,一、根据接口(图片内容)创建Java接口

// 一、根据接口创建Java接口
public interface HttpbinService {

    // 以下在测试类中进行 =========================================
    
    // 自己定义method的请求方式(未写例子)
    @HTTP(method = "GET",path = "get",hasBody = false)
    Call<ResponseBody> http(@Field("username") String userName,@Field("password") String pwd);

    // 可以直接传入json字符串(未写例子)
    @POST("post")
    @FormUrlEncoded
    Call<ResponseBody> postJson(@Field("json") String json);

    //@Body注解
    @POST("post")//@FormUrlEncoded不能使用标记注解,使用Body需要自己去代码定义传递类型
    Call<ResponseBody> postBody(@Body RequestBody body);

    //例如需要访问第几页数据
    //@POST("/xxx/{pageNum}")
    //Call<ResponseBody> postInPath(@Path("pageNum") String path);

    //@Path注解、@Header注解
    @POST("{id}")
    @FormUrlEncoded
    Call<ResponseBody> postInPath(@Path("id") String path,
                                  @Header("os") String os,
                                  @Field("username") String userName,
                                  @Field("password") String pwd);

    //@Headers写死多个请求头,类似于OkHttp里面的添加拦截器加入请求头例子
    @Headers({"os:android","version:1.0"})
    @POST("post")
    Call<ResponseBody> postWithHeaders();

    //使用@Url注解,POST后面不能再加post,在代码参数url里面指定post/get,编写固定完整的url地址
    @POST
    Call<ResponseBody> postUrl(@Url String url);

}

4、AnnotationUnitTest,测试类

public class AnnotationUnitTest {

    // 二、创建Retrofit对象,并生成接口实现类对象
    // .baseUrl("https://www.httpbin.org")传入服务器域名
    Retrofit retrofit = new Retrofit.Builder().baseUrl("https://www.httpbin.org").build();
    HttpbinService httpbinService = retrofit.create(HttpbinService.class);

    @Test
    public void bodyTest() throws IOException {

        //使用@Body注解需要自己定义请求方式,这里是form表单方式
        FormBody formBody = new FormBody.Builder()
                .add("a","1").add("b","2").build();
        Response<ResponseBody> response = httpbinService.postBody(formBody).execute();
        System.out.println(response.body().string());
    }

    @Test
    public void pathTest() throws IOException {
        /**
         * 请求地址就为https://www.httpbin.org/post
         *   "form": {
         *     "password": "123",
         *     "username": "shroud"
         *   },
         *
         *   "headers": {
         *     "Os": "android",
         *   }
         */
        Response<ResponseBody> response = httpbinService.postInPath("post","android","shroud","123").execute();
        System.out.println(response.body().string());
    }


    @Test
    public void headersTest() throws IOException {
        /**
         * "headers": {
         *     "Os": "android",
         *     "Version": "1.0",
         *   },
         */
        Response<ResponseBody> responseBodyCall = httpbinService.postWithHeaders().execute();
        System.out.println(responseBodyCall.body().string());
    }

    @Test
    public void urlTest() throws IOException {
        Response<ResponseBody> response = httpbinService.postUrl("https://www.httpbin.org/post").execute();
        System.out.println(response.body().string());
    }
}

Retrofit转换器

接到服务器的响应后,无论是OkHttp还是Retrofit都只能接收到String字符串类型的数据,实际开发中,需要将字符串进行解析将其转变为一个Java Bean对象,例如:服务器响应数据为JSON格式字符串,自己可以利用GSON库完成反序列化的操作,而Retrofit转换器能够完成自动的数据转换,记住Retrofit有许多的转换器使得能够完成自动的数据转换。

以下为JSON解析为例:

 登录			"还装起来了", "tzs101699."
https://www.wanandroid.com/user/login

方法:POST
参数:
	username,password

在这里插入图片描述

使用JSON工具,生成Java实体类,BaseResponse为主类,Data为主类中的一个属性

1、引入retrofit依赖,build.gradle

//引入Gson库
implementation 'com.google.code.gson:gson:2.8.6'
//引入Retrofit-json转换器,用引入Gson库也能用Gson
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'

2、因为是网络请求,清单文件中加入权限注册,AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET"/>

3、根据访问参数,定义访问接口WanAndroidService

public interface WanAndroidService {

    @POST("user/login")
    @FormUrlEncoded
    Call<ResponseBody> login(@Field("username") String username,@Field("password") String pwd);

    //直接修改泛型ResponseBody为BaseResponse
    // 因为ResponseBody只能返回字符串
    @POST("user/login")
    @FormUrlEncoded
    Call<BaseResponse> loginConvert(@Field("username") String username, @Field("password") String pwd);

}

4、在测试类中进行操作,一、不转换器(需要自己手动进行序列化或反序列化操作);二、使用转换器(转换器内部实现New Gson操作)

public class WanAndroidUnitTest {

    // 一、使用login方法,自己手动反序列化
    //创建Retrofit对象
    Retrofit retrofit1 = new Retrofit.Builder()
            .baseUrl("https://www.wanandroid.com/")
            .build();
    WanAndroidService wanAndroidService1 = retrofit1.create(WanAndroidService.class);

    @Test
    public void loginTest() throws IOException {

        Call<ResponseBody> call = wanAndroidService1.login("还装起来了", "tzs101699.");

        Response<ResponseBody> response = call.execute();

        String string = response.body().string();
        System.out.println(string);

        // 自己手动进行序列化数据,将字符串反序列化转换为BaseResponse对象
        BaseResponse baseResponse = new Gson().fromJson(string, BaseResponse.class);
        System.out.println(baseResponse);
    }


    // 二、使用loginConvert,就不需要自己进行反序列化
    //创建Retrofit对象,并添加转换器
    Retrofit retrofit2 = new Retrofit.Builder()
            .baseUrl("https://www.wanandroid.com/")
            //添加转换器,实际上转换器内部就实现了New Gson的操作
            .addConverterFactory(GsonConverterFactory.create())
            .build();
    WanAndroidService wanAndroidService2 = retrofit2.create(WanAndroidService.class);

    @Test
    public void loginConvertTest() throws IOException {

        Call<BaseResponse> call = wanAndroidService2.loginConvert("还装起来了", "tzs101699.");
        Response<BaseResponse> response = call.execute();
        System.out.println(response);

        BaseResponse body = response.body();
        System.out.println(body);
    }
}

Retrofit嵌套请求与适配器

嵌套请求:实际开发中,可能存在:需要先请求A接口,再请求B接口的情况。例如下面场景,需要请求获取文章列表,但是需要先登录拿到Cookie才能请求收藏文章列表接口,此时请求就有了前后关联顺序。

适配器:Retrofit的接口方法返回类型必须是Call,希望能够将Call改为RxJava中的Observable,对于嵌套的情况,就能得到非常方便优雅的解决。这就是适配器的功能,如果想要返回的不是Call,适配器就能帮助转换为其他类型。

wanandroid的请求地址与参数

登录			"还装起来了", "tzs101699."
https://www.wanandroid.com/user/login

方法:POST
参数:
	username,password
https://www.wanandroid.com/lg/collect/list/0/json

方法:GET
参数: 页码:拼接在链接中,从0开始。

注:该接口支持传入 page_size 控制分页数量,取值为[1-40],不传则使用默认值,一旦传入了 page_size,后续该接口分页都需要带上,否则会造成分页读取错误。

1、引入rxjava、rxAndroid依赖,build.gradle

//引入rxjava
implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
//如果是做Android开发,引入了rxjava之后,还需要引入rxAndroid
implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'

2、因为是网络请求,清单文件中加入权限注册,AndroidManifest.xml

<!--2、加入权限注册-->
<uses-permission android:name="android.permission.INTERNET"/>

3、根据访问参数,定义访问接口WanAndroidService

public interface WanAndroidService {
    // 登录
    @POST("user/login")
    @FormUrlEncoded
    Flowable<BaseResponse> login2(@Field("username") String username, @Field("password") String pwd);

    // 获取收藏文章列表
    @GET("lg/collect/list/{pageNum}/json")
    Flowable<ResponseBody> getArticle(@Path("pageNum") int pageNum);
}

4、在测试类中进行操作

​ 配置OkHttpClient,定制callFactory->实现cookieJar

​ 添加转换器 添加适配器

public class WanAndroidUnitTest {
    /**
     * 切换线程,在Android中使用,这里是在Java中。
     * .observeOn(Schedulers.io())必须将网络单独切换到一个子线程
     * .subscribeOn(AndroidSchedulers.mainThread())观察的回调切换到Android的主线程
     * 这样就能保证对结果的回调是在主线程中进行的
     * .subscribe(new Consumer<BaseResponse>(){accept……//里面就能进行UI的操作})
     * flatMap:能够帮助我们根据login2请求的结果,生成一个新的Publisher,新的就是去请求第0页的收藏文章列表
     * 最终监听得到的结果,改为文章列表的返回值.subscribe(new Consumer<BaseResponse>()) ->ResponseBody
     */
    // 使用适配器,嵌套请求。(Gson+RxJava)

    // 定义全局变量,用于只在内存中保存Cookie,最好使用Map集合
    Map<String, List<Cookie>> cookies = new HashMap<>();
    Retrofit retrofit3 = new Retrofit.Builder()
            .baseUrl("https://www.wanandroid.com/")
            // Retrofit也能自己进行配置OkHttpClient,定制callFactory->实现cookieJar
            // 达到cookie登录的操作
            .callFactory(new OkHttpClient.Builder()
                    .cookieJar(new CookieJar() {
                        // 将Cookie保存,封装在List集合中
                        @Override
                        public void saveFromResponse(@NonNull HttpUrl url, @NonNull List<Cookie> list) {
                            cookies.put(url.host(), list);
                        }

                        // httpUrl会接收到url与接口
                        @NonNull
                        @Override
                        public List<Cookie> loadForRequest(@NonNull HttpUrl url) {
                            // 通过HttpUrl对象获取cookie
                            List<Cookie> cookieList = cookies.get(url.host());
                            return cookieList == null ? new ArrayList<>() : cookieList;
                        }
                    }).build())
            .addConverterFactory(GsonConverterFactory.create()) //添加转换器
            .addCallAdapterFactory(RxJava3CallAdapterFactory.create()) //添加适配器
            .build();
    WanAndroidService wanAndroidService3 = retrofit3.create(WanAndroidService.class);

    @Test
    public void rxjavaTest() {

        wanAndroidService3.login2("还装起来了", "tzs101699.")
                .flatMap(new Function<BaseResponse, Publisher<ResponseBody>>() {
                    @Override
                    public Publisher<ResponseBody> apply(BaseResponse baseResponse) throws Throwable {
                        // 请求文章收藏列表
                        return wanAndroidService3.getArticle(0);
                    }
                })
                .observeOn(Schedulers.io())
                // 这里是Java中,就直接new新线程
                .subscribeOn(Schedulers.newThread())
                // Object ->> 接口所设置的返回值ResponseBody返回
                .subscribe(new Consumer<ResponseBody>() {
                    @Override
                    public void accept(ResponseBody responseBody) throws Throwable {
                        System.out.println(responseBody.string());
                    }
                }, new Consumer<Throwable>() {//添加异常处理
                    @Override
                    public void accept(Throwable throwable) throws Throwable {
                        throwable.printStackTrace();
                    }
                });
        // 需要阻塞在这里,不然不会输出responseBody
        while (true) { }

    }
}

Retrofit文件上传与下载

1、引入retrofit、rxjava、rxAndroid依赖,build.gradle

//引入retrofit依赖,封装了OkHttp,不用引入OkHttp也能用OkHttp
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
//引入rxjava
implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
//如果是做Android开发,引入了rxjava之后,还需要引入rxAndroid
implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'

2、因为是网络请求,清单文件中加入权限注册,AndroidManifest.xml

<!--2、加入权限注册-->
<uses-permission android:name="android.permission.INTERNET"/>

3、编写文件上传下载的接口UploadAndDownloadService

public interface UploadAndDownloadService {

    // 如果是上传多个文件,直接使用@PartMap
    @POST("post")
    @Multipart
    Call<ResponseBody> upload(@Part MultipartBody.Part file);

    // 下载文件
    // 在下载文件中,加入@Streaming注解以流的方式非常关键,防止文件太大,导致内存溢出
    @Streaming
    @GET
    Call<ResponseBody> download(@Url String url);

    @Streaming
    @GET
    Flowable<ResponseBody> downloadRxJava(@Url String url);
}

4、在测试类中进行操作,这里是进行了一张图片的上传下载

注意:文件上传下载需要加入Retrofit的适配器,因为希望把接口中的Call对象转换成Flowable对象,需要使用适配器进行自动转换

public class UploadFileUnitTest {

    Retrofit retrofit = new Retrofit.Builder().baseUrl("https://www.httpbin.org").build();
    UploadAndDownloadService uploadAndDownloadService = retrofit.create(UploadAndDownloadService.class);

    //下载操作
    @Test
    public void uploadFileText() throws IOException {

        // 参数2:文件名
        File file1 = new File("C:\\Users\\Shroud\\Desktop\\1.txt");

        // 参数3: 文件、MediaType.parse(文件格式),create 构建
        RequestBody requestBody = RequestBody.create(MediaType.parse("text/pain"), file1);

        //使用MultipartBody的方式提交
        MultipartBody.Part part = MultipartBody.Part.createFormData("file1", "1.txt", requestBody);

        Call<ResponseBody> call = uploadAndDownloadService.upload(part);

        Response<ResponseBody> response = call.execute();

        System.out.println(response.body().string());
    }

    //上传操作Retrofit方式
    @Test
    public void downloadText() throws IOException {
        Response<ResponseBody> response = uploadAndDownloadService.download("https://cn.bing.com/th?id=OHR.GlastonburySolstice_ZH-CN9694169797_1920x1200.jpg&rf=LaDigue_1920x1200.jpg")
                .execute();
        // 下载文件通过流
        if (response.isSuccessful()) {
            // 服务器得到的数据
            InputStream inputStream = response.body().byteStream();
            // 下载完毕之后放在哪
            FileOutputStream fos = new FileOutputStream("C:\\Users\\Shroud\\Desktop\\sun.jpg");

            //数据的读入和写出操作
            int len = 0;//记录每次读入到buffer数组中的字符的个数
            byte[] buffer = new byte[4096];
            // 读到的数据放入再buffer中,读到-1表示读完
            while ((inputStream.read(buffer)) != -1) {
                //写出buffer,从0索引开始,每次读写出len个字符
                fos.write(buffer, 0, len);
            }

            // 关闭资源
            fos.close();
            inputStream.close();
        }
    }

    Retrofit retrofit1 = new Retrofit.Builder().baseUrl("https://www.httpbin.org")
            .addCallAdapterFactory(RxJava3CallAdapterFactory.create()) //添加适配器
            .build();
    UploadAndDownloadService downloadRxJavaService = retrofit1.create(UploadAndDownloadService.class);

    //上传操作RxJava方式
    @Test
    public void downloadRxJavaText() throws IOException {

        downloadRxJavaService.downloadRxJava("https://cn.bing.com/th?id=OHR.GlastonburySolstice_ZH-CN9694169797_1920x1200.jpg&rf=LaDigue_1920x1200.jpg")
                // 在map操作符中,直接将responseBody变为File
                .map(new Function<ResponseBody, File>() {
                    @Override
                    public File apply(ResponseBody responseBody) throws Throwable {
                        // 服务器得到的数据
                        InputStream inputStream = responseBody.byteStream();
                        File file = new File("C:\\Users\\Shroud\\Desktop\\sun.jpg");
                        // 下载完毕之后放在哪
                        FileOutputStream fos = new FileOutputStream(file);

                        //数据的读入和写出操作
                        int len = 0;//记录每次读入到buffer数组中的字符的个数
                        byte[] buffer = new byte[4096];
                        // 读到的数据放入再buffer中,读到-1表示读完
                        while ((inputStream.read(buffer)) != -1) {
                            //写出buffer,从0索引开始,每次读写出len个字符
                            fos.write(buffer, 0, len);
                        }
                        // 关闭资源
                        fos.close();
                        inputStream.close();
                        return file;
                    }
                }).subscribe(new Consumer<File>() {
            @Override
            public void accept(File file) throws Throwable {

            }
        });
        while (true){}
    }
}

RX思维

RX : 反应式 reactivex

RX思维 : 响应式编程思维 → 根据上一层的响应,影响下一层的变化(观察者设计模式)

RX思维是链条式思维 : 起点(被观察者Observable) → 终点(观察者Observer),以后加新的需求的话就加在起点和终点的中间。
比如: (起点)用户操作 → 请求服务器 → 服务器的响应 → 解析响应的数据(终点)
RXJava为什么把所有函数都 称为 操作符,因为我们的函数要操作的范围是:从起点到终点,因为我们的函数要去操作

为什么使用?
如果采用传统开发方式,每位开发者的思想都不一样,后面的人接手前面开发者的代码,就很痛苦(因为思维不同)

**RxJava 使用三步走 **
第一步:创建 Observable 被观察者
第二步:创建 Observer 观察者
第三步:使用 subscribe 进行订阅

RX思维下载图片

在这里插入图片描述

执行步骤:
当用户点击显示图片按钮的时候,会先进行检查工作,还未开始执行,.subscribe订阅了开始执行,之后执行第一步弹出加载框,第二步将String分发出去,第三步将String转换为Bitmap,将Bitmap流向终点,第四步显示图片,第五步隐藏加载框。

UI显示操作在异步操作中会报错,需要切换回UI主线程

导入依赖包

//引入rxjava
implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
//如果是做Android开发,引入了rxjava之后,还需要引入rxAndroid
implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'

加入网络请求权限注册

<uses-permission android:name="android.permission.INTERNET" />

UI控件

<!--通过RxJava显示图片-->
<ImageView
    android:id="@+id/Iv_image"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:onClick="showImageAction"
    android:text="图片显示加载功能" />

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:onClick="operatorAction"
    android:text="常用操作符" />

Activity

这里采用的操作符为just()(在RxJava中,所有的函数都被称为操作符,因为函数要去操作从起点流向终点),即将传送的数据以此发送出去,最多可传递10个参数,且要求是将原为String类型的path转换为Bitmap类型

下载图片需求使用:map()方法,下载图片的同时将String类型转换为Bitmap类型,在起点just(PATH)与终点new Observer<Bitmap>()之间添加map()方法并在里面写入具体实现

public class MainActivity extends AppCompatActivity {

    // 打印logcat日志的标签
    private final String TAG = MainActivity.class.getSimpleName();

    // 网络图片的链接地址
    private final static String PATH = "https://cn.bing.com/th?id=OHR.GlastonburySolstice_ZH-CN9694169797_1920x1200.jpg&rf=LaDigue_1920x1200.jpg";

    // 弹出加载框(正在加载中...)
    private ProgressDialog progressDialog;

    // ImageView控件,用来显示结果图像
    private ImageView imageView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        imageView = findViewById(R.id.Iv_image);
    }

    // 点击按钮,图片显示加载功能
    public void showImageAction(View view) {

        // TODO 第二步 检查工作 分发PATH-String类型的数据
        // RxJava中所有的函数都称为操作符
        // Observable被观察者作为-起点
        Observable.just(PATH)

                // TODO 第三步
                // 需求:001 图片下载需求 String --> Bitmap
                .map(new Function<String, Bitmap>() {
                    @Override
                    public Bitmap apply(String path) throws Throwable {
                        // 请求服务器操作会捕获异常
                        try {
                            // 模拟网络延迟
                            Thread.sleep(2000);

                            URL url = new URL(path);
                            // 打开连接请求服务器
                            HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
                            // 设置请求连接时长-5s(超过5s失败)
                            httpURLConnection.setConnectTimeout(5000);
                            // 拿到服务器的响应码    200成功 404..
                            int responseCode = httpURLConnection.getResponseCode();
                            // 如果请求状态码为200
                            if (responseCode == HttpURLConnection.HTTP_OK) {
                                // 得到的流就是我们要的照片
                                InputStream inputStream = httpURLConnection.getInputStream();
                                // 将流转换为Bitmap
                                Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
                                return bitmap;
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        return null;
                    }
                })

                // 需求:002 图片加水印需求 Bitmap --> Bitmap(水印版)
                .map(new Function<Bitmap, Bitmap>() {
                    @Override
                    public Bitmap apply(Bitmap bitmap) throws Throwable {
                        // 调用加水印
                        Paint paint = new Paint();
                        paint.setColor(Color.RED);
                        paint.setTextSize(88);
                        Bitmap markBitmap = drawTextToBitmap(bitmap, "我是Shroud", paint, 88, 88);
                        return markBitmap;
                    }
                })

                // 需求:003 日志记录需求
                .map(new Function<Bitmap, Bitmap>() {
                    @Override
                    public Bitmap apply(Bitmap bitmap) throws Throwable {
                        Log.e(TAG, "在什么时候下载了图片 apply: " + System.currentTimeMillis());
                        return bitmap;
                    }
                })

                // 给起点的请求服务器操作分配异步线程(图片下载操作)
                .subscribeOn(Schedulers.io())

                // 给终点分配Android主线程
                .observeOn(AndroidSchedulers.mainThread())

                // TODO 导火索开始执行
                // 关联:观察者设计模式,关联起点与终点 == 订阅
                // 因为PATH是String,所以这个是String,只要上一层变,就要跟随着变
                .subscribe(new Observer<Bitmap>() { //Observer观察者作为-终点
                    // TODO 第一步
                    // 起点与终点订阅成功
                    @Override
                    public void onSubscribe(@NonNull Disposable d) {
                        // 设置显示加载框
                        progressDialog = new ProgressDialog(MainActivity.this);
                        progressDialog.setTitle("RxJava Shroud run 正在加载中...");
                        progressDialog.show();
                    }

                    // TODO 第四步
                    // 能够接收到上一层的响应
                    @Override
                    public void onNext(@NonNull Bitmap bitmap) {
                        // 拿到上一层的bitmap,设置到控件上
                        imageView.setImageBitmap(bitmap);
                    }

                    // 类似链条思维上发生异常
                    @Override
                    public void onError(@NonNull Throwable e) {

                    }

                    // TODO 第五步
                    // 整个链条全部结束
                    @Override
                    public void onComplete() {
                        // 隐藏加载框
                        if (progressDialog != null) {
                            progressDialog.dismiss();
                        }
                    }
                });
    }

    // 图片上绘制文字 加水印,传入参数会得到一个加水印后的Bitmap
    private final Bitmap drawTextToBitmap(Bitmap bitmap, String text, Paint paint, int paddingLeft, int paddingTop) {
        Bitmap.Config bitmapConfig = bitmap.getConfig();

        paint.setDither(true);//获取跟清晰的图像采样
        paint.setFilterBitmap(true);// 过滤一些
        if (bitmapConfig == null) {
            bitmapConfig = Bitmap.Config.ARGB_8888;
        }
        bitmap = bitmap.copy(bitmapConfig, true);
        Canvas canvas = new Canvas(bitmap);

        canvas.drawText(text, paddingLeft, paddingTop, paint);
        return bitmap;
    }


    // 其他常用操作符-完成遍历
    public void operatorAction(View view) {

        String[] strings = {"AAA", "BBB", "CCC"};

        //也可以使用for循环

        // RxJava 设置起点
        Observable.fromArray(strings)
                //订阅:起点 和 终点
                .subscribe(new Consumer<String>() {
                    @Override
                    public void accept(String s) throws Throwable {
                        Log.d(TAG, "accept: " + s);
                    }
                });
    }
}

自定义Observer

自定义Observer解决项目中服务器响应的问题

在这里插入图片描述

具体 步骤
1 、导入依赖包

2 、准备工作:总bean和成功bean

3 、创建 Observable 被观察者
LoginEngine.java:
Observable.just(zongBean)

4、 创建 Observer 观察者
MyObserver实现Observer
重写方法

5 、使用 subscribe 进行订阅
MainActivity.java
被观察者.subscribe

导入依赖包

//引入rxjava
implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
//如果是做Android开发,引入了rxjava之后,还需要引入rxAndroid
implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'

加入网络请求权限注册

<uses-permission android:name="android.permission.INTERNET" />

准备工作:总bean和成功bean

// 请求服务器的结果Bean (总Bean)
public class ResponseResult {

    // 登录成功
    private SuccessBean data;
    private int code;
    private String message;
}

// 成功Bean
public class SuccessBean {

    private int id;
    private String name;
}

LoginEngine登录引擎,最后返回创Observable 被观察者(起点)

// 登录引擎
public class LoginEngine {

    // 返回 起点 Observable 被观察者
    public static Observable<ResponseResult> login(String name,String pwd) {

        // 最终返回总Bean,根据需求进行拼接
        ResponseResult responseResult = new ResponseResult();

        // 登录成功
        if ("Shroud".equals(name)&&"123456".equals(pwd)) {
            /**
             * {
             *     data:{ 成功Bean }
             *     code:200
             *     message:"登录成功"
             * }
             */

            // 创建成功Bean,
            SuccessBean successBean = new SuccessBean();
            successBean.setId(43413123);
            successBean.setName("Shroud成功");

            responseResult.setData(successBean);
            responseResult.setCode(200);
            responseResult.setMessage("登录成功");

        // 登录失败
        }else{
            /**
             * {
             *     data:null/ /0
             *     code:404
             *     message:"登录失败"
             * }
             */
            responseResult.setData(null);
            responseResult.setCode(404);
            responseResult.setMessage("登录失败");
        }

        // 分发总Bean,返回Rx思维起点
        // just 是 RxJava 的诸多操作符中的创建操作符,用于创建一个 Observable
        return Observable.just(responseResult);

    }

}

创建 Observer 观察者(终点)

public abstract class CustomObserver implements Observer<ResponseResult> {

    // 请求成功只要成功Bean,请求失败只要Message
    public abstract void success(SuccessBean successBean);
    public abstract void error(String message);

    // 起点与终点订阅成功
    @Override
    public void onSubscribe(@NonNull Disposable d) {
    }

    // 能够接收到上一层的响应
    // 每当 Observable 发送新值的时候,onNext 方法会被调用
    @Override
    public void onNext(@NonNull ResponseResult responseResult) {
        if (responseResult.getData() == null){
            error(responseResult.getMessage() + "请求失败,请Shroud检查日志");
        } else {
            success(responseResult.getData());
        }
    }

    // 类似链条思维上发生异常
    // 当 Observable 内发生错误时,error方法就会被调用
    @Override
    public void onError(@NonNull Throwable e) {
        error(e.getMessage() + "请Shroud检查日志,错误详情");
    }

    // 整个链条全部结束
    // 当 Observable 数据终止后,complete 方法会被调用
    // 在调用 complete 方法之后,next 方法就不会再次被调用
    @Override
    public void onComplete() {
    }
}

使用 subscribe 进行订阅

public class LoginActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

        /**
         * RX的思维解决此问题
         * TODO 需求:登录成功-->得到成功Bean
         *           登录失败-->得到Message
         */
        // 使用引擎登录,返回值为Observable<ResponseResult> 起点<总Bean>
        LoginEngine.login("Shroud","123456")
                //new自己定义的Observer 返回的是起点
        .subscribe(new CustomObserver() {
            @Override
            public void success(SuccessBean successBean) {
                // 登录成功打印成功信息
                Log.d("ShroudTag", "success: " + successBean.toString());
            }

            @Override
            public void error(String message) {
                // 登录失败打印失败信息
                Log.d("ShroudTag", "error: " + message);
            }
        });
    }
}

结果:

密码正确:D/ShroudTag: success: SuccessBean{id=43413123, name='Shroud成功'}

密码错误:D/ShroudTag: error: 登录失败请求失败,请Shroud检查日志

数据共享

使用Intent、Bundle,可以传输属性;
实现Serializable接口对象,可以共享对象数据

共享使用xml布局文件,点击activity_reuse1.xml跳转activity_reuse2.xml

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="传递数据"
    android:onClick="startAction"/>

Intent传递数据:IntentActivity1传数据,IntentActivity2收数据

public class IntentActivity1 extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_reuse1);
    }

    // 携带数据,跳转到IntentActivity2
    public void startAction(View view) {

        Intent intent = new Intent(IntentActivity1.this,IntentActivity2.class);

        // 使用Intent传输数据,注意不要s
        intent.putExtra("name","Shroud");
        intent.putExtra("sex",'M');

        startActivity(intent);
    }
}
public class IntentActivity2 extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_reuse2);

        Intent intent = getIntent();

        // 获取IntentActivity通过Intent传过来的数据
        String name = intent.getStringExtra("name");
        char sex = intent.getCharExtra("sex", 'W');

        Toast.makeText(this, "name = "+name+",sex = "+sex, Toast.LENGTH_SHORT).show();
    }
}

Intent传递数据:IntentActivity1传数据,IntentActivity2收数据

public class BundleActivity1 extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_reuse1);
    }

    // 携带数据,跳转到BundleActivity2
    public void startAction(View view) {

        Intent intent = new Intent(BundleActivity1.this,BundleActivity2.class);

        // 使用Bundle传输数据
        Bundle bundle = new Bundle();
        bundle.putString("name","Mark");
        bundle.putChar("sex",'W');
        // put 100条数据

        // intent携带Bundle对象,注意要s
        intent.putExtras(bundle);

        startActivity(intent);
    }
}
public class BundleActivity2 extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_reuse2);

        Intent intent = getIntent();

        // 拆开Bundle对象
        String name = intent.getStringExtra("name");
        char sex = intent.getCharExtra("sex", 'M');

        Toast.makeText(this, "name = "+name+",sex = "+sex, Toast.LENGTH_SHORT).show();
    }
}

Serializable传递对象:SerializableAct1传数据,SerializableAct2收数据

// 想要共享对象数据,必须实现Serializable接口,此对象才有资格
public class Student implements Serializable {
    private int id;
    private String name;
    private int age;

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
public class SerializableAct1 extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_reuse1);
    }

    // 携带数据,跳转到SerializableAct2
    public void startAction(View view) {

        Intent intent = new Intent(SerializableAct1.this, SerializableAct2.class);

        Student student = new Student();
        student.setId(9);
        student.setName("Shroud");
        student.setAge(23);

        // 对象使用Intent传输数据,注意不要s
        intent.putExtra("Student",student);

        startActivity(intent);
    }
}
public class SerializableAct2 extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_reuse2);

        Intent intent = getIntent();

        Serializable student = intent.getSerializableExtra("Student");

        Student student1 = new Student();
        Toast.makeText(this, "student.id"+student1.getId()
                +"student.name"+student1.getName()
                +"student.age"+student1.getAge()
                , Toast.LENGTH_SHORT).show();
    }
}

返回的是起点
.subscribe(new CustomObserver() {
@Override
public void success(SuccessBean successBean) {
// 登录成功打印成功信息
Log.d(“ShroudTag”, "success: " + successBean.toString());
}

        @Override
        public void error(String message) {
            // 登录失败打印失败信息
            Log.d("ShroudTag", "error: " + message);
        }
    });
}

}


结果:

```java
密码正确:D/ShroudTag: success: SuccessBean{id=43413123, name='Shroud成功'}

密码错误:D/ShroudTag: error: 登录失败请求失败,请Shroud检查日志

数据共享

使用Intent、Bundle,可以传输属性;
实现Serializable接口对象,可以共享对象数据

共享使用xml布局文件,点击activity_reuse1.xml跳转activity_reuse2.xml

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="传递数据"
    android:onClick="startAction"/>

Intent传递数据:IntentActivity1传数据,IntentActivity2收数据

public class IntentActivity1 extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_reuse1);
    }

    // 携带数据,跳转到IntentActivity2
    public void startAction(View view) {

        Intent intent = new Intent(IntentActivity1.this,IntentActivity2.class);

        // 使用Intent传输数据,注意不要s
        intent.putExtra("name","Shroud");
        intent.putExtra("sex",'M');

        startActivity(intent);
    }
}
public class IntentActivity2 extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_reuse2);

        Intent intent = getIntent();

        // 获取IntentActivity通过Intent传过来的数据
        String name = intent.getStringExtra("name");
        char sex = intent.getCharExtra("sex", 'W');

        Toast.makeText(this, "name = "+name+",sex = "+sex, Toast.LENGTH_SHORT).show();
    }
}

Intent传递数据:IntentActivity1传数据,IntentActivity2收数据

public class BundleActivity1 extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_reuse1);
    }

    // 携带数据,跳转到BundleActivity2
    public void startAction(View view) {

        Intent intent = new Intent(BundleActivity1.this,BundleActivity2.class);

        // 使用Bundle传输数据
        Bundle bundle = new Bundle();
        bundle.putString("name","Mark");
        bundle.putChar("sex",'W');
        // put 100条数据

        // intent携带Bundle对象,注意要s
        intent.putExtras(bundle);

        startActivity(intent);
    }
}
public class BundleActivity2 extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_reuse2);

        Intent intent = getIntent();

        // 拆开Bundle对象
        String name = intent.getStringExtra("name");
        char sex = intent.getCharExtra("sex", 'M');

        Toast.makeText(this, "name = "+name+",sex = "+sex, Toast.LENGTH_SHORT).show();
    }
}

Serializable传递对象:SerializableAct1传数据,SerializableAct2收数据

// 想要共享对象数据,必须实现Serializable接口,此对象才有资格
public class Student implements Serializable {
    private int id;
    private String name;
    private int age;

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
public class SerializableAct1 extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_reuse1);
    }

    // 携带数据,跳转到SerializableAct2
    public void startAction(View view) {

        Intent intent = new Intent(SerializableAct1.this, SerializableAct2.class);

        Student student = new Student();
        student.setId(9);
        student.setName("Shroud");
        student.setAge(23);

        // 对象使用Intent传输数据,注意不要s
        intent.putExtra("Student",student);

        startActivity(intent);
    }
}
public class SerializableAct2 extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_reuse2);

        Intent intent = getIntent();

        Serializable student = intent.getSerializableExtra("Student");

        Student student1 = new Student();
        Toast.makeText(this, "student.id"+student1.getId()
                +"student.name"+student1.getName()
                +"student.age"+student1.getAge()
                , Toast.LENGTH_SHORT).show();
    }
}
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值