Fork me on GitHub Android 《第一行代码》简要笔记(四) - 冰路梦 | binglumeng

Android 《第一行代码》简要笔记(四)

Posted by 冰路梦 on 2017-06-08

Android 《第一行代码》简要笔记(四)

第十章、Service

1、线程的基本用法

  • 继承Thread覆写run方法

    1
    2
    3
    4
    5
    6
    class MyThread extends Thread{
    @Override
    public void run(){
    //子线程逻辑
    }
    }

    然后new MyThread().start()即可调用子线程。

  • 实现Runnable接口

    1
    2
    3
    4
    5
    6
    class MyRunnable implements Runnable {
    @Override
    public void run(){
    //子线程逻辑
    }
    }

    然后new Thread(new MyRunnable()).start()推荐此种方式,便于解耦

  • 习惯写法

    1
    2
    3
    4
    5
    6
    new Thread(new Runnable(){
    @Override
    public void run(){
    //
    }
    }).start();

2、线程间通讯

Android中子线程是不允许更新UI的。常用的方式:Handler和AsyncTask

  • Handler

    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
    public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    public static final int UPDATE_TEXT = 1;
    private TextView text;
    private Handler handler = new Handler(){
    @Override
    public void handleMessage(Message msg){
    switch(msg.what){
    case UPDATE_TEXT:
    text.setText("After Update Text");
    break;
    default:
    break;
    }
    }
    };
    ...
    new Thread(new Runnabe(){
    @Override
    public void run(){
    Message msg = new Message();
    msg.what = UPDATE_TEXT;
    handler.sendMessage(msg);
    }
    }).start();
    ...
    }

简要分析Handler机制:

Android中异步消息处理主要有4部分组成:messagehandlerMeaageQueueLooper;

  • Message

    消息类,用于线程间少量信息的通讯。含有whatarg1arg2obj

  • Handler

    消息处理者,sendMessage()handleMessage()方法

  • MessageQueue

    消息队列,一个线程一个队列FIFO的消息栈。存放handler发来的消息,由Looper循环,再有handler取出处理。

  • Looper

    消息循环管理者,调用loop()方法,进入无限循环,用于取出消息给handler,每个线程一个looper。

  • Async Task

    Android系统提供的异步操作类。使用时需要继承AsyncTask并实现对应的方法。需要制定三个参数

    • Params

      执行AsyncTask需要传入的参数,用于后台操作使用。

    • Progress

      后台执行时候,可用于前台显示进度,泛型作为进度单位。

    • Result

      后台执行完毕,需要返回的结果类型。

AsyncTask需要实现的子类方法:

  • onPreExecute(),任务执行前准备工作
  • doInBackground(Params…),后台执行的逻辑,如要反馈进度,调用publishProgress(Progress...)
  • onProgressUpdate(Progress…),更新进度
  • onPostExecute(Result),后台执行完毕,返回给前台数据。
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
class DownloadTask extends AsyncTask<Void,Integer,Boolean>{
@Override
protected void onPreExecute(){
progressDialog.show();//显示进度条
}
@Override
protected Boolean doInBackground(Void... params){
//...do something
return true;
}
@Override
protected void onProgressUpdate(Integer... values){
//显示进度
progressDialog.setMessage("Downloaded "+ values[0]+ " %");
}
@Override
protected void onPostExecute(Boolean result){
progressDialog.dismiss();
//... do something
}
}

3、Service

Android四大组件之一,需要子类继承Service来实现相应的逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
class MyService extends Service{
//构造函数
public MyService(){
}
//用于绑定
@Override
public IBinder onBind(Intent intent){
//此时暂时抛个异常,下面会实现这个方法。
throw new UnSupportedOperationException("Not ye implented");
}
}

Service的启动有两种:Context.startService()Context.bindService();前者启动服务后,不再过问,后者是与Service共命运。

停止可以用Service.stopSelf()即可停止。或者Context.stopServie(),Context.unbindService只是解绑,并不能停止。

  • Activity与Service通讯

    通过Binder沟通Activity与Service

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    ...
    class MyBinder extends Binder {
    //自定义方法
    }
    ...
    //Service中
    @Override
    public IBinder onBind(Intent intent){
    return new MyBinder();
    }

    Activity中需要持有这个链接通讯:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    ...
    private ServiceConnection conn = new ServiceConnection(){
    @Override
    public void onServiceDisconnected(ComponentName name){
    //断开链接,,,
    }
    @Override
    public void onServiceConnected(ComponentName name,IBinder service){
    MyBinder myBinder = (MyBinder)service;
    //获取Binder对象后,操作对应定义的方法,实现通讯。
    }
    }

    绑定服务bingService(intent,conn,BIND_AUTO_CREATE);

  • Service 生命周期

    onCreate()–>onStartCommand()–>onBind()–>onUnbind()–>onDestroy(),还有一个onRebind();

    不论多次调用onStartCommand()都将是一个Service实例,而且只需要调用一次StopService或stopSelf即可停止。

  1. 只startService,stopService即可停止。
  2. 只bindService,unbindService即可销毁。
  3. startService后,又bindService,则需要unbindService,再stopService方可销毁。

前台服务,是服务处于前台,而不会轻易的被销毁,以前什么优先级、系统级,双服务之类的,效果都不那么好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MyService extends Service {
...
@Override
public void onCreate(){
super.onCreate();
//创建前台服务,需要用的PendingIntent,设置成通知Notification形式,但是不显示出来。
Intent intent = new Intent(this,MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this,0,intnet,0);
//构建Notification
Notification notification = new NotificationCompat.Builder(this)
.setContentTitle("Content title")
.setContentText("content text")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResource(),R.mipmap.ic_launcher))
.setContentIntent(pi)
.build();
//id,notification,此处没用NotificationManager,而用startForground,可以是service变为前台服务。
startForeground(1,notification);
}
}
  • IntentService

    Service也是运行在主线程,不能运行耗时操作。若在Service中开启异步逻辑,需要的时候,可用service的stopSelf停止自己。

    而Android官方提供了一个IntentService可以自动运行完逻辑,就可停止的Service。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class MyIntentService extends IntentService {
    public MyIntentService(){
    super("MyIntentService");
    }
    @Override
    protected void onHandleIntent(Intent intent){
    //处理逻辑,这里就是在异步子线程运行了,不用在new新的线程,而且运行完这里的逻辑,service会自动销毁。
    }
    @Override
    public void onDestroy(){
    super.onDestroy();
    }
    }

    Android四大组建,都要在AndroidManifest中注册的哦

Git实践:

1
2
3
4
5
6
7
8
9
10
11
git branch
git checkout branch_name
git merge other_branch_name
git branch -D branch_name
git push origin branch_name
# master 为分支名
git fetch origin master
git diff origin/master
git merge origin/master
#
git pull origin master

其他章节

使用第三方的服务SDK的讲解,最好的是看官方文档和示例代码。如Baidu定位、地图等。

12、material design

Android官方推荐ToolBar替换ActionBar。

1
2
3
4
5
ActionBar bar = getSupportActionBar();//其实xml布局中使用了ToolBar
if(bar != null){
bar.setDisplayHomeAsUpEnable(true);//HomeAsUp 就是ActionBar最左侧那个按钮。
bar.setHomeAsUpIndicator(R.drawable.ic);
}
1
2
3
4
...
<android.support.v7.widget.Toolbar
....>
</android.support.v7.widget.Toolbar>

滑动菜单可以用DrawerLayout、Google还提供了更好的选择,就是NavigationView在依赖兼容库中

1
compile 'com.android.support:design:24.2.1'

NavigationView中的item可以分组,可以设置checkableBehavior="single"单个选择。

  • 悬浮按钮和可交互提示

    FloatinActionButton

    1
    2
    3
    4
    5
    6
    7
    8
    <android.support.design.widget.FloatingActionButton
    android:id="@+id/fab"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom|end"
    android:layout_margin="16dp"
    android:src="@drawable/ic_done"
    android:elevation="8dp"/>

    其中elevation属性表示投影效果,数值大,就范围大而浅。其他属性类似Button没特别的。

  • SnackBar

    SnackBar类似与toast,但是多了一个可以撤销的按钮。它放在其他按钮中,调用到它的逻辑。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    ...
    fab.setOnClickListener(new View.OnClickListener(){
    @Override
    public void onClick(View view){
    Snackbar.make(view,"data deleted ?",Snackbar.LENGTH_SHORT)
    .setAction("Undo",new View.OnClickListener(){
    @Override
    public void onClick(View v){
    //撤销操作
    }
    }).show();
    }
    });
    ...
  • CoordinatorLayout

    CoordinatorLayout可谓是增强版的FrameLayout,如上悬浮按钮fab点击后弹出一个snackbar,会遮挡到fab。这时需要使用CoordinatorLayout来自动处理子空间的事件。它会自动偏移其他控件,来专注于当前。

    如上Bug,则需要把fab放入到CoordinatorLayout中即可,因为snackbar也是在fab中点击的,所以可以被控制。

  • CardView

    1
    2
    3
    4
    5
    6
    7
    <android:support.v7.widget.CardView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:cardCornerRadius="4dp"
    app:elevation="5dp">
    ...
    </android:support.v7.widget.CardView>

    cardview需要单独添加依赖包

  • Glide图片加载库

    1
    compile 'com.github.bumptech.glide:glide:3.7.0'
    1
    Glide.with(mContext).load(imageId).into(ImageView)//glide的用法,更多可以参照api

全局布局时候,RecyclerView会吧ActionBar遮挡,原因是FrameLayout的内部布局从左上开始的缘故。

解决方案,使用AppBarLayout包裹住Toolbar然后下面的控件,设置layout_behavior属性

1
2
3
4
5
6
7
8
9
10
11
12
13
...
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
andorid:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
....>
</android.support.v7.widget.Toolbar>
</android.support.design.widget.AppBarLayout>
<!-- 上有AppBarLayout,这下面的RecyclerView,设置layout_behavior属性,就不会遮挡上面了-->
<android.support.v7.widget.RecyclerView
...
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
...

上面app:layout_behavior="@string/appbar_scrolling_view_behavior"都是固定的属性写法。

  • AppBarLayout

    还有一些高级用法,它能控制内部一下空间的滚动时候的事件处理

    比如Toolbar在其属性中加入app:layout_scrollFlags="scroll"enterAlways|snap"

    • scroll表示toolbar下面的recyclerView向上滚动时,toolbar会一起滚动,并隐藏。
    • enterAlways,表示recyclerView向下滚动时,toolbar会向下滚动,并重新显示。
    • snap表示,toolbar未完全隐藏或显示时候,根据滚动距离决定显示隐藏。
  • 下拉刷新

    SwipeRefreshLayout包裹listView或RecyclerView即可是它们拥有下拉刷新功能效果。

    1
    2
    3
    4
    5
    6
    7
    8
    <android.suport.v4.widget.SwipeRefreshLayout
    ...
    app:layout_behavior="@string/appbar_scrolling_view_behavior">
    <ListView
    ...>
    </ListView>
    </android.suport.v4.widget.SwipeRefreshLayout>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    SwipeRefreshLayout srl;
    srl.setonRefreshListener(new SwipeRefreshLayout.onRefreshListener(){
    @Override
    public void onRefresh({
    ...
    })
    });
    //刷新完毕,需要设置
    srl.setRefreshing(false);
  • 折叠式标题栏

    CollapsingToolbarLayout用于使Toolbar更为炫酷,但是它必须为AppBarLayout的子控件才可,而AppBarLayout又必须是CoordinatorLayout的子布局,还是看代码吧~

    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
    <android.support.design.widget.CoordinatorLayout
    ...>
    <android.support.design.widget.AppBarLayout
    ...>
    <android.support.design.widget.CollapsingToolbarLayout
    ...>
    <!--这个imageView的效果就是那种下拉折叠式标题栏的背景-->
    <ImageView
    android:scaleType="centerCrop"
    app:layout_collapseMode="parallax"
    ...>
    </ImageView>
    <android.support.v7.widget.Toolbar
    app:layout_collapseMode="pin"
    ...>
    </android.support.v7.widget.Toolbar>
    </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>
    <!--折叠式标题栏下面的正文空间-->
    <android.support.v4.widget.NestedScrollView
    ...
    app:layout_behavior="@string/appbar_scrolling_view_behavior">
    <!-- ScrollView 和 NestScrollView都只允许一个子布局,要想多布局,就用布局嵌套吧。-->
    <LinearLayout
    ...>
    ...
    </LinearLayout>
    </android.support.v4.widget.NestedScrollView> <android.support.design.widget.FloatingActionButton
    ...
    app:layout_anchor="@id/appBar"
    app:layout_anchorGravity="bottom|end"/>
    </android.support.design.widget.CoordinatorLayout>

    上面需要注意的就是layout_collapseMode属性,pin表示钉住不动,parallax表示随动偏移。layout_anchor表示设置悬浮按钮的锚点,指定控件的id即可。

    使用android:fitsSystemWindows属性,设置布局,就可以是控件与系统状态栏融合。

13、进阶知识

  • 全局Context,覆写Application, getApplication()。

  • 对象序列化

    • Serializable

      1
      2
      3
      public class Student implements Serializable {
      ....
      }
    • Parcelable

      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
      public class Student implements Parcelabe {
      private string name;
      private int age;
      ...
      @Override
      public int describeConents(){
      return 0
      }
      @Override
      public void writeToParcel(Parcel dest,int flags){
      dest.writeString(name);//写出name
      dest.writeInt(age);//写出age
      }
      public static final Parcelable.Creator<Student> CREATOR = new Parcelable.Creator<Student>(){
      @Override
      public Student createFromParcel(Parcel source){
      Student stu = new Student();
      stu.name = source.readString();
      stu.age = source.readInt();
      }
      @Override
      public Student[] newArray(int size){
      return new Student[size];
      }
      }
      }

    Intent中可以传递这些序列化的对象。

  • Log工具

    为使的开发中的log不会带入到发行办,需要做个全局开关配置,或则log级别限制。

    1. 定义Level ,只有当全局Level =< log内的级别时候,打印出相应级别一下的log,如此,发布时候,定义level大于最高的log级别,就不会输出log。
    2. 定于debug开关。封装log类。
  • 定时任务

Android中虽然也有timer定时,但是由于cpu休眠等情况,导致它的计时并不会准确。不适合长时间的定时任务。

Alarm机制

1
2
3
4
5
6
7
> AlramManager manager = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
> //十秒后执行一个任务
> long triggerAtTime = SystemClock.elapsedRealtime() + 10*1000;
> manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pendingIntent);
> //其中参数ELAPSED_REALTIME、ELAPSED_REALTIME_WAKEUP、表示系统开机以来的到现在的时间,wakeup表示唤醒cpu。
> //RTC_REALTIME、RTC_REALTIME_WAKEUP、分别表示1970年以来的时间,wakeup表示唤醒cpu。
>

>

逻辑操作也会耗时,所以定时任务放在子线程会让时间更精准点。

Android 4.4以后Alarm定时任务多有不准,因为休眠cpu时候,会逐级减少唤醒cpu的次数和增加时间间隔。若要保证任务准时,使用AlarmManagersetExact()代替set()方法。

  • Doze模式

Android 6.0引入此模式,用于省电。网络、cpu、wifi、同步、Alarm等都会在这个模式下受到影响。若要alarm精准,使得在Doze模式下也运作,调用AlarmManager.setAndAllowWhileIdle()setExactAndAllowWhildeIdle()

  • 多窗口模式,7.0引入,同时多个窗口,也只有一个真正活动,生命周期一样的。

    1
    2
    <!-- 禁用多窗口 -->
    android:resizeableActivity = "false"
  • Lambda表达式

    JAVA8的新特性,Gralde配置新增

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    android{
    ...
    defaultConfit{
    ...
    jackOptions.enabled = true
    }
    compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
    }
    ...
    }

    详细的Lambda需要专门学习一下,这里就简单说一个,单个参数,或简单参数,接口只有一个方法的形式,可以简略。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    new Thread(new Runnable(){
    @Override
    public void run(){
    //
    }
    }).start();
    // -- > lambda
    new Thread(() -> {
    //
    }).start();