现在Android开发非常流行使用Retrofit2+OkHttp3的组合做网络请求,在平时开发测试中,会有频繁切换线上线下环境的需求。一般情况下,线上线下环境url地址就是前缀不一样,修改一下前缀,重新编译打包。相当的费时间,特别是,产品,测试,后端,leader随时会丢过来一句:这是线下包,给我打个线上包;这个是线上包给我打个线下包。。。如果你正在全力奋战修改一个bug时,感觉就要崩溃了有木有。
这时就会想,如果不需要重新编译该有多好,在app里面留一个后门,在某个地方连击多少下切换线上线下环境该有多好,当然为了不被用户发现后门,可以限制在debug包里面才有这个后门。
我解决这个问题的核心是拦截器Interceptor,okhttp支持拦截器,在拦截器里面可以添加header,可以打印请求网络的日志等等,总之功能很多。我们就是要在拦截器里面替换掉url的前缀。代码如下:
public class ChangeUrlInterceptor implements Interceptor{
private Context context;
public ChangeUrlInterceptor(Context context){
this.context = context;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Request.Builder requestBuilder = request.newBuilder();
String url = request.url().toString();
if(Config.isDebugMode(context)) {
//默认是线上环境,如果是线下调试,将url前缀替换成线下的url前缀
if (url.contains(Config.UC_REQUEST_PREFIX)) {
url = url.replace(Config.UC_REQUEST_PREFIX, Config.DEBUG_UC_REQUEST_PREFIX);
}
}
request = requestBuilder.url(url).build();
return chain.proceed(request);
}
}
默认采用线上环境, 也就是Config.UC_REQUEST_PREFIX,拦截器里做一个判断如果是线下环境而且url里也包含线上url前缀,那么将前缀替换成线下环境的前缀Config.DEBUG_UC_REQUEST_PREFIX。
添加拦截器到OKHttp
OkHttpClient.Builder builder =
new OkHttpClient.Builder().connectTimeout(20 * 1000, TimeUnit.MILLISECONDS)
.readTimeout(20 * 1000, TimeUnit.MILLISECONDS);
ChangeUrlInterceptor changeUrlInterceptor = new ChangeUrlInterceptor(context);
builder.addInterceptor(changeUrlInterceptor);
return builder.build();
再看看请求
@POST(Config.UC_REQUEST_PREFIX + "member/mobilelogin")
Observable<UCResponse<UCMobileLoginData>> mobileLogin(@Body String request);
因为我项目中采用了Dagger2注入,所以我项目中只用了一个retrofit对象,然后就交给Dagger2了,加上项目中请求有好几种不同的url前缀,所以baseUrl我基本上没用,请求上用的是全量的url地址。而且是用了注解,注解不支持方法,所以我才用拦截器拦截,不然的话可以写个方法获取url前缀,是线下就返回一个线下的url前缀,是线上就返回一个线上的url前缀。所以我使用拦截器来实现是有很多前提条件的,大家如果不受上面所说的限制,可以使用更轻量的解决方案。
回到正题,我们可以再应用的某个地方留一个后门,比方说点击连续点击五下,弹出一个对话框,告诉我们当前是线上还是线下环境,然后是否切换环境。重点是点击切换环境要做什么事情。当然为了规避风险,这个后门可以做到只在debug模式的的时候才有。
exitConfirmDialog.dismiss();
if(isOnline==0){
SharedPreferencesUtils.saveIntData(getActivity(),"isOnline",1);
}else if(isOnline==1){
SharedPreferencesUtils.saveIntData(getActivity(),"isOnline",0);
}
//退出登录
//清除一切跟登录信息相关的缓存
RestartAPPTool.restartAPP(getContext(),1000);
先改变一下线上线下环境的状态值,然后退出登录,清除跟登录信息相关的一切缓存,为什么呢,因为切换了环境,再登录相当于是另外一个用户了,缓存里不能再有当前账号相关的东西了。最后一个操作是重启app。
public class RestartAPPTool {
public static void restartAPP(Context context, long Delayed){
/**开启一个新的服务,用来重启本APP*/
Intent intent1=new Intent(context,KillSelfService.class);
intent1.putExtra("PackageName",context.getPackageName());
intent1.putExtra("Delayed",Delayed);
context.startService(intent1);
/**杀死整个进程**/
killAllProcess(context);
}
/***重启整个APP*/
public static void restartAPP(Context context){
restartAPP(context,2000);
}
public static void killAllProcess(Context context) {
String processName = getServiceProcessName(context,NimService.class);
if(!TextUtils.isEmpty(processName)){
XLog.e("NimService processName: " + processName);
final ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
// ActivityManager getRunningAppProcesses()
List<ActivityManager.RunningAppProcessInfo> appProcessList = am
.getRunningAppProcesses();
if (appProcessList == null) {
return;
}
for (ActivityManager.RunningAppProcessInfo appProcess : appProcessList) {
XLog.e("processName: " + appProcess.processName);
if(processName.equals(appProcess.processName)){
android.os.Process.killProcess(appProcess.pid);
}
}
}
android.os.Process.killProcess(android.os.Process.myPid());
}
private static String getServiceProcessName(Context context, Class<? extends Service> serviceClass) {
PackageManager packageManager = context.getPackageManager();
ComponentName component = new ComponentName(context, serviceClass);
ServiceInfo serviceInfo;
try {
serviceInfo = packageManager.getServiceInfo(component, 0);
} catch (Throwable ignored) {
// Service is disabled.
return null;
}
return serviceInfo.processName;
}
}
这里就额外有一个杀死网易云信的进程,因为项目中使用了网易云信,然后这个网易云信跟咱们的用户系统是绑定的,我们用户登录了之后还要去网易云信登录。如果不杀死网易云信这个进程,去网易云信登录就会失败。
public class KillSelfService extends Service {
/**关闭应用后多久重新启动*/
private static long stopDelayed=2000;
private Handler handler;
private String PackageName;
public KillSelfService() {
handler=new Handler();
}
@Override
public int onStartCommand(final Intent intent, int flags, int startId) {
stopDelayed=intent.getLongExtra("Delayed",2000);
PackageName=intent.getStringExtra("PackageName");
handler.postDelayed(new Runnable() {
@Override
public void run() {
Intent LaunchIntent = getPackageManager().getLaunchIntentForPackage(PackageName);
startActivity(LaunchIntent);
KillSelfService.this.stopSelf();
}
},stopDelayed);
return super.onStartCommand(intent, flags, startId);
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
杀死app后一秒后重启应用。
介绍完了,这样以后切换线上线下环境就方便高效多了。