《第一行代码》学习笔记

基于《第一行代码》第二版

  1. 应用抽屉中显示的名称通常是 AndroidManifest.xml 文件中 <application> 标签的 android:label 定义的值。但如果在应用程序的 入口 Activity(具有 MAINLAUNCHERintent-filter)中定义了 android:label,则该值会覆盖 <application>android:label,并显示在应用抽屉中。

  2. Switch-case的case后只能放常亮不能放变量

    1
    android:id="@+id/add_item"

​ 以上创建的id是一个变量

  1. 四种布局

    1. LinearLayout:

      1
      2
      3
      android:orientation排列方向
      android:layout_gravity单个组件排列方向的另一方向
      android:layout_weight比例布局(需要把orientation方向的高或宽置零)
    2. RelativeLayout

      1
      2
      3
      4
      5
      android:layout_alignParentTop
      android:layout_centerInParent
      android:layout_above某一个组件
      android:layout_toLeftOf某一个组件
      android:layout_alignLeft某个组件 多组件指定边缘对齐
    3. FrameLayout

      1
      android:layout_gravity
    4. 帧布局

      add dependency

      1
      implementation "androidx.percentlayout:percentlayout:1.0.0"

      有PercentFrameLayout和PercentRelativeLayout两种

      以前者为例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <androidx.percentlayout.widget.PercentFrameLayout
      xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto"
      android:layout_width="match_parent"
      android:layout_height="match_parent">

      <Button
      android:id="@+id/button1"
      android:text="Button 1"
      android:layout_gravity="left|top"
      app:layout_widthPercent="50%"
      app:layout_heightPercent="50%"/>

      </androidx.percentlayout.widget.PercentFrameLayout>
  2. 自定义布局

    android:gravity=”center”是对textView中文字居中

    android:layout_gravity=”center”是对textview控件在整个布局中居中

    在别的xml中使用:

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

    自定义控件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class TitleLayout extends LinearLayout {

    public TitleLayout(Context context, AttributeSet attrs){
    super(context,attrs);
    LayoutInflater.from(context).inflate(R.layout.title,this);
    Button button1 = (Button) findViewById(R.id.title_back);
    Button button2 = (Button) findViewById(R.id.title_edit);
    button1.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
    ((Activity)getContext()).finish();
    }
    });
    button2.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
    Toast.makeText(getContext(),"You clicked Edit button",Toast.LENGTH_SHORT).show();
    }
    });
    }
    }
  3. ListView控件:

    1. 定义实体类:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      public class Fruit {
      private String name;
      private int imageId;

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

      public String getName(){
      return name;
      }

      public int getImageId(){
      return imageId;
      }
      }
    2. 定义ListView的子项布局

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="match_parent"
      android:layout_height="wrap_content">

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

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

      </LinearLayout>
    3. 定义Adapter

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      public class FruitAdapter extends ArrayAdapter<Fruit> {
      private int resourceId;

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

      @NonNull
      @Override
      public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
      Fruit fruit = getItem(position);
      View view = LayoutInflater.from(getContext()).inflate(resourceId,parent,false);
      ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
      TextView fruitName = (TextView) view.findViewById(R.id.fruit_name);
      fruitImage.setImageResource(fruit.getImageId());
      fruitName.setText(fruit.getName());
      return view;
      }
      }
    4. 在主程序调用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      public class MainActivity extends AppCompatActivity {

      private List<Fruit> fruitList = new ArrayList<>();

      @Override
      protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      //定制ListView
      initFruits();
      FruitAdapter adapter = new FruitAdapter(this,R.layout.fruit_item,fruitList);
      ListView listView = (ListView) findViewById(R.id.list_view);
      listView.setAdapter(adapter);

      }

      private void initFruits(){
      for(int i =0;i<2;i++){
      Fruit apple = new Fruit("Apple",R.mipmap.ic_launcher);
      fruitList.add(apple);
      Fruit banana = new Fruit("Banana",R.mipmap.ic_launcher);
      fruitList.add(banana);
      Fruit orange = new Fruit("Orange",R.mipmap.ic_launcher);
      fruitList.add(orange);
      Fruit watermelon = new Fruit("Watermelon",R.mipmap.ic_launcher);
      fruitList.add(watermelon);
      Fruit pear = new Fruit("Pear",R.mipmap.ic_launcher);
      fruitList.add(pear);
      Fruit grape = new Fruit("Grape",R.mipmap.ic_launcher);
      fruitList.add(grape);
      Fruit pineapple = new Fruit("Pineapple",R.mipmap.ic_launcher);
      fruitList.add(pineapple);
      Fruit strawberry = new Fruit("Strawberry",R.mipmap.ic_launcher);
      fruitList.add(strawberry);
      Fruit cherry = new Fruit("Cherry",R.mipmap.ic_launcher);
      fruitList.add(cherry);
      Fruit mango = new Fruit("Mango",R.mipmap.ic_launcher);
      fruitList.add(mango);
      }
      }
      }

      对Adaptor进行优化:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      public class FruitAdapter extends ArrayAdapter<Fruit> {
      private int resourceId;

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

      @NonNull
      @Override
      public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
      Fruit fruit = getItem(position);
      View view;
      ViewHolder viewHolder;
      if(convertView == null){
      view = LayoutInflater.from(getContext()).inflate(resourceId,parent,false);
      viewHolder = new ViewHolder();
      viewHolder.fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
      viewHolder.fruitName = (TextView) view.findViewById(R.id.fruit_name);
      view.setTag(viewHolder);
      }else{
      view = convertView;//convertView对之前加载好的布局进行缓存
      viewHolder = (ViewHolder) view.getTag();
      }

      viewHolder.fruitImage.setImageResource(fruit.getImageId());
      viewHolder.fruitName.setText(fruit.getName());
      return view;
      }

      class ViewHolder{
      ImageView fruitImage;
      TextView fruitName;
      }
      }

      点击列表item事件:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      //定制ListView
      initFruits();
      FruitAdapter adapter = new FruitAdapter(this,R.layout.fruit_item,fruitList);
      ListView listView = (ListView) findViewById(R.id.list_view);
      listView.setAdapter(adapter);
      listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
      @Override
      public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
      Fruit fruit = fruitList.get(position);
      Toast.makeText(MainActivity.this,fruit.getName(),Toast.LENGTH_SHORT).show();
      }
      });
      }

      RecyclerView控件

      1. 同样定义在了support库中,需要添加依赖

        1
        implementation "androidx.recyclerview:recyclerview:1.3.2"
      2. 在xml使用

        1
        2
        3
        4
        <androidx.recyclerview.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/recycler_view"/>
      3. 写Adaptor

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        package com.example.recyclerview;

        import android.view.LayoutInflater;
        import android.view.View;
        import android.view.ViewGroup;
        import android.widget.ImageView;
        import android.widget.TextView;

        import androidx.annotation.NonNull;
        import androidx.recyclerview.widget.RecyclerView;

        import java.util.List;

        public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
        private List<Fruit> mFruitList;

        static class ViewHolder extends RecyclerView.ViewHolder{
        ImageView fruitImage;
        TextView fruitName;

        public ViewHolder(View view){
        super(view);
        fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
        fruitName = (TextView) view.findViewById(R.id.fruit_name);
        }
        }

        public FruitAdapter(List<Fruit> fruitList){
        mFruitList = fruitList;
        }

        @NonNull
        @Override
        public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item,parent,false);
        ViewHolder holder = new ViewHolder(view);
        return holder;
        }

        @Override
        public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        Fruit fruit = mFruitList.get(position);
        holder.fruitImage.setImageResource(fruit.getImageId());
        holder.fruitName.setText(fruit.getName());
        }

        @Override
        public int getItemCount() {
        return mFruitList.size();
        }
        }

      4. 使用

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        public class MainActivity extends AppCompatActivity {

        private List<Fruit> fruitList = new ArrayList<>();

        @Override
        protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initFruits();
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(layoutManager);
        FruitAdapter adapter = new FruitAdapter(fruitList);
        recyclerView.setAdapter(adapter);

        }

        private void initFruits(){
        for(int i =0;i<2;i++){
        Fruit apple = new Fruit("Apple",R.mipmap.ic_launcher);
        fruitList.add(apple);
        Fruit banana = new Fruit("Banana",R.mipmap.ic_launcher);
        fruitList.add(banana);
        Fruit orange = new Fruit("Orange",R.mipmap.ic_launcher);
        fruitList.add(orange);
        Fruit watermelon = new Fruit("Watermelon",R.mipmap.ic_launcher);
        fruitList.add(watermelon);
        Fruit pear = new Fruit("Pear",R.mipmap.ic_launcher);
        fruitList.add(pear);
        Fruit grape = new Fruit("Grape",R.mipmap.ic_launcher);
        fruitList.add(grape);
        Fruit pineapple = new Fruit("Pineapple",R.mipmap.ic_launcher);
        fruitList.add(pineapple);
        Fruit strawberry = new Fruit("Strawberry",R.mipmap.ic_launcher);
        fruitList.add(strawberry);
        Fruit cherry = new Fruit("Cherry",R.mipmap.ic_launcher);
        fruitList.add(cherry);
        Fruit mango = new Fruit("Mango",R.mipmap.ic_launcher);
        fruitList.add(mango);
        }
        }
        }
      5. 实现横向滚动和瀑布流布局:

        在LayoutManager后配置:

        1
        layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
      6. 其他布局(GridLayoutManager, StaggeredGridLayoutManager etc)

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        public class MainActivity extends AppCompatActivity {

        private List<Fruit> fruitList = new ArrayList<>();

        @Override
        protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initFruits();
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        LinearLayoutManager(this);
        StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(layoutManager);
        FruitAdapter adapter = new FruitAdapter(fruitList);
        recyclerView.setAdapter(adapter);

        }
        }
      7. RecyclerView点击事件

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
        private List<Fruit> mFruitList;

        static class ViewHolder extends RecyclerView.ViewHolder{
        ImageView fruitImage;
        TextView fruitName;
        View fruitView;

        public ViewHolder(View view){
        super(view);
        fruitView = view;
        fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
        fruitName = (TextView) view.findViewById(R.id.fruit_name);
        }
        }

        public FruitAdapter(List<Fruit> fruitList){
        mFruitList = fruitList;
        }

        @NonNull
        @Override
        public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item,parent,false);
        ViewHolder holder = new ViewHolder(view);
        holder.fruitView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
        int position = holder.getAdapterPosition();
        Fruit fruit = mFruitList.get(position);
        Toast.makeText(parent.getContext(),"you clicked view "+fruit.getName(),Toast.LENGTH_SHORT).show();
        }
        });
        holder.fruitImage.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
        int position = holder.getAdapterPosition();
        Fruit fruit = mFruitList.get(position);
        Toast.makeText(parent.getContext(),"you clicked image "+fruit.getName(),Toast.LENGTH_SHORT).show();
        }
        });
        return holder;
        }
        ...

        一个实战案例

        1. 首先需要添加RecyclerView依赖库,然后编写主界面,主界面主要是一个滚动列表、一个文本框和一个send按键,这里嵌套了一个LinearLayout:

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31
          32
          <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:orientation="vertical"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:background="#d8e0e8">

          <androidx.recyclerview.widget.RecyclerView
          android:layout_width="match_parent"
          android:layout_height="0dp"
          android:layout_weight="1"
          android:id="@+id/msg_recycler_view"/>

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

          <EditText
          android:id="@+id/input_text"
          android:layout_width="0dp"
          android:layout_height="wrap_content"
          android:layout_weight="1"
          android:hint="Type something here"
          android:maxLines="2"/>

          <Button
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:id="@+id/send"
          android:text="Send"/>
          </LinearLayout>

          </LinearLayout>
        2. 然后建立消息实体类,消息包含两部分内容:消息体和消息来源:

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          public class Msg {
          public static final int TYPE_RECEIVED = 0;
          public static final int TYPE_SENT = 1;
          private String content;
          private int type;

          public Msg(String content,int type){
          this.content = content;
          this.type = type;
          }

          public String getContent(){
          return content;
          }

          public int getType(){
          return type;
          }
          }
        3. 然后写RecyclerView子项的布局,子项有两部分:一个是发送方的文本框,一个是接收方的文本框,LinearLayout中嵌套两个LinearLayout,可以控制显示隐藏

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31
          32
          33
          34
          35
          36
          37
          <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:orientation="vertical"
          android:padding="10dp">
          <LinearLayout
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:id="@+id/left_layout"
          android:layout_gravity="left"
          android:background="@drawable/message_left">
          <TextView
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_gravity="center"
          android:layout_margin="10dp"
          android:id="@+id/left_msg"
          android:textColor="#fff"/>

          </LinearLayout>

          <LinearLayout
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:id="@+id/right_layout"
          android:layout_gravity="right"
          android:background="@drawable/message_right">
          <TextView
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_gravity="center"
          android:id="@+id/right_msg"
          android:layout_margin="10dp"/>

          </LinearLayout>

          </LinearLayout>
        4. 编写RecyclerView的adapter MsgAdapter,创建私有消息列表;创建ViewHolder类继承RecyclerView.ViewHolder类并在class中重写ViewHolder,将RecyclerView子项布局中的子布局和文本都注册;写MsgAdapter的构造方法;重写onCreateViewHolder,创建view为子类布局,将子布局的xml_id注入,返回ViewHolder(view) ;重写onBindViewHolder,得到当前位置,得到对应信息,根据信息来源将holder中内容配置可见或GONE以及文本内容;重写getItemCount。

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31
          32
          33
          34
          35
          36
          37
          38
          39
          40
          41
          42
          43
          44
          45
          46
          47
          48
          public class MsgAdapter extends RecyclerView.Adapter<MsgAdapter.ViewHolder>{

          private List<Msg> mMsglList;
          static class ViewHolder extends RecyclerView.ViewHolder{
          LinearLayout leftLayout;
          LinearLayout rightLayout;
          TextView leftMsg;
          TextView rightMsg;
          public ViewHolder(View view){
          super(view);//kankan super shisha
          leftLayout = (LinearLayout) view.findViewById(R.id.left_layout);
          rightLayout = (LinearLayout) view.findViewById(R.id.right_layout);
          leftMsg = (TextView) view.findViewById(R.id.left_msg);
          rightMsg = (TextView) view.findViewById(R.id.right_msg);
          }
          }


          public MsgAdapter(List<Msg> msglist){
          mMsglList = msglist;
          }

          @NonNull
          @Override
          public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
          View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.msg_item,parent,false);
          return new ViewHolder(view);
          }

          @Override
          public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
          Msg msg = mMsglList.get(position);
          if(msg.getType()==Msg.TYPE_RECEIVED){
          holder.leftLayout.setVisibility(View.VISIBLE);
          holder.rightLayout.setVisibility(View.GONE);
          holder.leftMsg.setText(msg.getContent());
          }else if(msg.getType()==Msg.TYPE_SENT){
          holder.leftLayout.setVisibility(View.GONE);
          holder.rightLayout.setVisibility(View.VISIBLE);
          holder.rightMsg.setText(msg.getContent());
          }
          }

          @Override
          public int getItemCount() {
          return mMsglList.size();
          }
          }
        5. 编写MainActivity,初始化消息列表;将输入文本框、按键和滑动列表分别对应到布局id,然后建立LinearLayoutManager并注入滑动列表,new出来适配器也注入滑动列表;然后设计了一个send按键的响应:获取输入文本框内容,将内容new成消息,将消息添加到消息列表,设置刷新滑动列表,设置定位到最后一行,清空输入文本框。

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31
          32
          33
          34
          35
          36
          37
          38
          39
          40
          41
          42
          43
          public class MainActivity extends AppCompatActivity {
          private List<Msg> msgList = new ArrayList<>();
          private EditText inputText;
          private Button send;
          private RecyclerView msgRecyclerView;
          private MsgAdapter adapter;

          @Override
          protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
          initMsgs();
          inputText = (EditText) findViewById(R.id.input_text);
          send = (Button) findViewById(R.id.send);
          msgRecyclerView = (RecyclerView) findViewById(R.id.msg_recycler_view);
          LinearLayoutManager layoutManager = new LinearLayoutManager(this);
          msgRecyclerView.setLayoutManager(layoutManager);
          adapter = new MsgAdapter(msgList);
          msgRecyclerView.setAdapter(adapter);
          send.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {
          String content = inputText.getText().toString();
          if(!"".equals(content)){
          Msg msg = new Msg(content,Msg.TYPE_SENT);
          msgList.add(msg);
          adapter.notifyItemInserted(msgList.size()-1);//有新消息时刷新RecyclerView中的显示
          msgRecyclerView.scrollToPosition(msgList.size()-1);
          inputText.setText("");
          }
          }
          });
          }

          private void initMsgs(){
          Msg msg1 = new Msg("Hello guy",Msg.TYPE_RECEIVED);
          msgList.add(msg1);
          Msg msg2 = new Msg("Hello. Who is that",Msg.TYPE_SENT);
          msgList.add(msg2);
          Msg msg3 = new Msg("This is Jerry",Msg.TYPE_RECEIVED);
          msgList.add(msg3);
          }
          }
  4. 碎片

    1. 添加对骗的步骤:

      1. 首先创建左右碎片的xml
      2. 设置两个类继承Fragment,重写onCreatView,view中引入布局
      3. 在主活动布局中引入两个<fragment,name是两个类
    2. 动态添加碎片:

      1. 创建另一碎片xml

      2. 创建另一碎片的类继承Fragment

      3. 在主活动布局中创建FrameLayout

      4. 在主函数中使用FragmentManager,然后创建FragmentTransaction,替换布局,提交:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        public class MainActivity extends AppCompatActivity {

        @Override
        protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
        replaceFragment(new AnotherRightFragment());
        }
        });
        replaceFragment(new RightFragment());
        }

        private void replaceFragment(Fragment fragment){
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.replace(R.id.right_layout,fragment);
        transaction.commit();
        }
        }
    3. 在碎片中模拟返回栈:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      public class MainActivity extends AppCompatActivity {

      @Override
      protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      Button button = (Button) findViewById(R.id.button);
      button.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
      replaceFragment(new AnotherRightFragment());
      }
      });
      replaceFragment(new RightFragment());
      }

      private void replaceFragment(Fragment fragment){
      FragmentManager fragmentManager = getSupportFragmentManager();
      FragmentTransaction transaction = fragmentManager.beginTransaction();
      transaction.replace(R.id.right_layout,fragment);
      transaction.addToBackStack(null);//加上这一句就可以了
      transaction.commit();
      }
      }
  5. 广播机制

    1. 分两种:

      1. 标准广播:完全异步 所有广播接收器都会同步收到
      2. 有序广播:同步执行 优先级高的广播接收器先收到消息,处理完成后才会继续传递广播或截断
    2. 动态注册监听网路变化(在代码中注册广播为动态注册,在AM.xml注册为静态注册,静态注册可以让陈故乡在未启动情况下收到广播)

      需要重写BroadcastReceiver来做接收器,接收器类中规定接受广播后的动作;需要一个IntentFilter实例,其中addAction为广播;然后组册广播接收器;还需要重写onDestroy将广播接收器取消注册。

      在接收器中,使用getSystemService得到ConnectivityManager实例,然后再通过ConnectivityManager中的getActiveNetworkInfo来得到NetworkInfo实例。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      public class MainActivity extends AppCompatActivity {

      private NetworkChangeReceiver networkChangeReceiver;

      private IntentFilter intentFilter;

      @Override
      protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      intentFilter = new IntentFilter();
      intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
      networkChangeReceiver = new NetworkChangeReceiver();
      registerReceiver(networkChangeReceiver,intentFilter);
      }

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

      class NetworkChangeReceiver extends BroadcastReceiver{
      @Override
      public void onReceive(Context context, Intent intent) {
      ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
      NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
      if(networkInfo!=null&&networkInfo.isAvailable()){
      Toast.makeText(context,"network is available",Toast.LENGTH_SHORT).show();
      }else{
      Toast.makeText(context,"network is unavailable",Toast.LENGTH_SHORT).show();
      }
      }
      }
      }

      注意需要再AM中声明权限:

      1
      <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    3. 发送自定义广播

      1. 发送标准广播:

        1. 首先写一个接收器类:

          1
          2
          3
          4
          5
          6
          public class MyBroadcastReceiver extends BroadcastReceiver {
          @Override
          public void onReceive(Context context, Intent intent) {
          Toast.makeText(context,"received in MyBroadcastReceiver",Toast.LENGTH_SHORT).show();
          }
          }
        2. 然后在AM中静态注册接收器:

          1
          2
          3
          4
          5
          6
          7
          8
          <receiver
          android:name=".MyBroadcastReceiver"
          android:enabled="true"
          android:exported="true">
          <intent-filter>
          <action android:name="com.example.broadcasttest.MY_BROADCAST"/>
          </intent-filter>
          </receiver>
        3. 在主活动布局添加按钮,然后在主活动中添加点击事件,构建Intent对象,将action放入,然后sendBroadcast:

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
          Button button = (Button) findViewById(R.id.button);
          button.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {
          Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST");
          sendBroadcast(intent);
          }
          });
      2. 发送有序广播:

        1. 发送广播的方法换为sendOrderedBroadcast(intent, null)

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
          Button button = (Button) findViewById(R.id.button);
          button.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {
          Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST");
          sendOrderedBroadcast(intent,null);
          }
          });
        2. 广播接收器权限从AM中设定 android:priority

          1
          2
          3
          4
          5
          6
          7
          8
          <receiver
          android:name=".MyBroadcastReceiver"
          android:enabled="true"
          android:exported="true">
          <intent-filter android:priority="100">
          <action android:name="com.example.broadcasttest.MY_BROADCAST"/>
          </intent-filter>
          </receiver>
        3. 可以在广播接收器中设置广播截断:

          1
          2
          3
          4
          5
          6
          7
          public class MyBroadcastReceiver extends BroadcastReceiver {
          @Override
          public void onReceive(Context context, Intent intent) {
          Toast.makeText(context,"received in MyBroadcastReceiver",Toast.LENGTH_SHORT).show();
          abortBroadcast();
          }
          }
        4. 使用本地广播:

          广播只能在应用内部传递,广播接收器只能接受来自应用发出的广播,更高效,只能动态注册;

          使用LocalBroadcastManager对本地广播管理

          用LocalBroadcastManager.getIntance来获取实例

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          private IntentFilter intentFilter;
          private LocalReceiver localReceiver;
          private LocalBroadcastManager localBroadcastManager;

          @Override
          protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
          localBroadcastManager = LocalBroadcastManager.getInstance(this);
          Button button = (Button) findViewById(R.id.button);
          button.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {
          Intent intent = new Intent("com.example.broadcasttest.LOCAL_BROADCAST");
          localBroadcastManager.sendBroadcast(intent);
          }
          });
          intentFilter = new IntentFilter();
          intentFilter.addAction("com.example.broadcasttest.LOCAL_BROADCAST");
          localReceiver = new LocalReceiver();
          localBroadcastManager.registerReceiver(localReceiver,intentFilter);
          }

          class LocalReceiver extends BroadcastReceiver {
          @Override
          public void onReceive(Context context, Intent intent) {
          Toast.makeText(context, "received local broadcast", Toast.LENGTH_SHORT).show();
          }
          }
  6. 持久化存储

    有文件存储、SharedPreference存储和数据库存储三种

    1. 文件存储

      openFileOutput方法,第一个参数为文件名,第二个参数为操作模式(MODE_PRIVATE是覆盖写,MODE_APPEND是追加写)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      public class MainActivity extends AppCompatActivity {
      private EditText edit;

      @Override
      protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      edit = (EditText) findViewById(R.id.edit);
      }

      @Override
      protected void onDestroy() {
      super.onDestroy();
      String inputText = edit.getText().toString();
      save(inputText);
      }

      private void save(String inputText){
      FileOutputStream out = null;
      BufferedWriter writer = null;
      try{
      out = openFileOutput("data",MODE_PRIVATE);//打开一个文件输出
      writer = new BufferedWriter(new OutputStreamWriter(out));//OutputStreamWriter将字节流转为字符流,BufferedOutput用于缓冲写入
      writer.write(inputText);
      }catch (IOException e){
      e.printStackTrace();
      }finally {
      try{
      if(writer != null){
      writer.close();
      }
      }catch (IOException e){
      e.printStackTrace();
      }
      }
      }
      }

      读取:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      private String load(){
      FileInputStream in = null;
      BufferedReader reader = null;
      StringBuilder content = new StringBuilder();
      try{
      in = openFileInput("data");
      reader = new BufferedReader(new InputStreamReader(in));
      String line = "";
      while((line = reader.readLine())!=null){
      content.append(line);
      }
      }catch (IOException e){
      e.printStackTrace();
      }finally {
      if(reader!=null){
      try{
      reader.close();
      }catch (IOException e){
      e.printStackTrace();
      }
      }
      }
      return content.toString();
      }
    2. SharedPreferences存储

      键值对

      1. 存储到SharedPreferences:

        1
        2
        3
        4
        5
        SharedPreferences.Editor editor = getSharedPreferences("data",MODE_PRIVATE).edit();
        editor.putString("name","Tom");
        editor.putInt("age",28);
        editor.putBoolean("married",false);
        editor.apply();

        调用SharedPreferences对象的edit方法来得到一个SharedPreferences.Editor对象,然后向对象中添加数据,apply方法提交数据。

      2. 从SharedPreferences中读取数据

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

        用getSharedPreferences得到对象,get读取

    3. SQLite数据存储

      1. 创建和升级数据库

        SQLiteOpenHelper是一个抽象类,需要重写onCreate和onUpgrade方法,前者用于数据库创建(构建实例后用getReadableDatabase或getWritableDatabase创建数据库),后者用于数据库版本更新

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        public class MyDatabaseHelper extends SQLiteOpenHelper {
        public static final String CREATE_BOOK = "create table Book ("+"id integer primary key autoincrement,"+"auther text,"+"price real,"+"pages integer,"+"name text)";

        public static final String CREATE_CATEGORY = "create table Category ("+"id integer primary key autoincrement,"+"category_name text,"+"category_code integer)";
        private Context mContext;

        public MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory,int version){
        super(context,name,factory,version);
        mContext = context;
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK);
        db.execSQL(CREATE_CATEGORY);
        Toast.makeText(mContext,"Create succeeded",Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("drop table if exists Book");
        db.execSQL("drop table if exists Category");
        onCreate(db);
        }
        }
      2. 添加数据

        四种操作CRUD:Create Retrieve代表查询 Update代表更新 Delete

        可借助getReadableDatabase或getWritableDatabase返回的SQLiteDatabase对象进行CRUD

        1
        2
        3
        4
        5
        6
        7
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put("author","Dan Brown");
        values.put("price",16.96);
        values.put("pages",454);
        values.put("name","The Da Vinci Code");
        db.insert("Book",null,values);
      3. 更新数据

        1
        2
        3
        4
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put("price",10.99);
        db.update("Book",values,"name=?",new String[]{"The Da Vinci Code"});
      4. 删除数据

        1
        2
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        db.delete("Book","pages>?",new String[]{"500"});
      5. 查询数据:最后一定要关闭cursor!

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        Cursor cursor = db.query("Book",null,null,null,null,null,null);
        if(cursor.moveToFirst()){
        do{
        String name = cursor.getString(cursor.getColumnIndex("name"));
        String author = cursor.getString(cursor.getColumnIndex("author"));
        int pages = cursor.getInt(cursor.getColumnIndex("pages"));
        double price = cursor.getDouble(cursor.getColumnIndex("price"));
        Toast.makeText(MainActivity.this,"book name is "+name+"book author is "+author+"book pages is "+pages+"book price is "+price,Toast.LENGTH_SHORT).show();
        }while(cursor.moveToNext());
        }
        cursor.close();
      6. 使用SQL操作数据库

        添加数据的方法如下:
        db.execSQL(“insert into Book (name, author, pages, price) values(?, ?, ?, ?)”,
        new String[] { “The Da Vinci Code”, “Dan Brown”, “454”, “16.96” });
        db.execSQL(“insert into Book (name, author, pages, price) values(?, ?, ?, ?)”,
        new String[] { “The Lost Symbol”, “Dan Brown”, “510”, “19.95” });
        更新数据的方法如下:
        db.execSQL(“update Book set price = ? where name = ?”, new String[] { “10.99”, “The Da V
        删除数据的方法如下:
        db.execSQL(“delete from Book where pages > ?”, new String[] { “500” });
        查询数据的方法如下:
        db.rawQuery(“select * from Book”, null);

    4. 使用LitePal操作数据库

      https://github.com/guolindev/LitePal?tab=readme-ov-file

  7. 内容提供器Content Provider:

    1. 安卓运行时权限:程序运行时授予危险权限

      用ContextCompat.checkSelfPermission检查权限

      用ActivityCompat.requestPermissions授予权限

      授予接受后onRequestPermissionsResult类处理结果

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      public class MainActivity extends AppCompatActivity {

      @Override
      protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      Button makeCall = (Button) findViewById(R.id.make_call);
      makeCall.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
      if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED){
      ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.CALL_PHONE},1);
      }else{
      call();
      }
      }
      });
      }

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

      @Override
      public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
      switch(requestCode){
      case 1:
      if(grantResults.length>0 && grantResults[0]==PackageManager.PERMISSION_GRANTED){
      call();
      }else {
      Toast.makeText(MainActivity.this,"Permission denied",Toast.LENGTH_SHORT).show();
      }
      break;
      default:
      }
      }
    2. 访问其他程序中的数据:

      1. ContentResolver的基本用法:

        看书吧

      2. 读取系统联系人:

        1. 首先在xml布局中添加一个Listview

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="match_parent"
          android:layout_height="match_parent">

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

          </LinearLayout>
        2. 在MainActivity类中,在onCreate中,首先创建Listview对象,然后构建adapter并放入;ContextCompat.checkSelfPermission检查权限,有权则执行readContacts方法;无权限则用ActivityCompat.requestPermissions申请权限,此后需要重写onRequestPermissionsResult类:授权则执行readContacts方法。

          在readContacts方法中,使用getContentResolver().query搭配uri来读取内容提供器提供的内容到cursor中,从cursor中读取信息放入contactsList中,更新adapter,最后关闭cursor。

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31
          32
          33
          34
          35
          36
          37
          38
          39
          40
          41
          42
          43
          44
          45
          46
          47
          48
          49
          50
          51
          List<String> contactsList = new ArrayList<>();
          ArrayAdapter<String> adapter;

          @Override
          protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
          ListView contactsView = (ListView) findViewById(R.id.contact_view);
          adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1,contactsList);
          contactsView.setAdapter(adapter);
          if(ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS)!= PackageManager.PERMISSION_GRANTED){
          ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.READ_CONTACTS},1);
          }else{
          readContacts();
          }
          }

          private void readContacts(){
          Cursor cursor = null;
          try{
          cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null,null,null,null);
          if(cursor!=null){
          while(cursor.moveToNext()){
          String displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
          String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
          contactsList.add(displayName+"\n"+number);
          }
          adapter.notifyDataSetChanged();
          }
          }catch (Exception e){
          e.printStackTrace();
          }finally {
          if(cursor!=null){
          cursor.close();
          }
          }
          }

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

        需要继承ContentProvider,重写onCreate、query、insert、update、delete、getType方法。具体看书吧。

      4. 实现跨程序数据共享

        从DatabaseTest项目上继续开发

        1. 创建DatabaseProvider内容提供器,getPathSegments()将”/“后分割
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64
        65
        66
        67
        68
        69
        70
        71
        72
        73
        74
        75
        76
        77
        78
        79
        80
        81
        82
        83
        84
        85
        86
        87
        88
        89
        90
        91
        92
        93
        94
        95
        96
        97
        98
        99
        100
        101
        102
        103
        104
        105
        106
        107
        108
        109
        110
        111
        112
        113
        114
        115
        116
        117
        118
        119
        120
        121
        122
        123
        124
        125
        126
        127
        128
        129
        130
        131
        132
        133
        134
        135
        136
        137
        138
        139
        140
        141
        142
        143
        public class DatabaseProvider extends ContentProvider {
        public static final int BOOK_DIR = 0;
        public static final int BOOK_ITEM = 1;
        public static final int CATEGORY_DIR = 2;
        public static final int CATEGORY_ITEM = 3;
        public static final String AUTHORITY = "com.example.databasetest.provider";
        private static UriMatcher uriMatcher;
        private static MyDatabaseHelper dbHelper;

        static{
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);//无匹配时返回NO_MATCH
        uriMatcher.addURI(AUTHORITY,"book",BOOK_DIR);
        uriMatcher.addURI(AUTHORITY,"book/#",BOOK_ITEM);
        uriMatcher.addURI(AUTHORITY,"category",CATEGORY_DIR);
        uriMatcher.addURI(AUTHORITY,"category/#",CATEGORY_ITEM);
        }
        public DatabaseProvider() {
        }

        @Override
        public int delete(Uri uri, String selection, String[] selectionArgs) {
        // Implement this to handle requests to delete one or more rows.
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int deleteRows = 0;
        switch (uriMatcher.match(uri)){
        case BOOK_DIR:
        deleteRows = db.delete("Book",selection,selectionArgs);
        break;
        case BOOK_ITEM:
        String bookId = uri.getPathSegments().get(1);
        deleteRows = db.delete("Book","id=?",new String[]{bookId});
        break;
        case CATEGORY_DIR:
        deleteRows = db.delete("Category",selection,selectionArgs);
        break;
        case CATEGORY_ITEM:
        String categoryId = uri.getPathSegments().get(1);
        deleteRows = db.delete("Category","id=?",new String[]{categoryId});
        break;
        default:
        }
        return deleteRows;
        }

        @Override
        public String getType(Uri uri) {
        // TODO: Implement this to handle requests for the MIME type of the data
        // at the given URI.
        switch (uriMatcher.match(uri)){
        case BOOK_DIR:
        return "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.book";
        case BOOK_ITEM:
        return "vnd.android.cursor.item/vnd.com.example.databasetest.provider.book";
        case CATEGORY_DIR:
        return "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.category";
        case CATEGORY_ITEM:
        return "vnd.android.cursor.item/vnd.com.example.databasetest.provider.category";
        }
        return null;
        }

        @Override
        public Uri insert(Uri uri, ContentValues values) {
        // TODO: Implement this to handle requests to insert a new row.
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        Uri uriReturn = null;
        switch(uriMatcher.match(uri)){
        case BOOK_DIR:
        case BOOK_ITEM:
        long newBookId = db.insert("Book",null,values);
        uriReturn = Uri.parse("content://"+AUTHORITY+"/book/"+newBookId);
        break;
        case CATEGORY_DIR:
        case CATEGORY_ITEM:
        long newCategoryId = db.insert("Category",null,values);
        uriReturn = Uri.parse("content://"+AUTHORITY+"/category/"+newCategoryId);
        break;
        default:
        break;
        }
        return uriReturn;
        }

        @Override
        public boolean onCreate() {
        // TODO: Implement this to initialize your content provider on startup.
        dbHelper = new MyDatabaseHelper(getContext(),"BookStore.db",null,2);
        return true;
        }

        @Override
        public Cursor query(Uri uri, String[] projection, String selection,
        String[] selectionArgs, String sortOrder) {
        // TODO: Implement this to handle query requests from clients.
        SQLiteDatabase db = dbHelper.getReadableDatabase();
        Cursor cursor = null;
        switch(uriMatcher.match(uri)){
        case BOOK_DIR:
        cursor = db.query("Book",projection,selection,selectionArgs,null,null,sortOrder);
        break;
        case BOOK_ITEM:
        String bookId = uri.getPathSegments().get(1);
        cursor = db.query("Book",projection,"id=?",new String[]{bookId},null,null,sortOrder);
        break;
        case CATEGORY_DIR:
        cursor = db.query("Category",projection,selection,selectionArgs,null,null,sortOrder);
        break;
        case CATEGORY_ITEM:
        String categoryId = uri.getPathSegments().get(1);
        cursor = db.query("Category",projection,"id=?",new String[]{categoryId},null,null,sortOrder);
        break;
        default:
        break;
        }
        return cursor;
        }

        @Override
        public int update(Uri uri, ContentValues values, String selection,
        String[] selectionArgs) {
        // TODO: Implement this to handle requests to update one or more rows.
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int updateRows = 0;
        switch(uriMatcher.match(uri)){
        case BOOK_DIR:
        updateRows = db.update("Book",values,selection,selectionArgs);
        break;
        case BOOK_ITEM:
        String bookId = uri.getPathSegments().get(1);
        updateRows = db.update("Book",values,"id=?",new String[]{bookId});
        break;
        case CATEGORY_DIR:
        updateRows = db.update("Category",values,selection,selectionArgs);
        break;
        case CATEGORY_ITEM:
        String categoryId = uri.getPathSegments().get(1);
        updateRows = db.update("Category",values,"id=?",new String[]{categoryId});
        break;
        default:
        }
        return updateRows;
        }
        }
        1. 在AM中注册:自动
  8. 运用多媒体

    1. 使用通知

      1
      2
      3
      4
      5
      6
      7
      NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
      Notification notification = new NotificationCompat.Builder(MainActivity.this)
      .setContentTitle("This is content title")
      .setContentText("This is content text")
      .setWhen(System.currentTimeMillis())
      .setSmallIcon(R.mipmap.ic_launcher) .setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher)).build();
      manager.notify(1,notification);

      设置意图PendingIntent来启动Intent,设置自动取消:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      Intent intent = new Intent(MainActivity.this,NotificationActivity.class);
      PendingIntent pi = PendingIntent.getActivity(MainActivity.this,0,intent,0);
      NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
      Notification notification = new NotificationCompat.Builder(MainActivity.this)
      .setContentTitle("This is content title")
      .setContentText("This is content text")
      .setWhen(System.currentTimeMillis())
      .setSmallIcon(R.mipmap.ic_launcher)
      .setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher))
      .setContentIntent(pi)
      .setAutoCancel(true)
      .build();
      manager.notify(1,notification);
    2. 调用摄像头

      1. 在主活动xml中添加按钮和ImageView,用来拍照和显示照片

      2. 在MainActivity中给按钮添加点击动作:创建File对象,参数为getCacheDir返回内部缓存路径和文件名;如果文件存在则删除,创建文件;接下来需要把文件对象转为uri,在a7.0以下只需要Uri.fromFile放入File文件即可,在a7.0及以上需要调用FileProvider(这是内容提供器,需在AM注册)的getUriForFile方法,三个参数为环境、唯一字符串和File对象;最后构建Intent对象,action为android.media.action.IMAGE_CAPTURE,再调用Intent的putExtra,第一个参数为MediaStore.EXTRA_OUTPUT用于指定相机应用将拍摄的照片保存到的 URI 地址,第二个参数放入前面得到的uri,然后用startActivityForResult启动intent(需要重写onActivityResult)。

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button takeButton = (Button) findViewById(R.id.take_photo);
        picture = (ImageView) findViewById(R.id.picture);
        takeButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
        File outputImage = new File(getCacheDir(),"output_image.jpg");
        try{
        if(outputImage.exists()){
        outputImage.delete();
        }
        outputImage.createNewFile();
        }catch (IOException e){
        e.printStackTrace();
        }
        if(Build.VERSION.SDK_INT>=24){
        imageUri = FileProvider.getUriForFile(MainActivity.this,"com.example.cameraalbumtest.fileprovider",outputImage);
        }else{
        imageUri = Uri.fromFile(outputImage);
        }
        Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
        intent.putExtra(MediaStore.EXTRA_OUTPUT,imageUri);
        startActivityForResult(intent,1);
        }
        });
        }
      3. 重写onActivityResult:

        如果拍照成功,则调用BitmapFactory的decodeStream方法将照片解析为Bitmap对象,用getContentResolver().openInputStream把uri引入;然后ImageView对象setImageBitmap

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        @Override
        protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        switch (requestCode){
        case 1:
        if(resultCode == RESULT_OK){
        try{
        Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
        picture.setImageBitmap(bitmap);
        }catch (FileNotFoundException e){
        e.printStackTrace();
        }

        }
        break;
        default:
        }
        }
      4. 在AM注册Provider

        1
        2
        3
        4
        5
        6
        7
        8
        9
        <provider
        android:name="androidx.core.content.FileProvider"
        android:authorities="com.example.cameraalbumtest.fileprovider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths"/>
        </provider>

        android:authorities属性的值必须要和刚才FileProvider.getUriForFile()方法中的第二个参数一致;在<provider>标签的内部使用<meta-data>来指定Uri的共享路径,并引用了一个@xml/file_paths资源

        创建file_paths.xml文件,name属性的值可以随便填,path属性的值表示共享的具体路径(. 表示当前目录,也就是应用程序的缓存目录的根目录。 这意味着 FileProvider 可以共享应用程序缓存目录中的所有文件。)

        1
        2
        3
        4
        <?xml version="1.0" encoding="utf-8" ?>
        <paths xmlns:android="http://schemas.android.com/apk/res/android">
        <cache-path name="my_images" path="."/>
        </paths>
    3. 从相册选择照片:第二版书中内容太旧,在a14已无法使用

    4. 播放多媒体文件:

      1. 播放音频MediaPlayer

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64
        65
        66
        67
        68
        69
        70
        71
        72
        private MediaPlayer mediaPlayer = new MediaPlayer();
        @Override
        protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button play = (Button) findViewById(R.id.play);
        Button pause = (Button) findViewById(R.id.pause);
        Button stop = (Button) findViewById(R.id.stop);
        play.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
        if(!mediaPlayer.isPlaying()){
        mediaPlayer.start();
        }
        }
        });
        pause.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
        if(mediaPlayer.isPlaying()){
        mediaPlayer.pause();
        }
        }
        });
        stop.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
        if(mediaPlayer.isPlaying()){
        mediaPlayer.stop();
        initMediaPlayer();
        }
        }
        });
        if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED){
        ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
        }else{
        initMediaPlayer();
        }
        }

        @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode){
        case 1:
        if(grantResults.length>0&&grantResults[0]==PackageManager.PERMISSION_GRANTED){
        initMediaPlayer();
        }else{
        Toast.makeText(this,"拒绝权限将无法使用程序", Toast.LENGTH_SHORT).show();
        finish();
        }
        break;
        default:
        }
        }

        private void initMediaPlayer(){
        try{
        File file = new File(Environment.getExternalStorageDirectory(),"music.mp3");
        mediaPlayer.setDataSource(file.getPath());
        mediaPlayer.prepare();
        }catch (Exception e){
        e.printStackTrace();
        }
        }

        @Override
        protected void onDestroy() {
        super.onDestroy();
        if(mediaPlayer!=null){
        mediaPlayer.stop();
        mediaPlayer.release();}
        }
      2. 播放视频VideoView:看书吧

  9. 使用网络技术

    1. WebView用法

      使用WebView显示网页:

      1
      2
      3
      4
      WebView webView = findViewById(R.id.web_view);
      webView.getSettings().setJavaScriptEnabled(true);
      webView.setWebViewClient(new WebViewClient());//仍在此WebView中显示而不是跳转浏览器
      webView.loadUrl("https://music.apple.com/");
    2. 使用http协议访问网络

      1. 使用HttpURLConnection

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64
        65
        66
        67
        TextView responseText;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
        Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
        v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
        return insets;
        });
        Button sendRequest = findViewById(R.id.send_request);
        sendRequest.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
        sendRequestWithHttpURLConnection();
        }
        });
        responseText = findViewById(R.id.response_test);
        }

        private void sendRequestWithHttpURLConnection() {
        new Thread(new Runnable() {
        @Override
        public void run() {
        HttpURLConnection connection = null;
        BufferedReader reader = null;
        try{
        URL url = new URL("https://www.baidu.com");
        connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("GET");
        connection.setConnectTimeout(8000);
        connection.setReadTimeout(8000);
        InputStream in = connection.getInputStream();
        //下面对获取到的输入流进行读取
        reader = new BufferedReader(new InputStreamReader(in));
        StringBuilder response = new StringBuilder();
        String line;
        while((line = reader.readLine())!=null){
        response.append(line);
        }
        showResponse(response.toString());
        } catch (Exception e) {
        throw new RuntimeException(e);
        }finally {
        if(reader!=null){
        try{
        reader.close();
        }catch (IOException e){
        e.printStackTrace();
        }
        }
        if(connection!=null){
        connection.disconnect();
        }
        }
        }
        }).start();
        }
        private void showResponse(String response){
        runOnUiThread(new Runnable() {
        @Override
        public void run() {
        responseText.setText(response);
        }
        });
        }
      2. 使用OkHttp:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        private void sendRequestWithOkHttp(){
        new Thread(new Runnable() {
        @Override
        public void run() {
        try {
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
        .url("https://www.baidu.com")
        .build();
        Response response = client.newCall(request).execute();
        String responseData = response.body().string();
        showResponse(responseData);
        } catch (Exception e) {
        e.printStackTrace();
        }
        }
        }).start();
        }
    3. 解析xml格式数据

      有点难 看书吧

  10. 服务

    1. 多线程编程:

      1. 在子线程中更新UI

        经过测试这是不被允许的

        android提供了一套异步消息处理机制,解决子线程中进行ui操作的问题

      2. 异步消息处理机制:

        主要由4个部分组成:Message Handler MessageQuene Looper

        Message

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

        Handler

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

        MessageQueue

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

        Looper

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

        异步消息处理的整个流程:

        首先,需要在主线程当中创建一个Handler对象,并重写handleMessage()方法。
        然后,当子线程中需要进行UI操作时,就创建一个Message对象,并通过Handler将这条消息发送出去。之后,这条消息会被添加到MessageQueue的队列中等待被处理,而Looper则会一直尝试从MessageQueue中取出待处理消息。

      3. 使用AsyncTask:

        这是一个抽象类,继承需要重写onPreExecute doInBackground onProgressUpdate onPostExecute,需要设置三个泛型参数Params Progress Result

    2. 服务

      1. 定义一个服务

        可以重写

        • onCreate() 方法会在服务创建的时候调用。
        • onStartCommand() 方法会在每次服务启动的时候调用。
        • onDestroy() 方法会在服务销毁的时候调用。
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        startService.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
        Intent startIntent = new Intent(MainActivity.this, MyService.class);
        startService(startIntent);
        }
        });
        stopService.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
        Intent stopIntent = new Intent(MainActivity.this, MyService.class);
        stopService(stopIntent);
        }
        });
      2. 活动和服务进行通信

        首先在服务的代码中继承Binder,写一些方法;创建一个实例;然后在onBind中返回实例。

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        public class MyService extends Service {
        public MyService() {
        }

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

        然后在活动代码中创建ServiceConnection的匿名类,然后重写两个方法;在绑定服务和活动时使用bindService三个参数:bindIntent对象,ServiceConnection实例,和标志位(这里传入BIND_AUTO_CREATE表示在活动和服务进行绑定后自动创建服务。这会使得MyService中的onCreate()方法得到执行,但onStartCommand()方法不会执行。)

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        private MyService.DownloadBinder downloadBinder;
        private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
        downloadBinder = (MyService.DownloadBinder) service;
        downloadBinder.startDownload();
        downloadBinder.getProgress();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
        };

        @Override
        protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        Button bindService = findViewById(R.id.bind_service);
        Button unbindService = findViewById(R.id.unbind_service);
        bindService.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
        Intent bindIntent = new Intent(MainActivity.this, MyService.class);
        bindService(bindIntent,connection,BIND_AUTO_CREATE);
        }
        });
        unbindService.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
        unbindService(connection);
        }
        }
      3. 服务的生命周期:

        服务也有自己的生命周期,前面我们使用到的onCreate()、onStartCommand()、onBind()和onDestroy()等方法都是在服务的生命周期内可能回调的方法。

        一旦在项目的任何位置调用了Context的startService()方法,相应的服务就会启动起来,并回调onStartCommand()方法。如果这个服务之前还没有创建过,onCreate()方法会先于onStartCommand()方法执行。服务启动了之后会一直保持运行状态,直到stopService()或stopSelf()方法被调用。

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

        另外,还可以调用Context的bindService()来获取一个服务的持久连接,这时就会回调服务中的onBind()方法。类似地,如果这个服务之前还没有创建过,onCreate()方法会先于onBind()方法执行。之后,调用方可以获取到onBind()方法里返回的IBinder对象的实例,这样就能自由地和服务进行通信了。只要调用方和服务之间的连接没有断开,服务就会一直保持运行状态。

        当调用了startService()方法后,又去调用stopService()方法,这时服务中的onDestroy()方法就会执行,表示服务已经销毁了。类似地,当调用了bindService()方法后,又去调用unbindService()方法,onDestroy()方法也会执行。

        需要注意,我们是完全有可能对一个服务既调用了startService()方法,又调用了bindService()方法的,这种情况下该如何才能让服务销毁掉呢?根据Android系统的机制,一个服务只要被启动或者被绑定了之后,就会一直处于运行状态,必须要让以上两种条件同时不满足,服务才能被销毁。所以,这种情况下要同时调用stopService()和unbindService()方法,onDestroy()方法才会执行。

      4. 服务的更多技巧:

        1. 使用前台服务:

          前台服务优先级高,免杀。

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          public void onCreate() {
          super.onCreate();
          Log.d("MyService", "onCreate executed");

          // 创建通知渠道(仅在 Android 8.0 及更高版本需要)
          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
          NotificationChannel channel = new NotificationChannel("my_channel_id", "My Channel Name", NotificationManager.IMPORTANCE_DEFAULT);
          NotificationManager manager = getSystemService(NotificationManager.class);
          manager.createNotificationChannel(channel);
          }

          Intent intent = new Intent(this, MainActivity.class);
          PendingIntent pi = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
          Notification notification = new NotificationCompat.Builder(this, "my_channel_id")
          .setContentText("This is content text")
          .setContentTitle("This is content title")
          .setWhen(System.currentTimeMillis())
          .setSmallIcon(R.mipmap.ic_launcher)
          .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
          .setContentIntent(pi)
          .build();
          startForeground(1, notification);
          }
        2. 使用IntentService

          用它建立线程执行完成后会自动destory

          当您使用 startService() 方法启动 IntentService 时,IntentService 会创建一个工作线程。
          IntentService 会将收到的 Intent 放入一个工作队列中。
          工作线程会依次从工作队列中取出 Intent,并调用 onHandleIntent() 方法来处理 Intent。
          onHandleIntent() 方法执行完毕后,工作线程会自动停止。
          如果工作队列中还有其他 Intent,工作线程会继续处理,直到所有 Intent 都处理完毕。 因此,onHandleIntent() 方法会在以下情况下被调用:

          先继承IntentService来写服务内容

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          public class MyIntentService extends IntentService {
          public MyIntentService() {
          super("MyIntentService");
          }

          @Override
          protected void onHandleIntent(@Nullable Intent intent) {
          Log.d("MyIntentService", "Thread id is"+Thread.currentThread().getId());
          }

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

          然后在活动中创建使用startService方法;

          最后不要忘记在AM中注册service。

      5. 服务的最佳实践 下载示例

        1. 首先定义一个回到接口,用于对下载过程中的各种状态进行监听和回调

          1
          2
          3
          4
          5
          6
          7
          public interface DownloadListner {
          void onProgress(int progress);
          void onSuccess();
          void onFailed();
          void onPaused();
          void onCanceled();
          }
        2. 写DownloadTask类 继承AsyncTask,实现子线程工作

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31
          32
          33
          34
          35
          36
          37
          38
          39
          40
          41
          42
          43
          44
          45
          46
          47
          48
          49
          50
          51
          52
          53
          54
          55
          56
          57
          58
          59
          60
          61
          62
          63
          64
          65
          66
          67
          68
          69
          70
          71
          72
          73
          74
          75
          76
          77
          78
          79
          80
          81
          82
          83
          84
          85
          86
          87
          88
          89
          90
          91
          92
          93
          94
          95
          96
          97
          98
          99
          100
          101
          102
          103
          104
          105
          106
          107
          108
          109
          110
          111
          112
          113
          114
          115
          116
          117
          118
          119
          120
          121
          122
          123
          124
          125
          126
          127
          128
          129
          130
          public class DownloadTask extends AsyncTask<String,Integer,Integer> {
          public static final int TYPE_SUCCESS = 0;
          public static final int TYPE_FAILED = 1;
          public static final int TYPE_PAUSED = 2;
          public static final int TYPE_CANCELED = 3;

          private DownloadListner listener;
          private boolean isCanceled = false;
          private boolean isPaused = false;
          private int lastProgress;

          public DownloadTask(DownloadListner listener) {
          this.listener = listener;
          }

          @Override
          protected Integer doInBackground(String... strings) {
          InputStream is = null;
          RandomAccessFile savedFile = null;
          File file = null;
          try{
          long downloadedLength = 0;
          String downloadUrl = strings[0];
          String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
          String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
          file = new File(directory+fileName);
          if(file.exists()){
          downloadedLength = file.length();
          }
          long contentLength = getContentLength(downloadUrl);
          if(contentLength==0){
          return TYPE_FAILED;
          }else if(contentLength==downloadedLength){
          return TYPE_SUCCESS;
          }
          OkHttpClient client = new OkHttpClient();
          Request request = new Request.Builder()
          .addHeader("RANGE","bytes="+downloadedLength+"-")
          .url(downloadUrl)
          .build();
          Response response = client.newCall(request).execute();
          if(response != null){
          is = response.body().byteStream();
          savedFile = new RandomAccessFile(file,"rw");
          savedFile.seek(downloadedLength);
          byte[] b = new byte[1024];
          int total = 0;
          int len;
          while((len=is.read(b))!=-1){
          if(isCanceled){
          return TYPE_CANCELED;
          }else if(isPaused){
          return TYPE_PAUSED;
          }else{
          total += len;
          savedFile.write(b,0,len);
          int progress = (int) ((total+downloadedLength)*100/contentLength);
          publishProgress(progress);
          }
          }
          response.body().close();
          return TYPE_SUCCESS;
          }
          }catch (Exception e){
          e.printStackTrace();
          }finally {
          try{
          if(is != null){
          is.close();
          }
          if(savedFile != null){
          savedFile.close();
          }
          if(isCanceled&&file != null){
          file.delete();
          }
          } catch (Exception e) {
          e.printStackTrace();
          }
          }
          return TYPE_FAILED;
          }

          @Override
          protected void onProgressUpdate(Integer... values) {
          int progress = values[0];
          if(progress>lastProgress){
          listener.onProgress(progress);
          lastProgress = progress;
          }
          }

          @Override
          protected void onPostExecute(Integer integer) {
          switch(integer){
          case TYPE_SUCCESS:
          listener.onSuccess();
          break;
          case TYPE_FAILED:
          listener.onFailed();
          break;
          case TYPE_PAUSED:
          listener.onPaused();
          break;
          case TYPE_CANCELED:
          listener.onCanceled();
          break;
          default:
          }
          }
          public void pauseDownload(){
          isPaused = true;
          }
          public void cancelDownload(){
          isCanceled = true;
          }
          private long getContentLength(String downloadUrl) throws IOException{
          OkHttpClient client = new OkHttpClient();
          Request request = new Request.Builder()
          .url(downloadUrl)
          .build();
          Response response = client.newCall(request).execute();
          if(response!= null&&response.isSuccessful()){
          long contentLength = response.body().contentLength();
          response.body().close();
          return contentLength;
          }
          return 0;
          }
          }
        3. 写DownloadService服务类,在里面重写并实例了DownloadListener接口,并写DownloadBinder继承Binder,在binder的任务开始方法中将DownloadTask实例并放入参数为DownloadListener实例

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31
          32
          33
          34
          35
          36
          37
          38
          39
          40
          41
          42
          43
          44
          45
          46
          47
          48
          49
          50
          51
          52
          53
          54
          55
          56
          57
          58
          59
          60
          61
          62
          63
          64
          65
          66
          67
          68
          69
          70
          71
          72
          73
          74
          75
          76
          77
          78
          79
          80
          81
          82
          83
          84
          85
          86
          87
          88
          89
          90
          91
          92
          93
          94
          95
          96
          97
          98
          99
          100
          public class DownloadService extends Service {
          private DownloadTask downloadTask;
          private String downloadUrl;

          private DownloadListner listner = new DownloadListner() {
          @Override
          public void onProgress(int progress) {
          getNotificationManager().notify(1,getNotification("Downloading...",progress));
          }

          @Override
          public void onSuccess() {
          downloadTask = null;
          stopForeground(true);
          getNotificationManager().notify(1,getNotification("Download Success",-1));
          Toast.makeText(DownloadService.this,"Download success",Toast.LENGTH_SHORT).show();
          }

          @Override
          public void onFailed() {
          downloadTask = null;
          stopForeground(true);
          getNotificationManager().notify(1,getNotification("Download Failed",-1));
          Toast.makeText(DownloadService.this,"Download failed",Toast.LENGTH_SHORT).show();
          }

          @Override
          public void onPaused() {
          downloadTask = null;
          Toast.makeText(DownloadService.this,"Paused",Toast.LENGTH_SHORT).show();
          }

          @Override
          public void onCanceled() {
          downloadTask = null;
          stopForeground(true);
          Toast.makeText(DownloadService.this,"Canceled",Toast.LENGTH_SHORT).show();
          }
          };
          public DownloadService() {
          }

          private DownloadBinder mBinder = new DownloadBinder();
          @Override
          public IBinder onBind(Intent intent) {
          // TODO: Return the communication channel to the service.
          return mBinder;
          }
          class DownloadBinder extends Binder {
          @SuppressLint("ForegroundServiceType")
          public void startDownload(String url){
          if(downloadTask==null){
          downloadUrl = url;
          downloadTask = new DownloadTask(listner);
          downloadTask.execute(downloadUrl);
          startForeground(1,getNotification("Downloding...",0));
          Toast.makeText(DownloadService.this,"Downloading...",Toast.LENGTH_SHORT).show();
          }
          }
          public void pauseDownload(){
          if(downloadTask!=null){
          downloadTask.pauseDownload();
          }
          }
          public void cancelDownload(){
          if (downloadTask != null) {
          downloadTask.cancelDownload();
          }else{
          if(downloadUrl!=null){
          String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
          String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
          File file = new File(directory+fileName);
          if(file.exists()){
          file.delete();
          }
          getNotificationManager().cancel(1);
          stopForeground(true);
          Toast.makeText(DownloadService.this,"Canceled",Toast.LENGTH_SHORT).show();
          }
          }
          }
          }
          private NotificationManager getNotificationManager(){
          return (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
          }
          private Notification getNotification(String title,int progress){
          Intent intent = new Intent(this,MainActivity.class);
          PendingIntent pi = PendingIntent.getActivity(this,0,intent,PendingIntent.FLAG_IMMUTABLE);
          NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
          builder.setSmallIcon(R.mipmap.ic_launcher);
          builder.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher));
          builder.setContentIntent(pi);
          builder.setContentTitle(title);
          if(progress>=0){
          builder.setContentText(progress+"%");
          builder.setProgress(100,progress,false);
          }
          return builder.build();
          }
          }
        4. 在活动中创建DownloadBinder实例,并创建服务连接,定义连接中的活动,为按键定义活动。需要请求写存储权限,需要网络权限,需要在活动结束后解除binder绑定:

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31
          32
          33
          34
          35
          36
          37
          38
          39
          40
          41
          42
          43
          44
          45
          46
          47
          48
          49
          50
          51
          52
          53
          54
          55
          56
          57
          58
          59
          60
          61
          62
          63
          64
          65
          66
          67
          68
          69
          70
          71
          72
          73
          74
          public class MainActivity extends AppCompatActivity {
          private DownloadService.DownloadBinder downloadBinder;
          private ServiceConnection connection = new ServiceConnection() {
          @Override
          public void onServiceConnected(ComponentName name, IBinder service) {
          downloadBinder = (DownloadService.DownloadBinder) service;
          }

          @Override
          public void onServiceDisconnected(ComponentName name) {

          }
          };

          @Override
          protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          EdgeToEdge.enable(this);
          setContentView(R.layout.activity_main);
          ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
          Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
          v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
          return insets;
          });

          Button startDownload = findViewById(R.id.start_download);
          Button pauseDownload = findViewById(R.id.pause_download);
          Button cancelDownload = findViewById(R.id.cancel_download);
          startDownload.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {
          String url = "https://github.com/BIGGGJerry/Course_In_SDUCST/archive/refs/heads/main.zip";
          downloadBinder.startDownload(url);
          }
          });
          pauseDownload.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {
          downloadBinder.pauseDownload();
          }
          });
          cancelDownload.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {
          downloadBinder.cancelDownload();
          }
          });
          Intent intent = new Intent(this,DownloadService.class);
          startService(intent);
          bindService(intent,connection,BIND_AUTO_CREATE);
          if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED){
          ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
          }
          }

          @Override
          public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
          switch (requestCode){
          case 1:
          if(grantResults.length>0&&grantResults[0]!=PackageManager.PERMISSION_GRANTED){
          Toast.makeText(this,"拒绝权限将无法使用程序",Toast.LENGTH_SHORT).show();
          finish();
          }
          break;
          default:
          }
          }

          @Override
          protected void onDestroy() {
          super.onDestroy();
          unbindService(connection);
          }
          }