开发Android服务

来源:互联网 发布:小米电力猫网络密码 编辑:程序博客网 时间:2024/06/07 12:29

前言

  服务是Android中的一个应用,它在后台运行,不需要与用户有任何的交互。例如,当使用一个应用的时候,你希望同时可以在后台播放音乐。这时,在后台播放音乐的代码不需要与用户交互;因此,它可以作为一个服务运行。同时,当应用不需要提供用户界面(UI)的时候,服务也是理想的选择。对于这种情况由一个很好的示例应用是持续记录设备的地理坐标。这时,可以编写一个服务在后台运行。

在服务中执行长时间运行的任务

  MainActivity

public class MainActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);    }    public void onStartService(View view) {        startService(new Intent(getApplicationContext(), MyService.class));    }    public void onStopService(View view) {        stopService(new Intent(getApplicationContext(), MyService.class));    }}

  activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical"    tools:context="link_work.myapplication.MainActivity">    <LinearLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:orientation="horizontal">        <Button            android:id="@+id/startService"            android:layout_width="0dp"            android:layout_height="wrap_content"            android:layout_weight="1"            android:onClick="onStartService"            android:text="@string/startService" />        <Button            android:id="@+id/stopService"            android:layout_width="0dp"            android:layout_height="wrap_content"            android:layout_weight="1"            android:onClick="onStopService"            android:text="@string/stopService" />    </LinearLayout></LinearLayout>

这里写图片描述

  这个实例演示了最简单的一个Service。服务本身不做任何有用的工作,当然它只是为了说明如何创建一个服务。
  首先定义一个继承于Service基类的子类。所有的服务都继承于Service类:

public class MyService extends Service {}

  在MyService类中,实现了三个方法:

public class MyService extends Service {    @Override    public IBinder onBind(Intent arg0) {        ...    }    @Override    public int onStartCommand(Intent intent, int flags, int startId) {        ...    }    @Override    public void onDestroy() {        ...    }}
方法 说明 onBind() 该方法使你能够将一个Activity绑定到一个服务上。反过来Activity也能够直接访问服务中的成员和方法。 onStartCommand() 该方法会在显式地使用startService方法启动服务时被调用。该方法表示服务启动,你可以在该方法中编写任何想要为服务执行的任务。 onDestory() 该方法会显示地使用stopService()方法停止服务时被调用。在该方法中清理服务使用的资源。

  要启动一个服务,调用startService()方法:

startService(new Intent(getApplicationContext(), MyService.class));

  要停止一个服务,调用StopService()方法:

stopService(new Intent(getApplicationContext(), MyService.class));

  先在中AndroidManifest.xml添加<service android:name=".Service.MyService" />

  MyService

public class MyService extends Service {    @Override    public IBinder onBind(Intent arg0) {        return null;    }    @Override    public int onStartCommand(Intent intent, int flags, int startId) {        // We want this service to continue running until it is explicitly        // stopped, so return sticky.        Toast.makeText(this, "Service Started", Toast.LENGTH_LONG).show();        try {            int result =                    downLoadFile(new URL("http://www.amazon.com/somefile.pdf"));            Toast.makeText(getBaseContext(), "Downloaded " + result + " bytes",                    Toast.LENGTH_LONG).show();        } catch (MalformedURLException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        return START_STICKY;    }    private int downLoadFile(URL url) {        try {            //---模拟下载---            Thread.sleep(5000);        } catch (InterruptedException e) {            e.printStackTrace();        }        //---随意返回一个任意值100---        return 100;    }    @Override    public void onDestroy() {        super.onDestroy();        Toast.makeText(this, "Service Destroyed", Toast.LENGTH_LONG).show();    }}

这里写图片描述

  注意:请注意在点击开启服务的之后,按钮会由1秒多的卡顿(按钮点击特效会有卡顿感)。


  在本示例中,服务调用downloadFile()方法模拟从给定的URL中下载一个文件。该方法返回下载的字节总数(这里硬编码为100)。为了模拟当下载文件时服务所经历的延时,这里使用Thead.Sleep()方法将服务暂停5秒钟(5000毫秒)。
  当启动服务的时候,注意Activity会有5秒钟的停顿。这是从网络下载文件所花的时间。在这段时间中,整个Activity没有任何响应,这也演示了一个非常重要的知识点:服务和Activity运行在相同的线程上。在这种情况下,因为服务停顿5秒钟,所以Activity也同样停顿5秒钟。


  也就是说,对于一个长时间运行的服务来说,必须将所有耗时代码放在一个独立的线程中,这样就不会影响调用它的应用。

在服务中创建异步执行任务

  MyService

public class MyService extends Service {    @Override    public IBinder onBind(Intent arg0) {        return null;    }    @Override    public int onStartCommand(Intent intent, int flags, int startId) {        // We want this service to continue running until it is explicitly        // stopped, so return sticky.        Toast.makeText(this, "Service Started", Toast.LENGTH_LONG).show();        try {            new DoBackgroundTask().execute(                    new URL("http://www.amazon.com/somefiles.pdf"),                    new URL("http://www.wrox.com/somefiles.pdf"),                    new URL("http://www.google.com/somefiles.pdf"),                    new URL("http://www.learn2develop.net/somefiles.pdf"));        } catch (MalformedURLException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        return START_STICKY;    }    private int downloadFile() {        try {            //---模拟下载停顿---            Thread.sleep(5000);        } catch (InterruptedException e) {            e.printStackTrace();        }        // 随意返回一个下载文件的大小。        return 100;    }    @Override    public void onDestroy() {        super.onDestroy();        Toast.makeText(this, "Service Destroyed", Toast.LENGTH_LONG).show();    }    @SuppressLint("StaticFieldLeak")    private class DoBackgroundTask extends AsyncTask<URL, Integer, Long> {        @Override        protected Long doInBackground(URL... urls) {            int count = urls.length;            long totalBytesDownloaded = 0;            for (int i = 0; i < count; i++) {                totalBytesDownloaded += downloadFile();                // 在下载中不断更新进度条                publishProgress((int) (((i + 1) / (float) count) * 100));            }            return totalBytesDownloaded;        }        @Override        protected void onProgressUpdate(Integer... progress) {            Log.d("Downloading files",                    String.valueOf(progress[0]) + "% downloaded");            Toast.makeText(getBaseContext(),                    String.valueOf(progress[0]) + "% downloaded",                    Toast.LENGTH_LONG).show();        }        @Override        protected void onPostExecute(Long result) {            Toast.makeText(getBaseContext(),                    "Downloaded " + result + " bytes",                    Toast.LENGTH_LONG).show();            // 当后台线程完成执行之后,需要手动调用stopSelf()方法停止服务。            // 该方法类似于调用stopService()方法停止服务。            stopSelf();        }    }}

这里写图片描述

  单击完开启服务按钮之后,Toast类显示信息指示下载的完成进度。你可以看到四条信息:25%、50%、75%、100%。


  本示例说明了一个在服务中执行异步任务的方法。该方法通过创建一个继承于AsyncTask类的内部类。AsyncTask方法能够在不需要手动处理线程和执行者的情况下在后台执行操作。

方法 说明 doInBackground() 这个方法接收一个数组作为参数,数组类型为早前制定的第一个类型,本示例中为URL类型。该方法可以在后台线程中执行,因此它就是防止耗时代码的地方。要报告任务的进度,可以调用publishProgress()方法,它会调用下一个方法,onProgressUpdate()。 onProgressUpdate() 该方法在UI线程中调用,当调用publishProgress()方法的时候就会调用该方法。它接受一个数组作为参数。使用这个方法可以为用户汇报后台任何的进度。 onPostExecute() 这个方法在UI线程中调用,当doInBackground()方法结束执行的时候就会调用该方法。

在服务中执行重复任务

  MyService

public class MyService extends Service {    static final int UPDATE_INTERVAL = 1000;    int counter = 0;    private Timer timer = new Timer();    @Override    public IBinder onBind(Intent arg0) {        return null;    }    @Override    public int onStartCommand(Intent intent, int flags, int startId) {        // We want this service to continue running until it is explicitly        // stopped, so return sticky.        doSomethingRepeatedly();        try {            new DoBackgroundTask().execute(                    new URL("http://www.amazon.com/somefiles.pdf"),                    new URL("http://www.wrox.com/somefiles.pdf"),                    new URL("http://www.google.com/somefiles.pdf"),                    new URL("http://www.learn2develop.net/somefiles.pdf"));        } catch (MalformedURLException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        return START_STICKY;    }    private void doSomethingRepeatedly() {        timer.scheduleAtFixedRate(new TimerTask() {            @Override            public void run() {                Log.d("MyService", String.valueOf(++counter));            }        }, 0, UPDATE_INTERVAL);    }    private int downloadFile() {        try {            //---模拟下载延时---            Thread.sleep(5000);        } catch (InterruptedException e) {            e.printStackTrace();        }        return 100;    }    @Override    public void onDestroy() {        super.onDestroy();        if (timer != null) {            timer.cancel();        }        Toast.makeText(this, "Service Destroyed", Toast.LENGTH_LONG).show();    }    @SuppressLint("StaticFieldLeak")    private class DoBackgroundTask extends AsyncTask<URL, Integer, Long> {        @Override        protected Long doInBackground(URL... urls) {            int count = urls.length;            long totalBytesDownloaded = 0;            for (int i = 0; i < count; i++) {                totalBytesDownloaded += downloadFile();                //---calculate percentage downloaded and                // report its progress---                publishProgress((int) (((i + 1) / (float) count) * 100));            }            return totalBytesDownloaded;        }        @Override        protected void onProgressUpdate(Integer... progress) {            Log.d("Downloading files",                    String.valueOf(progress[0]) + "% downloaded");            Toast.makeText(getBaseContext(),                    String.valueOf(progress[0]) + "% downloaded",                    Toast.LENGTH_LONG).show();        }        @Override        protected void onPostExecute(Long result) {            Toast.makeText(getBaseContext(),                    "Downloaded " + result + " bytes",                    Toast.LENGTH_LONG).show();            stopSelf();        }    }}

这里写图片描述

  在本例中,创建了一个Timer对象,并在自定义的doSomethingRepeatedly()方法中调用Timer对象的scheduleAtFixedRate()方法:

private void doSomethingRepeatedly() {        timer.scheduleAtFixedRate(new TimerTask() {            @Override            public void run() {                Log.d("MyService", String.valueOf(++counter));                }        }, 0, UPDATE_INTERVAL);}

  向scheduleAtFixedRate()方法中传入了一个TimerTask类的实例,从而可以在run()方法中重复执行一段代码。scheduleAtFixedRate()方法的第二个参数指定了第一次执行前的等待时间,以毫秒为单位。第三个参数指定了后续执行的时间间隔,以毫秒为单位。
  以上示例代码实际上每秒打印计数器的数值(1000毫秒)。服务会重复打印计数器的数值直到服务被终止。

@Overridepublic void onDestroy() {        super.onDestroy();        if (timer != null) {            timer.cancel();    }    Toast.makeText(this, "Service Destroyed", Toast.LENGTH_LONG).show();}

  对于scheduleAtFixedRate()方法来说,它会固定时间间隔执行任务,不管每次任务会消耗多长时间。
  同样需要注意的是,在onStartCommand()方法中直接调用doSomethingRepeatedly(),而不需要将它封装在AsyncTask类的子类中。这是因为TimerTask类中自己实现了Runnable接口,能够允许它在独立的线程上运行。

使用IntentService在独立的线程中执行异步任务

  使用Service的时候,需要时刻注意的就是,当服务结束执行一个任务以后,它应该立即停止从而可以释放宝贵的资源。这就是为什么当一个任务结束以后需要调用stopSelf()方法停止服务的原因。遗憾的是,当任务完成以后,很多开发者经常忘记终止服务。为了方便地创建一个异步运行任务的服务,并且当任务结束的时候自动终止,可以使用IntentService类。
  作为服务的基类,IntentService类根据需求处理异步请求。启动它的方法与普通服务相同;但是它会在一个工作线程中执行它的任务并且当任务完成时它会自动终止

  MyIntentService

public class MyIntentService extends IntentService {    public MyIntentService() {        // Intent Service的名称        super("MyIntentServiceName");    }    @Override    protected void onHandleIntent(Intent intent) {        try {            int result =                    downloadFile(new URL("http://www.amazon.com/somefile.pdf"));            Log.d("IntentService", "Downloaded " + result + " bytes");        } catch (MalformedURLException e) {            e.printStackTrace();        }    }    private int downloadFile(URL url) {        try {            //---模拟下载---            Thread.sleep(5000);        } catch (InterruptedException e) {            e.printStackTrace();        }        return 100;    }}

这里写图片描述

  onHandleIntent()方法就是放置需要在独立线程中执行的代码的位置,比如从服务器上下载文件。当代码结束执行后,线程会被终止而且服务也会自动停止。

在服务和Activity之间通信

  MyIntentService

public class MyIntentService extends IntentService {    public MyIntentService() {        super("MyIntentServiceName");    }    @Override    protected void onHandleIntent(Intent intent) {        try {            int result =                    downloadFile(new URL("http://www.amazon.com/somefile.pdf"));            Log.d("IntentService", "Downloaded " + result + " bytes");            //---send a broadcast to inform the activity            // that the file has been downloaded---            Intent broadcastIntent = new Intent();            broadcastIntent.setAction("FILE_DOWNLOADED_ACTION");            getBaseContext().sendBroadcast(broadcastIntent);        } catch (MalformedURLException e) {            e.printStackTrace();        }    }    private int downloadFile(URL url) {        try {            //---模拟下载---            Thread.sleep(5000);        } catch (InterruptedException e) {            e.printStackTrace();        }        return 100;    }}

  MainActivity

public class MainActivity extends AppCompatActivity {    private BroadcastReceiver intentReceiver;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        intentReceiver = new BroadcastReceiver() {            @Override            public void onReceive(Context context, Intent intent) {                Toast.makeText(getApplicationContext(), "File downloaded.",                        Toast.LENGTH_LONG).show();            }        };    }    public void onStartIntentService(View view) {        startService(new Intent(getApplicationContext(), MyIntentService.class));    }    public void onStopIntentService(View view) {        stopService(new Intent(getApplicationContext(), MyIntentService.class));    }    @Override    public void onResume() {        super.onResume();        //---intent to filter for file downloaded intent---        IntentFilter intentFilter = new IntentFilter();        intentFilter.addAction("FILE_DOWNLOADED_ACTION");        //---register the receiver---        registerReceiver(intentReceiver, intentFilter);    }    @Override    public void onPause() {        super.onPause();        //---unregister the receiver---        unregisterReceiver(intentReceiver);    }}

  单击打开Intent服务按钮,大约5秒钟后,Toast类就会弹出一条消息表示文件下载完成。

这里写图片描述

  当服务的执行任务完成之后,想要通知Activity,可以使用sendBroadcast()方法广播一个Intent对象:

@Overrideprotected void onHandleIntent(Intent intent) {        try {            int result =            downloadFile(new URL("http://www.amazon.com/somefile.pdf"));            Log.d("IntentService", "Downloaded " + result + " bytes");            //---send a broadcast to inform the activity            // that the file has been downloaded---            Intent broadcastIntent = new Intent();            broadcastIntent.setAction("FILE_DOWNLOADED_ACTION");            getBaseContext().sendBroadcast(broadcastIntent);    } catch (MalformedURLException e) {            e.printStackTrace();    }}

  被广播的Intent对象的动作被设置为FILE_DOWNLOADED_ACTION,这意味着所有监听该Intent的Activity将会被调用。也就是说,在MainActivity文件中,需要使用registerReceiver()方法监听IntentFilter类中的Intent对象。

@Overridepublic void onResume() {        super.onResume();        //---intent to filter for file downloaded intent---        IntentFilter intentFilter = new IntentFilter();        intentFilter.addAction("FILE_DOWNLOADED_ACTION");        //---register the receiver---        registerReceiver(intentReceiver, intentFilter);}

  当接收到Intent后,它会调用已经定义的BroadcastReceiver类的实例:

    private BroadcastReceiver intentReceiver;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        intentReceiver = new BroadcastReceiver() {            @Override            public void onReceive(Context context, Intent intent) {                Toast.makeText(getApplicationContext(), "File downloaded.",                        Toast.LENGTH_LONG).show();            }        };    }

将Activity与服务绑定

  MainActivity

public class MainActivity extends AppCompatActivity {    int notificationID = 1;    MyService serviceBinder;    Intent i;    private BroadcastReceiver intentReceiver;    private ServiceConnection connection = new ServiceConnection() {        @Override        public void onServiceConnected(                ComponentName className, IBinder service) {            //—-called when the connection is made—-            serviceBinder = ((MyService.MyBinder) service).getService();            try {                //---assign the URLs to the service through the                // serviceBinder object---                serviceBinder.urls = new URL[]{                        new URL("http://www.amazon.com/somefiles.pdf"),                        new URL("http://www.wrox.com/somefiles.pdf"),                        new URL("http://www.google.com/somefiles.pdf"),                        new URL("http://www.learn2develop.net/somefiles.pdf")};            } catch (MalformedURLException e) {                e.printStackTrace();            }            startService(i);        }        @Override        public void onServiceDisconnected(ComponentName className) {            //---当service为null的时候回调---            serviceBinder = null;        }    };    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        intentReceiver = new BroadcastReceiver() {            @Override            public void onReceive(Context context, Intent intent) {                Toast.makeText(getApplicationContext(), "File downloaded.",                        Toast.LENGTH_LONG).show();            }        };    }    private void displayNotification() {        Intent i = new Intent(this, NotificationView.class);        i.putExtra("notificationID", notificationID);        PendingIntent pendingIntent = PendingIntent.getActivity(this,                0, i, 0);        NotificationManager nm = (NotificationManager) getSystemService                (NOTIFICATION_SERVICE);        NotificationCompat.Builder notifBuilder = new NotificationCompat.Builder(this, this.getPackageName())                .setSmallIcon(R.mipmap.ic_launcher)                .setContentTitle("会议提醒")                .setContentText("内容: 敌军将在五秒钟后到达!")                .addAction(R.mipmap.ic_launcher, "Notzuonotdied",                        pendingIntent);        assert nm != null;        nm.notify(notificationID, notifBuilder.build());    }    public void onClick(View view) {        displayNotification();    }    public void onContentProvider(View view) {        startActivity(new Intent(this, Main2Activity.class));    }    public void onMyCP(View view) {        startActivity(new Intent(this, Main3Activity.class));    }    public void onStartService(View view) {//        startService(new Intent(getApplicationContext(), MyService.class));        i = new Intent(MainActivity.this, MyService.class);        bindService(i, connection, Context.BIND_AUTO_CREATE);    }    public void onStopService(View view) {        stopService(new Intent(getApplicationContext(), MyService.class));    }    public void onStartIntentService(View view) {        startService(new Intent(getApplicationContext(), MyIntentService.class));    }    public void onStopIntentService(View view) {        stopService(new Intent(getApplicationContext(), MyIntentService.class));    }    @Override    public void onResume() {        super.onResume();        //---intent to filter for file downloaded intent---        IntentFilter intentFilter = new IntentFilter();        intentFilter.addAction("FILE_DOWNLOADED_ACTION");        //---register the receiver---        registerReceiver(intentReceiver, intentFilter);    }    @Override    public void onPause() {        super.onPause();        //---unregister the receiver---        unregisterReceiver(intentReceiver);    }}

  MyService

public class MyService extends Service {    public URL[] urls;    private final IBinder binder = new MyBinder();    public class MyBinder extends Binder {        public MyService getService() {            return MyService.this;        }    }    @Override    public IBinder onBind(Intent arg0) {        return binder;    }    @Override    public int onStartCommand(Intent intent, int flags, int startId) {        // We want this service to continue running until it is explicitly        // stopped, so return sticky.        Toast.makeText(this, "Service Started", Toast.LENGTH_LONG).show();        new DoBackgroundTask().execute(urls);        return START_STICKY;    }    private int downloadFile() {        try {            //---模拟下载延时---            Thread.sleep(5000);        } catch (InterruptedException e) {            e.printStackTrace();        }        return 100;    }    @SuppressLint("StaticFieldLeak")    private class DoBackgroundTask extends AsyncTask<URL, Integer, Long> {        @Override        protected Long doInBackground(URL... urls) {            int count = urls.length;            long totalBytesDownloaded = 0;            for (int i = 0; i < count; i++) {                totalBytesDownloaded += downloadFile();                //---calculate percentage downloaded and                // report its progress---                publishProgress((int) (((i + 1) / (float) count) * 100));            }            return totalBytesDownloaded;        }        @Override        protected void onProgressUpdate(Integer... progress) {            Log.d("Downloading files",                    String.valueOf(progress[0]) + "% downloaded");            Toast.makeText(getBaseContext(),                    String.valueOf(progress[0]) + "% downloaded",                    Toast.LENGTH_LONG).show();        }        @Override        protected void onPostExecute(Long result) {            Toast.makeText(getBaseContext(),                    "Downloaded " + result + " bytes",                    Toast.LENGTH_LONG).show();            stopSelf();        }    }}

这里写图片描述

  要将Activity与服务绑定,首先必须在服务中创建一个继承于Binder类的内部类:

public class MyBinder extends Binder {        public MyService getService() {            return MyService.this;    }}

  在这个内部类中实现getService()方法,该方法返回一个服务的实例:

private final IBinder binder = new MyBinder();

  同时修改onBind()方法返回MyBind实例:

@Overridepublic IBinder onBind(Intent arg0) {        return binder;}

  在onStartCommand()方法中,使用urls数组调用execute()方法,urls数组在服务中被声明为一个公共成员:

public class MyService extends Service {    public URL[] urls;    ......    @Override    public int onStartCommand(Intent intent, int flags, int startId) {        // We want this service to continue running until it is explicitly        // stopped, so return sticky.        Toast.makeText(this, "Service Started", Toast.LENGTH_LONG).show();        new DoBackgroundTask().execute(urls);        return START_STICKY;    }    ......}

  接着,这个URL数组必须从Activity中直接赋值。
  在MainActivity文件中,首先声明一个服务的实例和一个Intent对象:

MyService serviceBinder;Intent i;

  serviceBinder对象将被用来作为服务的引用,并在Activity中可以直接访问。
  然后创建一个ServiceConnection类的实例,从而可以监控服务的状态:

private ServiceConnection connection = new ServiceConnection() {        @Override        public void onServiceConnected(                ComponentName className, IBinder service) {            //—-当连接成功的时候回调—-            serviceBinder = ((MyService.MyBinder) service).getService();            try {                //---初始化URLs数组,并通过Intent传递给Service---                serviceBinder.urls = new URL[]{                        new URL("http://www.amazon.com/somefiles.pdf"),                        new URL("http://www.wrox.com/somefiles.pdf"),                        new URL("http://www.google.com/somefiles.pdf"),                        new URL("http://www.learn2develop.net/somefiles.pdf")};                } catch (MalformedURLException e) {                        e.printStackTrace();                }                startService(i);        }        @Override        public void onServiceDisconnected(ComponentName className) {            //---当service为null的时候回调---            serviceBinder = null;    }};

  需要实现两个方法:onServiceConnectedonServiceDisconnected

方法 说明 onServiceConnected() 当Activity与服务连接的时候调用。 onServiceDisconnected() 当Activity与服务断开的时候调用。

  随后使用startService(i);启动服务。
  在启动服务之前,必须将Activity与该服务绑定。该内容在启动服务按钮的onClick事件函数onStartService中:

public void onStartService(View view) {        i = new Intent(MainActivity.this, MyService.class);        bindService(i, connection, Context.BIND_AUTO_CREATE);}

  bindService()方法使Activity连接到服务。

参数 说明 Intent service 一个Intent对象。 ServiceConnection conn 一个ServiceConnection对象。 int flags 一个标识如何绑定服务的标志。

附录

  • 《Beginning Android Programming with Android Studio, 4th Edition》

阅读全文
'); })();
0 0
原创粉丝点击
热门IT博客
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 头皮屑多是什么原因引起的 头发头皮屑多怎么办 头皮屑很多怎么办 头皮多是什么原因 起头皮屑怎么办 有头皮怎么办 春季头皮屑多 头发起头皮屑怎么办 头皮屑多是什么病 头皮屑特别多怎么办 头皮削多是什么原因 头皮屑特别多 头皮屑怎么办 头上头皮屑多怎么办 头皮屑多的原因 头皮削 头皮太多怎么办 为什么会有头皮屑 头皮多怎么办 为什么头皮屑多 头皮屑多 头皮屑多又痒是什么原因 头上有头皮屑怎么办 什么是头皮屑 头皮屑好多 头皮屑是什么 头皮屑怎么去掉 为什么有头皮屑 超大头皮屑图片 头皮屑太多怎么办 抓头皮屑的危害 头皮屑多了怎么办 大块头皮屑看得到毛孔 有头皮屑是什么原因 大块头皮屑图片 孕妇头皮屑多怎么办 头皮屑的危害 头皮屑的图片 头皮屑图 头皮屑怎么去除 头上大块头皮屑图片