月度归档: 2021 年 5 月

保持Service不被Kill掉的方法–双Service守护 & Android实现双进程守护

本文分为两个部分,*部分为双Service守护,第二部分为双进程守护

*部分:

一、Service简介:
java.lang.Object

↳android.content.Context

↳android.content.ContextWrapper

↳android.app.Service

Service是应用程序Application的一个组件(component)。
它的作用有两点:1.用来提供一个长期在后台运行并且不与用户交互的操作,2.也可以为其他应用程序提供服务。
Service必须和其他四大组件一样,使用<service>标签在AndroidManifest.xml中进行声明。
启动service有两种方式Context.startService() 和 Context.bindService()。

注意,除了特别指定外,service并不是单独的进程,一般service在其宿主进程的主线程(UI Thread)中运行【当然也可以在新的线程中startService,这样Service就不是在MainThread了】。这意味着,如果您的服务要做任何 耗时(如 MP3 播放) 或阻塞 (比如网络) 操作,它应该产生它自己的线程,用来做那项工作。(service不是单独的进程也不是单独的线程)

Service提供了两大功能:
Context.startService()用来在后台启动一个服务;
Context.bindService()用来绑定其他服务,以此来获取其他service提供的服务;

 

本地服务 Local Service 用于应用程序内部

它可以启动并运行,直至有人停止了它或它自己停止。在这种方式下,它以调用Context.startService()启动,而以调用Context.stopService()结束。它可以调用Service.stopSelf() 或 Service.stopSelfResult()来自己停止。不论调用了多少次startService()方法,你只需要调用一次stopService()来停止服务。

【用于实现应用程序自己的一些耗时任务,比如查询升级信息,并不占用应用程序比如Activity所属线程,而是单开线程后台执行,这样用户体验比较好】

 

远程服务 Remote Service 用于android系统内部的应用程序之间

它可以通过自己定义并暴露出来的接口进行程序操作。客户端建立一个到服务对象的连接,并通过那个连接来调用服务。连接以调用Context.bindService()方法建立,以调用 Context.unbindService()关闭。多个客户端可以绑定至同一个服务。如果服务此时还没有加载,bindService()会先加载它。

【可被其他应用程序复用,比如天气预报服务,其他应用程序不需要再写这样的服务,调用已有的即可】

 

 

二、Service运行方式和生命周期图:

以startService()启动服务,系统将通过传入的Intent在底层搜索相关符合Intent里面信息的service。如果服务没有启动则先运行onCreate,然后运行onStartCommand (可在里面处理启动时传过来的Intent和其他参数),直到明显调用stopService或者stopSelf才将停止Service。无论运行startService多少次,只要调用一次stopService或者stopSelf,Service都会停止。使用stopSelf(int)方法可以保证在处理好intent后再停止。onStartCommand ,在2.0后被引入用于service的启动函数,2.0之前为public void onStart(Intent intent, int startId) 。

以bindService()方法启用服务,调用者与服务绑定在了一起,调用者一旦退出,服务也就终止。onBind()只有采用Context.bindService()方法启动服务时才会回调该方法。该方法在调用者与服务绑定时被调用,当调用者与服务已经绑定,多次调用Context.bindService()方法并不会导致该方法被多次调用。采用Context.bindService()方法启动服务时只能调用onUnbind()方法解除调用者与服务解除,服务结束时会调用onDestroy()方法。

 

(注意这个新老API的改变)

void onStart(Intent intent, int startId)
This method was deprecated      in API level 5.    Implement onStartCommand(Intent, int, int) instead.

 

int onStartCommand(Intent intent, int flags, int startId)
Called by the system every time a client explicitly starts the service by calling  startService(Intent), providing the arguments it supplied and a  unique integer token representing the start request.

 

三、Service的优先级

官方文档告诉我们,Android系统会尽量保持拥有service的进程运行,只要在该service已经被启动(start)或者客户端连接(bindService)到它。当内存不足时,需要保持,拥有service的进程具有较高的优先级。

1. 如果service正在调用onCreate,onStartCommand或者onDestory方法,那么用于当前service的进程则变为前台进程以避免被killed。
2. 如果当前service已经被启动(start),拥有它的进程则比那些用户可见的进程优先级低一些,但是比那些不可见的进程更重要,这就意味着service一般不会被killed.
3. 如果客户端已经连接到service (bindService),那么拥有Service的进程则拥有*高的优先级,可以认为service是可见的。
4. 如果service可以使用startForeground(int, Notification)方法来将service设置为前台状态,那么系统就认为是对用户可见的,并不会在内存不足时killed。
5. 如果有其他的应用组件作为Service,Activity等运行在相同的进程中,那么将会增加该进程的重要性。

 

四、保持service不被kill掉

方法一:

START_STICKY is used for services that are explicitly started and stopped as needed, while START_NOT_STICKY or START_REDELIVER_INTENT are used for services that should only remain running while processing any commands sent to them

onStartCommand方法几个返回值简介:

1、START_STICKY

在运行onStartCommand后service进程被kill后,那将保留在开始状态,但是不保留那些传入的intent。不久后service就会再次尝试重新创建,因为保留在开始状态,在创建     service后将保证调用onstartCommand。如果没有传递任何开始命令给service,那将获取到null的intent。

2、START_NOT_STICKY

在运行onStartCommand后service进程被kill后,并且没有新的intent传递给它。Service将移出开始状态,并且直到新的明显的方法(startService)调用才重新创建。因为如果没有传递任何未决定的intent那么service是不会启动,也就是期间onstartCommand不会接收到任何null的intent。

3、START_REDELIVER_INTENT

在运行onStartCommand后service进程被kill后,系统将会再次启动service,并传入*后一个intent给onstartCommand。直到调用stopSelf(int)才停止传递intent。如果在被kill后还有未处理好的intent,那被kill后服务还是会自动启动。因此onstartCommand不会接收到任何null的intent。

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
flags = START_STICKY;
return super.onStartCommand(intent, flags, startId);
}
【结论】 手动返回START_STICKY,亲测当service因内存不足被kill,当内存又有的时候,service又被重新创建,比较不错,但是不能保证任何情况下都被重建,比如进程被干掉了….

方法二:

提升service优先级

在AndroidManifest.xml文件中对于intent-filter可以通过android:priority = “1000”这个属性设置*高优先级,1000是*高值,如果数字越小则优先级越低,同时适用于广播。

<service
android:name=”com.dbjtech.acbxt.waiqin.UploadService”
android:enabled=”true” >
<intent-filter android:priority=”1000″ >
<action android:name=”com.dbjtech.myservice” />
</intent-filter>
</service>
【结论】目前看来,priority这个属性貌似只适用于broadcast,对于Service来说可能无效

 

方法三:

提升service进程优先级

Android中的进程是托管的,当系统进程空间紧张的时候,会依照优先级自动进行进程的回收。Android将进程分为6个等级,它们按优先级顺序由高到低依次是:

1.前台进程( FOREGROUND_APP)
2.可视进程(VISIBLE_APP )
3. 次要服务进程(SECONDARY_SERVER )
4.后台进程 (HIDDEN_APP)
5.内容供应节点(CONTENT_PROVIDER)
6.空进程(EMPTY_APP)

当service运行在低内存的环境时,将会kill掉一些存在的进程。因此进程的优先级将会很重要,可以使用startForeground将service放到前台状态。这样在低内存时被kill的几率会低一些。

在onStartCommand方法内添加如下代码:

Notification notification = new Notification(R.drawable.ic_launcher,getString(R.string.app_name), System.currentTimeMillis());

PendingIntent pendingintent = PendingIntent.getActivity(this, 0,new Intent(this, AppMain.class), 0);
notification.setLatestEventInfo(this, “uploadservice”, “请保持程序在后台运行”, pendingintent);
startForeground(0x111, notification);

注意在onDestroy里还需要stopForeground(true),运行时在下拉列表会看到自己的APP在:

 

【结论】如果在*度*度低内存的压力下,该service还是会被kill掉,并且不一定会restart

保持Service不被Kill掉的方法–双Service守护,代码如下:

 

AndroidManifest.xml:
<activity
android:name=”.MainActivity”
android:label=”@string/app_name” >
<intent-filter>
<action android:name=”android.intent.action.MAIN” />
<category android:name=”android.intent.category.LAUNCHER” />
</intent-filter>
</activity>

<service
android:name=”ServiceOne”
android:process=”:remote” >
<intent-filter>
<action android:name=”com.example.servicedemo.ServiceOne” />
</intent-filter>
</service>

<service
android:name=”ServiceTwo”
android:process=”:remote” >
<intent-filter>
<action android:name=”com.example.servicedemo.ServiceTwo” />
</intent-filter>
</service>
MainActivity.java:
package com.example.servicedemo;

import java.util.ArrayList;

import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningServiceInfo;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;

public class MainActivity extends Activity {

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

Intent serviceOne = new Intent();
serviceOne.setClass(MainActivity.this, ServiceOne.class);
startService(serviceOne);

Intent serviceTwo = new Intent();
serviceTwo.setClass(MainActivity.this, ServiceTwo.class);
startService(serviceTwo);
}

public static boolean isServiceWorked(Context context, String serviceName) {
ActivityManager myManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
ArrayList<RunningServiceInfo> runningService = (ArrayList<RunningServiceInfo>) myManager.getRunningServices(Integer.MAX_VALUE);
for (int i = 0; i < runningService.size(); i++) {
if (runningService.get(i).service.getClassName().toString().equals(serviceName)) {
return true;
}
}
return false;
}
}

ServiceOne.java:
package com.example.servicedemo;

import java.util.Timer;
import java.util.TimerTask;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

public class ServiceOne extends Service {

public final static String TAG = “com.example.servicedemo.ServiceOne”;

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG, “onStartCommand”);

thread.start();
return START_STICKY;
}

Thread thread = new Thread(new Runnable() {

@Override
public void run() {
Timer timer = new Timer();
TimerTask task = new TimerTask() {

@Override
public void run() {
Log.e(TAG, “ServiceOne Run: “+System.currentTimeMillis());
boolean b = MainActivity.isServiceWorked(ServiceOne.this, “com.example.servicedemo.ServiceTwo”);
if(!b) {
Intent service = new Intent(ServiceOne.this, ServiceTwo.class);
startService(service);
Log.e(TAG, “Start ServiceTwo”);
}
}
};
timer.schedule(task, 0, 1000);
}
});

@Override
public IBinder onBind(Intent arg0) {
return null;
}

}

ServiceTwo.java:
package com.example.servicedemo;

import java.util.Timer;
import java.util.TimerTask;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

public class ServiceTwo extends Service {

public final static String TAG = “com.example.servicedemo.ServiceTwo”;

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG, “onStartCommand”);

thread.start();
return START_REDELIVER_INTENT;
}

Thread thread = new Thread(new Runnable() {

@Override
public void run() {
Timer timer = new Timer();
TimerTask task = new TimerTask() {

@Override
public void run() {
Log.e(TAG, “ServiceTwo Run: ” + System.currentTimeMillis());
boolean b = MainActivity.isServiceWorked(ServiceTwo.this, “com.example.servicedemo.ServiceOne”);
if(!b) {
Intent service = new Intent(ServiceTwo.this, ServiceOne.class);
startService(service);
}
}
};
timer.schedule(task, 0, 1000);
}
});

@Override
public IBinder onBind(Intent arg0) {
return null;
}

}
第二部分:

做过android开发的人应该都知道应用会在系统资源匮乏的情况下被系统杀死!当后台的应用被系统回收之后,如何重新恢复它呢?网上对此问题有很多的讨论。这里先总结一下网上流传的各种解决方案,看看这些办法是不是真的可行。
1.提高优先级
这个办法对普通应用而言,应该只是降低了应用被杀死的概率,但是如果真的被系统回收了,还是无法让应用自动重新启动!

2.让service.onStartCommand返回START_STICKY
通过实验发现,如果在adb shell当中kill掉进程模拟应用被意外杀死的情况(或者用360手机卫士进行清理操作),如果服务的onStartCommand返回START_STICKY,在eclipse的进程管理器中会发现过一小会后被杀死的进程的确又会出现在任务管理器中,貌似这是一个可行的办法。但是如果在系统设置的App管理中选择强行关闭应用,这时候会发现即使onStartCommand返回了START_STICKY,应用还是没能重新启动起来!

3.android:persistent=”true”
网上还提出了设置这个属性的办法,通过实验发现即使设置了这个属性,应用程序被kill之后还是不能重新启动起来的!

4.让应用成为系统应用
实验发现即使成为系统应用,被杀死之后也不能自动重新启动。但是如果对一个系统应用设置了persistent=”true”,情况就不一样了。实验表明对一个设置了persistent属性的系统应用,即使kill掉会立刻重启。一个设置了persistent=”true”的系统应用,在android中具有core service优先级,这种优先级的应用对系统的low memory killer是免疫的!

OK,说了半天,只有core service优先级的应用才能保证在被意外杀死之后做到立刻满血复活。而普通应用要想成为系统应用就必须要用目标机器的签名文件进行签名,但这样又造成了应用无法保证兼容所有不同厂商的产品。那么该怎么办呢?这里就来说一说双进程守护。网上也有人提到过双进程守护的办法,但是很少能搜索到类似的源码!如果从进程管理器重观察会发现新浪微博或者360卫视都有两个相关的进程,其中一个就是守护进程,由此可以猜到这些商业级的软件也采用了双进程守护的办法。

什么是双进程守护呢?顾名思义就是两个进程互相监视对方,发现对方挂掉就立刻重启!不知道应该把这样的一对进程是叫做相依为命呢还是难兄难弟好呢,但总之,双进程守护的确是一个解决问题的办法!相信说到这里,很多人已经迫切的想知道如何实现双进程守护了。这篇文章就介绍一个用NDK来实现双进程保护的办法,不过首先说明一点,下面要介绍的方法中,会损失不少的效率,反应到现实中就是会使手机的耗电量变大!但是这篇文章仅仅是抛砖引玉,相信看完之后会有更多高人指点出更妙的实现办法。

需要了解些什么?
这篇文章中实现双进程保护的方法基本上是纯的NDK开发,或者说全部是用C++来实现的,需要双进程保护的程序,只需要在程序的任何地方调用一下JAVA接口即可。下面几个知识点是需要了解的:
1.linux中多进程;
2.unix domain套接字实现跨进程通信;
3.linux的信号处理;
4.exec函数族的用法;

其实这些东西本身并不是多复杂的技术,只是我们把他们组合起来实现了一个双进程守护而已,没有想象中那么神秘!在正式贴出代码之前,先来说说几个实现双进程守护时的关键点:
1.父进程如何监视到子进程(监视进程)的死亡?
很简单,在linux中,子进程被终止时,会向父进程发送SIG_CHLD信号,于是我们可以安装信号处理函数,并在此信号处理函数中重新启动创建监视进程;
2.子进程(监视进程)如何监视到父进程死亡?
当父进程死亡以后,子进程就成为了孤儿进程由Init进程领养,于是我们可以在一个循环中读取子进程的父进程PID,当变为1就说明其父进程已经死亡,于是可以重启父进程。这里因为采用了循环,所以就引出了之前提到的耗电量的问题。
3.父子进程间的通信
有一种办法是父子进程间建立通信通道,然后通过监视此通道来感知对方的存在,这样不会存在之前提到的耗电量的问题,在本文的实现中,为了简单,还是采用了轮询父进程PID的办法,但是还是留出了父子进程的通信通道,虽然暂时没有用到,但可备不时之需!

 

腾讯的面试官问我:应用程序死了如何恢复?确实,双进程守护只能做到进程被杀死后重新启动,但是重启后如何恢复到之前的状态这是一个问题。因为进程被意外杀死的情况,onSaveInstance是来不及执行的,所以程序的状态没法保存!对于双进程守护来说,不知道是不是可以再父进程进入后台以后(onStop),把数据收集起来保存到子进程中,然后父进程重启以后从子进程中取出这些信息呢?这是一个办法,但是上面说明的双进程守护程序的实现中还做不到,因为父进程重启以后,子进程也挂掉重新建立了,要想实现优雅的恢复,还得在做出点改进才是!只能实时保存数据到数据库等。

bindService获取代理是同步还是异步

Android中bindService是一个异步的过程,什么意思呢?使用bindService无非是想获得一个Binder服务的Proxy,但这个代理获取到的时机并非由bindService发起端控制,而是由Service端来控制,也就是说bindService之后,APP端并不会立刻获得Proxy,而是要等待Service通知APP端,具体流程可简化如下:

  • APP端先通过bindService去AMS登记,说明自己需要绑定这样一个服务,并留下派送地址
  • APP回来,继续做其他事情,可以看做是非阻塞的
  • AMS通知Service端启动这个服务
  • Service启动,并通知AMS启动完毕
  • AMS跟住之前APP端留下的地址通知APP端,并将Proxy代理传递给APP端

通过代码来看更直接

  1.     void test(){
  2.         bindService(intent, new ServiceConnection() {
  3.             @Override
  4.             public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
  5.                iMyAidlInterface = IMyAidlInterface.Stub.asInterface(iBinder);
  6.                Log.v(TAG, “onServiceConnected…” );
  7.             }
  8.            @Override
  9.             public void onServiceDisconnected(ComponentName componentName) {
  10.            }
  11.         }, Context.BIND_AUTO_CREATE);
  12.         Log.v(TAG, “end…” );
  13.     }

bindService的过程中,上面代码的Log应该是怎么样的呢?如果bindService是一个同步过程,那么Log应该如下:

  1. TAG  onServiceConnected …
  2. TAG  end …

但是由于是个异步过程,真实的Log如下

  1. TAG  end …
  2. TAG  onServiceConnected …

也就是说bindService不会阻塞等待APP端获取Proxy,而是直接返回,这些都可以从源码获得支持,略过,直接去ActivityManagerNative去看

  1. public int bindService(IApplicationThread caller, IBinder token,
  2.         Intent service, String resolvedType, IServiceConnection connection,
  3.         int flags, int userId) throws RemoteException {
  4.     Parcel data = Parcel.obtain();
  5.     Parcel reply = Parcel.obtain();
  6.     data.writeInterfaceToken(IActivityManager.descriptor);
  7.     data.writeStrongBinder(caller != null ? caller.asBinder() : null);
  8.     data.writeStrongBinder(token);
  9.     service.writeToParcel(data, 0);
  10.     data.writeString(resolvedType);
  11.     data.writeStrongBinder(connection.asBinder());
  12.     data.writeInt(flags);
  13.     data.writeInt(userId);
  14.     <!–阻塞等待–>
  15.     mRemote.transact(BIND_SERVICE_TRANSACTION, data, reply, 0);
  16.     reply.readException();
  17.     int res = reply.readInt();
  18.     data.recycle();
  19.     reply.recycle();
  20.     return res;
  21. }

mRemote.transact(BIND_SERVICE_TRANSACTION, data, reply, 0)确实会让APP端调用线程阻塞,等待AMS执行BIND_SERVICE_TRANSACTION请求,不过AMS在执行这个请求的时候并非是唤醒Service才返回,它返回的时机更早,接着看ActivityManagerService,

  1. public int bindService(IApplicationThread caller, IBinder token,
  2.         Intent service, String resolvedType,
  3.         IServiceConnection connection, int flags, int userId) {
  4.     …
  5.     synchronized(this) {
  6.         return mServices.bindServiceLocked(caller, token, service, resolvedType,
  7.                 connection, flags, userId);
  8.     }
  9. }

ActivityManagerService直接调用ActiveServices的函数bindServiceLocked,请求绑定Service,到这里APP端线程依旧阻塞,等待AMS端返回,假定Service所处的进程已经启动但是Service没有启动,这时ActiveServices会进一步调用bindServiceLocked->realStartServiceLocked来启动Service,有趣的就在这里:

  1.  private final void realStartServiceLocked(ServiceRecord r,
  2.             ProcessRecord app) throws RemoteException {
  3.         …
  4.         <!–请求Service端启动Service–>
  5.             app.thread.scheduleCreateService(r, r.serviceInfo,
  6.                     mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo));
  7.         …
  8.         <!–请求绑定Service–>
  9.         requestServiceBindingsLocked(r);

app.thread.scheduleCreateService也是一个Binder通信过程,他其实是AMS异步请求ActivityThread中的ApplicationThread服务,系统服务请求客户端的本地服务一般都是异步的:

  1. // 插入消息,等待主线程执行
  2. public final void scheduleCreateService(IBinder token,
  3.         ServiceInfo info, CompatibilityInfo compatInfo) {
  4.     CreateServiceData s = new CreateServiceData();
  5.     s.token = token;
  6.     s.info = info;
  7.     s.compatInfo = compatInfo;
  8.     <!–向Loop的MessagerQueue插入一条消息就返回–>
  9.     queueOrSendMessage(H.CREATE_SERVICE, s);
  10. }

不过,这个请求直接向Service端的ActivityThread线程中直接插入一个消息就返回了,而并未等到该请求执行,因为AMS使用的非常频繁,不可能老等待客户端完成一些任务,所以AMS端向客户端发送完命令就直接返回,这个时候其实Service还没有被创建,也就是这个请求只是完成了一半,onServiceConnected也并不会执行,onServiceConnected什么时候执行呢?app.thread.scheduleCreateService向APP端插入*条消息,是用来创建Service的, requestServiceBindingsLocked其实就是第二条消息,用来处理绑定的

  1.  private final boolean requestServiceBindingLocked(ServiceRecord r,
  2.             IntentBindRecord i, boolean rebind) {
  3.                 …
  4.            <!– 第二个消息,请求处理绑定–>
  5.             r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind);

第二条消息是处理一些绑定需求,Android的Hanlder消息处理机制保证了第二条消息一定是在*条消息之后执行,

  1.  public final void scheduleBindService(IBinder token, Intent intent,
  2.         boolean rebind) {
  3.     BindServiceData s = new BindServiceData();
  4.     s.token = token;
  5.     s.intent = intent;
  6.     s.rebind = rebind;
  7.     queueOrSendMessage(H.BIND_SERVICE, s);
  8. }

以上两条消息插入后,AMS端被唤醒,进而重新唤醒之前阻塞的bindService端,而这个时候,Service并不一定被创建,所以说这是个未知的异步过程,Service端处理*条消息的时会创建Service,

  1.  private void handleCreateService(CreateServiceData data) {
  2.     …
  3.     LoadedApk packageInfo = getPackageInfoNoCheck(
  4.             data.info.applicationInfo, data.compatInfo);
  5.     Service service = null;
  6.     try {
  7.         java.lang.ClassLoader cl = packageInfo.getClassLoader();
  8.         service = (Service) cl.loadClass(data.info.name).newInstance();
  9.    …

执行第二条消息的时候, 会向AMS请求publishService,其实就是告诉AMS,服务启动完毕,可以向之前请求APP端派发代理了。

  1.  private void handleBindService(BindServiceData data) {
  2.     Service s = mServices.get(data.token);
  3.     if (s != null) {
  4.        try {
  5.         data.intent.setExtrasClassLoader(s.getClassLoader());
  6.         try {
  7.             if (!data.rebind) {
  8.                 IBinder binder = s.onBind(data.intent);
  9.                 ActivityManagerNative.getDefault().publishService(
  10.                         data.token, data.intent, binder);
  11.             …

AMS端收到publishService消息之后,才会向APP端发送通知,进而通过Binder回调APP端onServiceConnected函数,同时传递Proxy Binder服务代理

  1. void publishServiceLocked(ServiceRecord r, Intent intent, IBinder service) {
  2.     …
  3.      try {
  4.     <!–通过binder 回到APP端的onServiceConnected–>
  5.         c.conn.connected(r.name, service);
  6.     } catch (Exception e) {}

到这里,onServiceConnected才会被回调,不过,至于Service端那两条消息什么时候执行,谁也不能保证,也许因为特殊原因,那两条消息永远不被执行,那onServiceConnected也就不会被回调,但是这不会影响AMS与APP端处理其他问题,因为这些消息是否被执行已经不能阻塞他们两个了,简单流程如下:

640?wx_fmt=pngbindService的异步流程

*后,其实startService也是异步。

Android中BindService方式使用的理解

*近学习了一下Android里面的Service的应用,在BindService部分小卡了一下,主要是开始没有彻底理解为什么要这么实现。

BindService和Started Service都是Service,有什么地方不一样呢:1. Started Service中使用StartService()方法来进行方法的调用,调用者和服务之间没有联系,即使调用者退出了,服务依然在进行【onCreate()- >onStartCommand()->startService()->onDestroy()】,注意其中没有onStart(),主要是被onStartCommand()方法给取代了,onStart方法不推荐使用了。2. BindService中使用bindService()方法来绑定服务,调用者和绑定者绑在一起,调用者一旦退出服务也就终止了【onCreate()->onBind()->onUnbind()->onDestroy()】。调用者Activity:

 

  1. package com.zys.service;
  2. import com.zys.service.BindService.MyBinder;
  3. import android.R.bool;
  4. import android.app.Activity;
  5. import android.content.ComponentName;
  6. import android.content.Context;
  7. import android.content.Intent;
  8. import android.content.ServiceConnection;
  9. import android.os.Bundle;
  10. import android.os.IBinder;
  11. import android.view.View;
  12. import android.view.View.OnClickListener;
  13. import android.widget.Button;
  14. public class MainActivity extends Activity {
  15. private Button startBtn;
  16. private Button stopBtn;
  17. private boolean flag;
  18. /** Called when the activity is first created. */
  19. @Override
  20. public void onCreate(Bundle savedInstanceState) {
  21. super.onCreate(savedInstanceState);
  22. setContentView(R.layout.main);
  23. flag = false;
  24. //设置
  25. startBtn = (Button)this.findViewById(R.id.startBtn);
  26. stopBtn = (Button)this.findViewById(R.id.stopBtn);
  27. startBtn.setOnClickListener(listener);
  28. stopBtn.setOnClickListener(listener);
  29. }
  30. private OnClickListener listener = new OnClickListener() {
  1. @Override
  2. public void onClick(View v) {
  3. // TODO Auto-generated method stub
  4. switch (v.getId()) {
  5. case R.id.startBtn:
  6. bindService();
  7. break;
  8. case R.id.stopBtn:
  9. unBind();
  10. break;
  11. default:
  12. break;
  13. }
  14. }
  15. };
  16. private void bindService(){
  17. Intent intent = new Intent(MainActivity.this,BindService.class);
  18. bindService(intent, conn, Context.BIND_AUTO_CREATE);
  19. }
  20. private void unBind(){
  21. if(flag == true){
  22. unbindService(conn);
  23. flag = false;
  24. }
  25. }
  26. private ServiceConnection conn = new ServiceConnection() {
  27. @Override
  28. public void onServiceDisconnected(ComponentName name) {
  29. // TODO Auto-generated method stub
  30. }
  31. @Override
  32. public void onServiceConnected(ComponentName name, IBinder service) {
  33. // TODO Auto-generated method stub
  34. MyBinder binder = (MyBinder)service;
  35. BindService bindService = binder.getService();
  36. bindService.MyMethod();
  37. flag = true;
  38. }
  39. };
  40. }

服务BindService

  1. package com.zys.service;
  2. import java.io.FileDescriptor;
  3. import android.app.Service;
  4. import android.content.Intent;
  5. import android.os.Binder;
  6. import android.os.IBinder;
  7. import android.os.IInterface;
  8. import android.os.Parcel;
  9. import android.os.RemoteException;
  10. import android.util.Log;
  11. public class BindService extends Service {
  12. private static final String TAG = “BindService”;
  13. public void MyMethod(){
  14. Log.i(TAG, “BindService–>MyMethod()”);
  15. }
  16. @Override
  17. public IBinder onBind(Intent intent) {
  18. // TODO Auto-generated method stub
  19. return myBinder;
  20. }
  21. public class MyBinder extends Binder{
  22. public BindService getService(){
  23. return BindService.this;
  24. }
  25. }
  26. private MyBinder myBinder = new MyBinder();
  27. }

 

由于Android 中的Service使用了onBind 的方法去绑定服务,返回一个Ibinder对象进行操作,而我们要获取具体的Service方法的内容的时候,我们需要Ibinder对象返回具体的Service对象才能操作,所以说具体的Service对象必须首先实现Binder对象,这个样子的话我们才能利用bindService的方法对Service进行绑定,获取Binder对象之后获取具体的Service对象,然后才获取Service中的方法等等。所以我们需要注意的是bindService的方式去绑定服务获取的必定是实现了Binder的对象,所以这是我们必须使用Binder的方式去获取Service的方式而不是直接使用Service的类,这个是Android内部实现所约束的。

方法过程如下:

Intent intent = new Intent(MainActivity.this,BindService.class)->新建了BindService对象->新建了MyBinder对象

->bindService(intent, conn, Context.BIND_AUTO_CREATE);->onBind()函数  —–传递MyBinder对象——->onServiceConnected()

–> 通过传递的Binder对象获取刚刚和Binder对象对应的BindService 对象  –>调用Service中定义的方法。

这个其中必须通过Binder对象,因为是通过Binder对象来传递的,通过Binder对象获取Service对象,然后获取所需的服务,所以Service必须实现Binder,以便传递和使用。

 

Android Studio 找不到R文件解决方法汇总

一、新建的Activity中R文件找不到,其他文件中的R文件可以正常使用。
解决方法:在该Activity中引入R包即可:import com.example.zcj.password.R;

在子目录下新建Activity文件都会出现这个问题,可以通过设置自动导入包:File-Settings-Editor-General-Auto Import,将Optimize imports on the fly和Add unambiguous imports on the fly勾选即可。

二、新导入的项目找不到R文件(所有文件都找不到R文件),
解决方法:Android Studio-Build-Clean Project-Rebuild Project;  Rebuild之后,AS中会显示错误信息,根据提示修改即可。

三、error提示为“Error:Execution failed for task ‘:app:compileDebugAidl’.> aidl is missing” 。
解决方法:http://blog.csdn.net/hao2244/article/details/46663885

四、Gradle版本问题

解决方法:修改工程中的 build.gradle版本,classpath ‘com.android.tools.build:gradle:3.1.0’,然后修改gradle-wrapper.properties 文件中的内容distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip,修改成相应的版本,然后重新同步一下,rebuild一下即可。

在Android Studio中找不到AppCompatActivity解决

在创建新的.java文件时,要导入父类中的 AppCompatActivity,报错,无法找到这个父类。

解决方案:

 

1.先找到“project structure”,然后app–Dependencies,点击右上角的“+”,出现如下图所示

2.点击“Library dependency”

 

3.再搜索框中,  添加上com.android.support:appcompat-v7就可以了。

关于Android找不到activity问题

Android启动找不到activity:

小白又来袭,刚才新建了一个项目,自己写了java和xml文件,但启动的时候发现出现如下error项:
No Launcher activity found!

The launch will only sync the application package on the device!

程序死活起不来,写的也没有问题,研究了一下,发现AndroidManifest.xml文件中没有写入activity路径。

<activity
android:name=”.MainActivity”
android:label=”@string/app_name” >
<intent-filter>
<action android:name=”android.intent.action.MAIN” />

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

加入上述代码就好了。下面是放入的具体位置:
<application
android:allowBackup=”true”
android:icon=”@drawable/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme” >
<activity
android:name=”.MainActivity”
android:label=”@string/app_name” >
<intent-filter>
<action android:name=”android.intent.action.MAIN” />

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

 

Android Studio找不到主Activity:

勾掉热更新即可,如下图:

android studio中MainActivity的R出错,找不到,标红

  1. public class MainActivity extends AppCompatActivity {
  2. @Override
  3. protected void onCreate(Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. setContentView(R.layout.activity_main);
  6. }
  7. }

看别人的问答里的回答,发现确实可以:

按ctrl+F9就好了

三种存储类型和三种存储方式

在这里插入图片描述
三种存储方式:DAS、SAN、NAS
三种存储类型:块存储、文件存储、对象存储

块存储和文件存储是我们比较熟悉的两种主流的存储类型,而对象存储(Object-based Storage)是一种新的网络存储架构,基于对象存储技术的设备就是对象存储设备(Object-based Storage Device)简称OSD。

本质是一样的,底层都是块存储,只是在对外接口上表现不一致,分别应用于不同的业务场景。

分布式存储的应用场景相对于其存储接口,现在流行分为三种:

对象存储: 也就是通常意义的键值存储,其接口就是简单的GET、PUT、DEL和其他扩展,如七牛、又拍、Swift、S3

块存储: 这种接口通常以QEMU Driver或者Kernel Module的方式存在,这种接口需要实现Linux的Block Device的接口或者QEMU提供的Block Driver接口,如Sheepdog,AWS的EBS,青云的云硬盘和阿里云的盘古系统,还有Ceph的RBD(RBD是Ceph面向块存储的接口)

文件存储: 通常意义是支持POSIX接口,它跟传统的文件系统如Ext4是一个类型的,但区别在于分布式存储提供了并行化的能力,如Ceph的CephFS(CephFS是Ceph面向文件存储的接口),但是有时候又会把GFS,HDFS这种非POSIX接口的类文件存储接口归入此类。

块存储

在这里插入图片描述
以下列出的两种存储方式都是块存储类型:

  • DAS(Direct Attach STorage):是直接连接于主机服务器的一种储存方式,每一台主机服务器有独立的储存设备,每台主机服务器的储存设备无法互通,需要跨主机存取资料时,必须经过相对复杂的设定,若主机服务器分属不同的操作系统,要存取彼此的资料,更是复杂,有些系统甚至不能存取。通常用在单一网络环境下且数据交换量不大,性能要求不高的环境下,可以说是一种应用较为早的技术实现。
  • SAN(Storage Area Network):是一种用高速(光纤)网络联接专业主机服务器的一种储存方式,此系统会位于主机群的后端,它使用高速I/O 联结方式, 如 SCSI, ESCON 及 Fibre- Channels。一般而言,SAN应用在对网络速度要求高、对数据的可靠性和安全性要求高、对数据共享的性能要求高的应用环境中,特点是代价高,性能好。例如电信、银行的大数据量关键应用。它采用SCSI 块I/O的命令集,通过在磁盘或FC(Fiber Channel)级的数据访问提供高性能的随机I/O和数据吞吐率,它具有高带宽、低延迟的优势,在高性能计算中占有一席之地,但是由于SAN系统的价格较高,且可扩展性较差,已不能满足成千上万个CPU规模的系统。

典型设备:磁盘阵列、硬盘
块存储主要是将裸磁盘空间整个映射给主机使用的。块存储就是在物理层这个层面对外提供服务,使用它的系统,有用自己的文件系统格式化。这样一旦被一个系统使用,就独占了。
就是说例如:磁盘阵列里面有5块硬盘,然后可以通过划逻辑盘、做Raid、或者LVM等方式逻辑划分出N个逻辑的硬盘。但是逻辑盘和物理盘是两个完全不同的概念。假设每个硬盘100G,共有5个硬盘,划分为逻辑盘也为5个,每个100G,但是这5个逻辑盘和原来的5个物理盘意义完全不同了。例如*个逻辑盘*个20G可能来自物理盘1,第二个20G来自物理盘2,所以逻辑盘是多个物理盘逻辑虚构出来的硬盘。
接着块存储会采用映射的方式将这几个逻辑盘映射给主机,主机上面的操作系统会识别到有5块硬盘,但是操作系统是无法区分到底是物理盘还是逻辑盘,它一概就认为只是5块裸的物理硬盘而已,跟直接拿一块物理硬盘挂载到操作系统没区别,至少操作系统感知上没有区别的。
在此方式下,操作系统还需要对挂载的裸硬盘进行分区、格式化后,才能使用,与平常主机内置的硬盘无差异。

优点
(1)这种方式的好处当然是因为通过了Raid与LVM等手段,对数据提供了保护;
(2)可以将多块廉价的硬盘组合起来,称为一个大容量的逻辑盘对外提供服务,提高了容量;
(3)写入数据时,由于是多块磁盘组合出来的逻辑盘,所以几块硬盘可以并行写入的,提升了读写效率;
(4)很多时候块存储采用SAN架构组网,传输速度以及封装协议的原因,使得传输速度和读写效率得到提升

缺点
(1)采用SAN架构组网时,需要额外为主机购买光纤通道卡,还要购买光纤交换机,造价成本高;
(2)主机之间数据无法共享,在服务器不做集群的情况下,块存储裸盘映射给主机,在格式化使用后,对于主机来说相当于本地盘,那么主机A的本地盘根本不能给主机B去使用,无法共享数据
(3)不利于不同操作系统主机间的数据共享:因为操作系统使用不同的文件系统,格式化后,不同的文件系统间的数据是共享不了的。 例如一台win7,文件系统是FAT32/NTFS,而linux是EXT4,EXT4是无法识别NTFS的文件系统的

使用场景
docker容器、虚拟机磁盘存储分配。
日志存储。
文件存储。

文件存储

在这里插入图片描述
通常,NAS产品都是文件级存储。
NAS(Network Attached Storage):是一套网络储存设备,通常是直接连在网络上并提供资料存取服务,一套 NAS 储存设备就如同一个提供数据文件服务的系统,特点是性价比高。例如教育、政府、企业等数据存储应用。
它采用NFS或CIFS命令集访问数据,以文件为传输协议,通过TCP/IP实现网络化存储,可扩展性好、价格便宜、用户易管理,如目前在集群计算中应用较多的NFS文件系统,但由于NAS的协议开销高、带宽低、延迟大,不利于在高性能集群中应用。

典型设备:FTP、NFS服务器
为了克服文件无法共享的问题,所以有了文件存储。
文件存储,就是在文件系统一层对外提供服务,系统只用访问文件系统一级就可以,各个系统都可以根据接口取访问。
文件存储也有软硬一体化的设备,但是其实一台普通的PC机,只要装上合适的操作系统和软件,就可以假设FTP与NFS服务了,架上该类服务之后的服务器,就是文件存储的一种了。
主机A可以直接对文件存储进行文件的上传和下载,与块存储不同,主机A是不需要再对文件存储进行格式化的,因为文件管理功能已经由文件存储自己搞定了。

优点
(1)造价低:随便一台机器就可以,另外普通的以太网就可以,根本不需要专用的SAN网络,所以造价低
(2)方便文件共享

缺点
(1)读写速率低,传输速率慢:以太网,上传下载速度较慢,另外所有读写都要1台服务器里面的硬盘来承受,相比起磁盘阵列动不动就十几上百块硬盘同时读写,速率慢了许多。

使用场景
日志存储。
有目录结构的文件存储。

对象存储

在这里插入图片描述
典型设备:内置大容量硬盘的分布式服务器

对象存储*常用的方案,就是多台服务器内置大容量硬盘,再装上对象存储软件,然后再额外搞几台服务作为管理节点,安装上对象存储管理软件。管理节点可以管理其他服务器对外提供读写访问功能。

之所以出现对象存储这种东西,是为了克服块存储与文件存储各自的缺点,发扬各自的优点。简单来说块存储读写快,不利于共享,文件存储读写慢,利于共享。能否弄一个读写块,利于共享的存储出来呢?于是就有了对象存储。

首先,一个文件包含了属性(术语:metadata,元数据,例如该文件的大小、修改时间、存储路径等)以及内容(数据)。

像FAT32这种文件系统,是直接将一份文件与metadata一起存储的,存储过程先将文件按照文件系统的*小块大小来打散(例如4M的文件,假设文件系统要求一个块4K,那么就将文件打散称为1000个小块),再写进硬盘里,过程中没有区分数据和metadata的。而每个块*后会告知你下一个要读取的块地址,然后一直这样顺序的按图索骥,*后完成整份文件的所有块的读取。
这种情况下读写速率很慢,因为就算你有100个机械臂在读写,但是由于你只有读取到*个块,才能知道下一个块在哪里,其实相当于只能有1个机械臂在实际工作。

而对象存储则将元数据独立出来了,控制节点叫元数据服务器(服务器+对象存储管理软件),里面主要负责存储对象的属性(主要是对象的数据被打散存放到了那几台分布式服务器中的信息)而其他负责存储数据的分布式服务器叫做OSD,主要负责存储文件的数据部分。当用户访问对象,会先访问元数据服务器,元数据服务器只负责反馈对象存储在哪里OSD,假设反馈文件A存储在B、C、D三台OSD,那么用户就会再次直接访问3台OSD服务器去读取数据。
这时候由于是3台OSD同时对外传输数据,所以传输的速度就会加快了,当OSD服务器数量越多,这种读写速度的提升就越大,通过此种方式,实现了读写快的目的。

另一方面,对象存储软件是有专门的文件系统的,所以OSD对外又相当于文件服务器,那么就不存在共享方面的困难了,也解决了文件共享方面的问题。所以对象存储的出现,很好的结合了块存储和文件存储的优点。

关键技术

对象存储文件系统的关键技术是什么?
(1)分布元数据
(2)并发数据访问,对象存储体系结构定义了一个新的、更加智能化的磁盘接口OSD

什么是OSD?

存储局域网(SAN)和网络附加存储(NAS)是我们比较熟悉的两种主流网络存储架构,而对象存储是一种新的网络存储架构,基于对象存储技术的设备就是对象存储设备,简称:OSD

在存储对象中通过什么对象方式访问对象?

在存储设备中,所有对象都有一个对象标识,通过对象标识OSD命令访问对象

OSD的主要功能是什么?
(1)数据存储。OSD管理对象数据,并将它们放置在标准的磁盘系统上,OSD不提供块接口访问方式,Client请求数据时用对象ID、偏移进行数据读写;
(2)智能分布。OSD用其自身的CPU和内存优化数据分布,并支持数据的预取。由于OSD可以智能的支持对象的预取,从而可以优化磁盘的性能
(3)每个对象元数据的管理。OSD管理存储在其上对象的元数据,该元数据与传统的inode元数据相似,通常包括对象的数据块和对象的长度。

优点
具备块存储的读写高速。
具备文件存储的共享等特性。

使用场景: (适合更新变动较少的数据)
图片存储。
视频存储。

为什么对象存储兼具块存储和文件存储的好处,还要使用块存储和文件存储呢?
(1)有一类应用是需要存储直接裸盘映射的,例如数据库。
因为数据需要存储裸盘映射给自己后,再根据自己的数据库文件系统来对裸盘进行格式化的,所以是不能够采用其他已经被格式化为某种文件系统的存储的。此类应用更合适使用块存储。
(2)对象存储的成本比起普通的文件存储还要较高,需要购买专门的对象存储软件以及大容量硬盘。如果对数据量要求不是海量,只是为了做文件共享的时候,直接用文件存储的形式好了,性价比高。

三种存储类型差异

在这里插入图片描述

三种存储方式差异

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

ubuntu 访问网络共享

如果你当前工作在ubuntu下,然而局域网中有一些windows的共享,同时又有一些linux的共享,如何访问他们呢?本文将会介绍一些简单的访问方法。

1 访问windows共享

这里介绍两种访问windows共享的方法,以供读者选择。

1)方法一

– ALT + F2 打开运行对话框

– 输入smb://xxx.xxx.xxx.xxx 另一个windows主机的IP地址,这里也可以输入主机名。

– 在打开的对话框中输入用户名,域名,密码。

– 确定

2)方法二 (ubuntu 9.10 英文版)

– 点击place -> connect to server…

– server type: window share / Server: server-name 输入相关信息,OK

– 输入用户名,域名,密码

– 确定

 

2 访问linux共享

使用ssh访问是一个不错的选择。

– ALT + F2,

– 输入ssh://xxx.xxx.xxx.xxx 另一个linux主机的IP地址,这里也可以输入主机名(不过不是每次都可以工作,原因笔者不祥,恳求读者赐教)。

– 在打开的对话框中输入用户名与密码。

– 确定。

[Ubuntu] Linux 下访问 NAS 服务器

OS: Ubuntu16.04

NAS 一定搭建了 SAMBA 服务(CIFS)。

sudo mount -t cifs //服务器IP/服务器文件夹 -o username=你的帐号,password=你的密码 /home/挂载点

如果 NAS 开启了 NFS ,-t 也可以指定 NFS。

mount nfs的可选参数

mount nfs的可选参数:
HARD mount和SOFT MOUNT:
HARD:NFS CLIENT会不断的尝试与SERVER的连接(在后台,不会给出任何提示信息,在LINUX下有的版本仍然会给出一些提示),直到MOUNT上。
SOFT:会在前台尝试与SERVER的连接,是默认的连接方式。当收到错误信息后终止mount尝试,并给出相关信息。
例如:

#mount -t nfs -o hard 192.168.0.10:/nfs /nfs

对于到底是使用hard还是soft的问题,这主要取决于你访问什么信息有关。例如你是想通过NFS来运行X PROGRAM的话,你*对不会希望由于一些意外的情况(如网络速度一下子变的很慢,插拔了一下网卡插头等)而使系统输出大量的错误信息,如果此时你用的是HARD方式的话,系统就会等待,直到能够重新与NFS SERVER建立连接传输信息。另外如果是非关键数据的话也可以使用SOFT方式,如FTP数据等,这样在远程机器暂时连接不上或关闭时就不会挂起你的会话过程。

rsize和wsize:
文件传输尺寸设定:V3没有限定传输尺寸,V2*多只能设定为8k,可以使用-rsizeand -wsize 来进行设定。这两个参数的设定对于NFS的执行效能有较大的影响
bg:在执行mount时如果无法顺利mount上时,系统会将mount的操作转移到后台并继续尝试mount,直到mount成功为止。(通常在设定/etc/fstab文件时都应该使用bg,以避免可能的mount不上而影响启动速度)
fg:和bg正好相反,是默认的参数
nfsvers=n:设定要使用的NFS版本,默认是使用2,这个选项的设定还要取决于server端是否支持NFS VER 3
mountport:设定mount的端口
port:根据server端export出的端口设定。例如,如果server使用5555端口输出NFS,那客户端就需要使用这个参数进行同样的设定
timeo=n:设置超时时间,当数据传输遇到问题时,会根据这个参数尝试进行重新传输。默认值是7/10妙(0.7秒)。如果网络连接不是很稳定的话就要加大这个数值,并且推荐使用HARD MOUNT方式,同时*好也加上INTR参数,这样你就可以终止任何挂起的文件访问。
intr: 允许通知中断一个NFS调用。当服务器没有应答需要放弃的时候有用处。
udp:使用udp作为nfs的传输协议(NFS V2只支持UDP)
tcp:使用tcp作为nfs的传输协议
namlen=n:设定远程服务器所允许的*长文件名。这个值的默认是255
acregmin=n:设定*小的在文件更新之前cache时间,默认是3
acregmax=n:设定*大的在文件更新之前cache时间,默认是60
acdirmin=n:设定*小的在目录更新之前cache时间,默认是30
acdirmax=n:设定*大的在目录更新之前cache时间,默认是60
actimeo=n:将acregmin、acregmax、acdirmin、acdirmax设定为同一个数值,默认是没有启用。
retry=n:设定当网络传输出现故障的时候,尝试重新连接多少时间后不再尝试。默认的数值是10000 minutes
noac:关闭cache机制。
同时使用多个参数的方法:mount -t nfs -o timeo=3,udp,hard 192.168.0.30:/tmp /nfs

注意,NFS客户机和服务器的选项并不一定完全相同,而且有的时候会有冲突。比如说服务器以只读的方式导出,客户端却以可写的方式mount,虽然可以成功mount上,但尝试写入的时候就会发生错误。一般服务器和客户端配置冲突的时候,会以服务器的配置为准。

对于很多使用NFS做为文件共享或者备份使用的系统来讲,-O [soft,hard]这个参数是值得我们考虑的。他会影响到运行在UNIX上生产系统的一些核心的应用,包括在启动时,运行时的状态。比如HACMP在NFS不正常挂载的情况下,HACMP会一直HAND在那儿,直到NFS客户端正常挂载。还有 ORACLE会使其宕机。

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速