package com.example.webview;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import com.example.webview.databinding.ActivityWebviewBinding;
import com.example.webview.utils.Constants;
public class WebViewActivity extends AppCompatActivity {
ActivityWebviewBinding mBinding;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_webview);
mBinding.webview.getSettings().setJavaScriptEnabled(true);//一定要加 否则报错
mBinding.webview.loadUrl("https://www.baidu.com/");//若要用http需在application中添加设置android:useCleartextTraffic="true"
mBinding.title.setText(getIntent().getStringExtra(Constants.TITLE));
mBinding.actionBar.setVisibility(getIntent().getBooleanExtra(Constants.IS_SHOW_ACTION_BAR, true)? View.VISIBLE:View.GONE);
mBinding.back.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
WebViewActivity.this.finish();
}
});
}
}
android:useCleartextTraffic=“true”
==》解决打不开页面的问题,设置允许HTTP请求
如何实现组件化?
arouter or cc(个人开发者)方式实现组件化
aotoService实现组件化的方式
第一步:添加依赖
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'
implementation 'com.google.auto.service:auto-service:1.0-rc7'
第二步:新增common module并添加接口类——组件下沉(implementation project(":common"))
package com.example.common.autoservice;
import android.content.Context;
public interface IWebViewService {
void startWebViewActivity(Context context, String url, String title, boolean showActionBar);
}
第三步:创建接口实现类并用注解@autoService标注
package com.example.webview;
import android.content.Context;
import android.content.Intent;
import com.example.common.autoservice.IWebViewService;
import com.example.webview.utils.Constants;
import com.google.auto.service.AutoService;
@AutoService({IWebViewService.class})
public class WebViewServiceImpl implements IWebViewService {
@Override
public void startWebViewActivity(Context context, String url, String title, boolean isShowActionBar) {
if (context != null) {
Intent intent = new Intent(context, WebViewActivity.class);
intent.putExtra(Constants.TITLE, title);
intent.putExtra(Constants.URL, url);
intent.putExtra(Constants.IS_SHOW_ACTION_BAR, isShowActionBar);
context.startActivity(intent);
}
}
}
第四步:关于ServiceLoader的设置及操作
case R.id.btn_webview:
- //startActivity(new Intent(UIDemoActivity.this, WebViewActivity.class));
// 组件化方式
+ IWebViewService webViewService = ServiceLoader.load(IWebViewService.class).iterator().next();
+ if (webViewService != null){
+ webViewService.startWebViewActivity(this, "https://www.baidu.com/", "百度", false);
}
break;
但迭代器的写法需要优化,故对ServiceLoader做封装,新增一个base的module做处理
package com.example.base.autoservice;
import java.util.ServiceLoader;
public final class CustomServiceLoader {
private CustomServiceLoader() {
}
public static S load(Class service) {
try {
return ServiceLoader.load(service).iterator().next();
} catch (Exception e) {
return null;
}
}
}
case R.id.btn_webview:
- //IWebViewService webViewService = ServiceLoader.load(IWebViewService.class).iterator().next();
+ IWebViewService webViewService = CustomServiceLoader.load(IWebViewService.class);
if (webViewService != null){
webViewService.startWebViewActivity(this, "https://www.baidu.com/", "百度", false);
}
break;
第五步:用组件化实现展示百度首页并控制是否显示ActionBar
小便签:
app的build.gradle中的引入jar包最好以文件的形式依赖,否则可能后续维护有困难,删除 implementation fileTree(dir: 'libs', include: ['*.jar'])
api与implement分别相当于Java中的public和private
将通用版本号统一设置到 rootProject 中——加入到ext(对应项目的build.gradle)
组件化(autoService比Arouter简单可靠 cc基于字符串) 模块化 层次化 控件化
kotlin实现时找不到autoService时需要添加三个插件,特别是kotlin-kapt,相当于Java的AnnotationProcessor
第一步:支持Fragment方式打开
package com.example.webview;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import com.example.webview.databinding.FragmentWebviewBinding;
import com.example.webview.utils.Constants;
public class WebViewFragment extends Fragment {
private String mUrl;
private FragmentWebviewBinding mBinding;
public static WebViewFragment newInstance(String url/*, boolean canNativeRefresh*/) {
WebViewFragment fragment = new WebViewFragment();
Bundle bundle = new Bundle();
bundle.putString(Constants.URL, url);
fragment.setArguments(bundle);
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle bundle = getArguments();
if (bundle != null) {
mUrl = bundle.getString(Constants.URL);
}
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_webview, container, false);
mBinding.webview.getSettings().setJavaScriptEnabled(true);
mBinding.webview.loadUrl(mUrl);
return mBinding.getRoot();
}
}
第二步:打开时有空白显示需添加进度条(webViewClient)——loadsir第三方控件 + 第三方Lottie动画
api 'com.kingja.loadsir:loadsir:1.3.6'
implementation 'com.airbnb.android:lottie:2.8.0'
package com.example.practicedemo;
import com.example.base.BaseApplication;
import com.example.base.loadsir.CustomCallback;
import com.example.base.loadsir.EmptyCallback;
import com.example.base.loadsir.ErrorCallback;
import com.example.base.loadsir.LoadingCallback;
import com.example.base.loadsir.TimeoutCallback;
import com.kingja.loadsir.core.LoadSir;
public class WebViewApplication extends BaseApplication{
@Override
public void onCreate() {
super.onCreate();
LoadSir.beginBuilder()
.addCallback(new ErrorCallback())
.addCallback(new EmptyCallback())
.addCallback(new LoadingCallback())
.addCallback(new TimeoutCallback())
.addCallback(new CustomCallback())
.setDefaultCallback(LoadingCallback.class)
.commit();
}
}
WebViewActivity.java
private void initWebViewFragment() {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
Fragment fragment = WebViewFragment.newInstance(getIntent().getStringExtra(Constants.URL));
ft.replace(R.id.web_view_fragment, fragment).commit();
}
package com.example.webview;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebViewClient;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import com.example.base.loadsir.ErrorCallback;
import com.example.base.loadsir.LoadingCallback;
import com.example.webview.databinding.FragmentWebviewBinding;
import com.example.webview.utils.Constants;
import com.example.webview.webviewclient.CustomWebviewClient;
import com.kingja.loadsir.callback.Callback;
import com.kingja.loadsir.core.LoadService;
import com.kingja.loadsir.core.LoadSir;
public class WebViewFragment extends Fragment implements WebViewCallback {
private static final String TAG = "WebViewFragment";
private String mUrl;
private FragmentWebviewBinding mBinding;
private LoadService mLoadService;
private boolean mIsError = false;
public static WebViewFragment newInstance(String url) {
WebViewFragment fragment = new WebViewFragment();
Bundle bundle = new Bundle();
bundle.putString(Constants.URL, url);
fragment.setArguments(bundle);
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle bundle = getArguments();
if (bundle != null) {
mUrl = bundle.getString(Constants.URL);
}
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_webview, container, false);
mBinding.webview.getSettings().setJavaScriptEnabled(true);
mBinding.webview.loadUrl(mUrl);
+ mBinding.webview.setWebViewClient(new CustomWebviewClient(this));
+ mLoadService = LoadSir.getDefault().register(mBinding.webview, new Callback.OnReloadListener() {
+ @Override
+ public void onReload(View v) {
+ mLoadService.showCallback(LoadingCallback.class);
+ mBinding.webview.reload();
}
});
- // return mBinding.getRoot();
+ return mLoadService.getLoadLayout();
}
@Override
+ public void pageStarted(String url) {
+ if (mLoadService != null) {
+ mLoadService.showCallback(LoadingCallback.class);
+ }
}
@Override
+ public void pageFinished(String url) {
+ if (mLoadService != null) {
+ if(mIsError){
+ mLoadService.showCallback(ErrorCallback.class);
+ } else {
+ mLoadService.showSuccess();
}
}
}
@Override
+ public void onError() {
+ Log.e(TAG, "onError");
+ mIsError = true;
}
}
此处需要知道WebViewClient 的作用,处理各种通知、请求事件,当查询完成或者刷新完成后对控件的状态做控制
package com.example.webview.webviewclient;
import android.graphics.Bitmap;
import android.util.Log;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.example.webview.WebViewCallback;
public class CustomWebviewClient extends WebViewClient {
private WebViewCallback mWebViewCallBack;
private static final String TAG = "CustomWebviewClient";
public CustomWebviewClient(WebViewCallback callBack){
this.mWebViewCallBack = callBack;
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
if(mWebViewCallBack != null) {
mWebViewCallBack.pageStarted(url);
} else {
Log.e(TAG, "WebViewCallBack is null.");
}
}
@Override
public void onPageFinished(WebView view, String url) {
if(mWebViewCallBack != null) {
mWebViewCallBack.pageFinished(url);
} else {
Log.e(TAG, "WebViewCallBack is null.");
}
}
@Override
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
super.onReceivedError(view, request, error);
if(mWebViewCallBack != null) {
mWebViewCallBack.onError();
} else {
Log.e(TAG, "WebViewCallBack is null.");
}
}
}
package com.example.webview;
public interface WebViewCallback {
void pageStarted(String url);
void pageFinished(String url);
void onError();
}
第三步:下拉刷新功能实现 + 各种状态(如错误界面的回调处理)
api 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0-alpha-7'
布局中包一层下拉刷新控件
package com.example.webview;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebViewClient;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import com.example.base.loadsir.ErrorCallback;
import com.example.base.loadsir.LoadingCallback;
import com.example.webview.databinding.FragmentWebviewBinding;
import com.example.webview.utils.Constants;
import com.example.webview.webviewclient.CustomWebviewClient;
import com.kingja.loadsir.callback.Callback;
import com.kingja.loadsir.core.LoadService;
import com.kingja.loadsir.core.LoadSir;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.listener.OnRefreshListener;
public class WebViewFragment extends Fragment implements WebViewCallback, OnRefreshListener {
private static final String TAG = "WebViewFragment";
private String mUrl;
private FragmentWebviewBinding mBinding;
private LoadService mLoadService;
private boolean mIsError = false;
private boolean mCanNativeRefresh;
public static WebViewFragment newInstance(String url, boolean canNativeRefresh) {
WebViewFragment fragment = new WebViewFragment();
Bundle bundle = new Bundle();
bundle.putString(Constants.URL, url);
+ bundle.putBoolean(Constants.CAN_NATIVE_REFRESH, canNativeRefresh);
fragment.setArguments(bundle);
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle bundle = getArguments();
if (bundle != null) {
mUrl = bundle.getString(Constants.URL);
+ mCanNativeRefresh = bundle.getBoolean(Constants.CAN_NATIVE_REFRESH);
}
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_webview, container, false);
mBinding.webview.getSettings().setJavaScriptEnabled(true);
mBinding.webview.loadUrl(mUrl);
mBinding.webview.setWebViewClient(new CustomWebviewClient(this));
+ mLoadService = LoadSir.getDefault().register(mBinding.smartrefreshlayout, new Callback.OnReloadListener() {
@Override
public void onReload(View v) {
mLoadService.showCallback(LoadingCallback.class);
mBinding.webview.reload();
}
});
+ mBinding.smartrefreshlayout.setOnRefreshListener(this);
+ mBinding.smartrefreshlayout.setEnableRefresh(mCanNativeRefresh);
+ mBinding.smartrefreshlayout.setEnableLoadMore(false);
// return mBinding.getRoot();
return mLoadService.getLoadLayout();
}
@Override
public void pageStarted(String url) {
if (mLoadService != null) {
mLoadService.showCallback(LoadingCallback.class);
}
}
@Override
public void pageFinished(String url) {
+ if(mIsError) {
+ mBinding.smartrefreshlayout.setEnableRefresh(true);
+ } else {
+ mBinding.smartrefreshlayout.setEnableRefresh(mCanNativeRefresh);
}
Log.d(TAG, "pageFinished");
+ mBinding.smartrefreshlayout.finishRefresh();
if (mLoadService != null) {
if(mIsError){
mLoadService.showCallback(ErrorCallback.class);
} else {
mLoadService.showSuccess();
}
}
}
@Override
public void onError() {
Log.e(TAG, "onError");
mIsError = true;
+ mBinding.smartrefreshlayout.finishRefresh();
}
@Override
+ public void onRefresh(@NonNull RefreshLayout refreshLayout) {
+ mBinding.webview.reload();
}
}
第四步:标题栏设置为对应网页标题——WebChromeClient.onReceivedTitle()
package com.example.webview.webchromeclient;
import android.util.Log;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import com.example.webview.WebViewCallback;
public class CustomWebChromeClient extends WebChromeClient {
private WebViewCallback mWebViewCallBack;
private static final String TAG = "CustomWebChromeClient";
public CustomWebChromeClient(WebViewCallback callBack){
this.mWebViewCallBack = callBack;
}
@Override
public void onReceivedTitle(WebView view, String title) {
if(mWebViewCallBack != null) {
mWebViewCallBack.updateTitle(title);
} else {
Log.e(TAG, "WebViewCallBack is null.");
}
}
}
package com.example.webview;
import ..........
public class WebViewFragment extends Fragment implements WebViewCallback, OnRefreshListener {
......
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_webview, container, false);
mBinding.webview.getSettings().setJavaScriptEnabled(true);
mBinding.webview.loadUrl(mUrl);
mBinding.webview.setWebViewClient(new CustomWebviewClient(this));
mLoadService = LoadSir.getDefault().register(mBinding.smartrefreshlayout, new Callback.OnReloadListener() {
@Override
public void onReload(View v) {
mLoadService.showCallback(LoadingCallback.class);
mBinding.webview.reload();
}
});
mBinding.smartrefreshlayout.setOnRefreshListener(this);
mBinding.smartrefreshlayout.setEnableRefresh(mCanNativeRefresh);
mBinding.smartrefreshlayout.setEnableLoadMore(false);
+ mBinding.webview.setWebChromeClient(new CustomWebChromeClient(this));
// return mBinding.getRoot();
return mLoadService.getLoadLayout();
}
.......
@Override
+ public void updateTitle(String title) {
+ if (getActivity() instanceof WebViewActivity){
+ ((WebViewActivity)getActivity()).updateTitle(title);
}
}
........
}
WebViewActivity.java
public void updateTitle(String title) {
mBinding.title.setText(title);
}
webView的组成部分(四部分)
webview组成名称 | 作用及常用方法 |
---|---|
WebView | 创建对象/加载URL/生命周期管理/状态管理 |
WebViewClient | 处理各种通知、请求事件—onPageStarted onPageFinished |
WebChromeClient | 辅助WebView处理JS对话框 |
WebSettings | 配置/管理WebView |
自定义WebSettings
package com.example.webview.settings;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
import android.util.Log;
import android.webkit.WebSettings;
import android.webkit.WebView;
import com.example.webview.BuildConfig;
public class WebViewDefaultSettings {
private WebSettings mWebSettings;
public static WebViewDefaultSettings getInstance(){
return new WebViewDefaultSettings();
}
private WebViewDefaultSettings(){
}
private static boolean isNetworkConnected(Context context) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = cm.getActiveNetworkInfo();
if (networkInfo != null) {
boolean a = networkInfo.isConnected();
return a;
} else {
return false;
}
}
public void setSettings(WebView webView) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
webView.enableSlowWholeDocumentDraw();
}
mWebSettings = webView.getSettings();
mWebSettings.setJavaScriptEnabled(true);
mWebSettings.setSupportZoom(true);
mWebSettings.setBuiltInZoomControls(false);
if (isNetworkConnected(webView.getContext())) {
mWebSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
} else {
mWebSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mWebSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
// 硬件加速兼容性问题有点多
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// webView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
// } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// webView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
// } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
// webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
// }
mWebSettings.setTextZoom(100);
mWebSettings.setDatabaseEnabled(true);
mWebSettings.setAppCacheEnabled(true);
mWebSettings.setLoadsImagesAutomatically(true);
mWebSettings.setSupportMultipleWindows(false);
mWebSettings.setBlockNetworkImage(false);//是否阻塞加载网络图片 协议http or https
mWebSettings.setAllowFileAccess(true); //允许加载本地文件html file协议
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
mWebSettings.setAllowFileAccessFromFileURLs(false); //通过 file url 加载的 Javascript 读取其他的本地文件 .建议关闭
mWebSettings.setAllowUniversalAccessFromFileURLs(false);//允许通过 file url 加载的 Javascript 可以访问其他的源,包括其他的文件和 http,https 等其他的源
}
mWebSettings.setJavaScriptCanOpenWindowsAutomatically(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mWebSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);
} else {
mWebSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
}
mWebSettings.setSavePassword(false);
mWebSettings.setSaveFormData(false);
mWebSettings.setLoadWithOverviewMode(true);
mWebSettings.setUseWideViewPort(true);
mWebSettings.setDomStorageEnabled(true);
mWebSettings.setNeedInitialFocus(true);
mWebSettings.setDefaultTextEncodingName("utf-8");//设置编码格式
mWebSettings.setDefaultFontSize(16);
mWebSettings.setMinimumFontSize(10);//设置 WebView 支持的最小字体大小,默认为 8
mWebSettings.setGeolocationEnabled(true);
mWebSettings.setUseWideViewPort(true);
String appCacheDir = webView.getContext().getDir("cache", Context.MODE_PRIVATE).getPath();
Log.i("WebSetting", "WebView cache dir: " + appCacheDir);
mWebSettings.setDatabasePath(appCacheDir);
mWebSettings.setAppCachePath(appCacheDir);
mWebSettings.setAppCacheMaxSize(1024*1024*80);
// 用户可以自己设置useragent
// mWebSettings.setUserAgentString("webprogress/build you agent info");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
webView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG);
}
}
}
WebViewFragment.java中使用自己写的WebSettings
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_webview, container, false);
- //mBinding.webview.getSettings().setJavaScriptEnabled(true);
mBinding.webview.loadUrl(mUrl);
mBinding.webview.setWebViewClient(new CustomWebviewClient(this));
mLoadService = LoadSir.getDefault().register(mBinding.smartrefreshlayout, new Callback.OnReloadListener() {
@Override
public void onReload(View v) {
mLoadService.showCallback(LoadingCallback.class);
mBinding.webview.reload();
}
});
mBinding.smartrefreshlayout.setOnRefreshListener(this);
mBinding.smartrefreshlayout.setEnableRefresh(mCanNativeRefresh);
mBinding.smartrefreshlayout.setEnableLoadMore(false);
mBinding.webview.setWebChromeClient(new CustomWebChromeClient(this));
+ WebViewDefaultSettings.getInstance().setSettings(mBinding.webview);
// return mBinding.getRoot();
return mLoadService.getLoadLayout();
}
需添加权限配置
为了使应用更可靠,使得当webView即使有问题也不影响app进程,故跨进程方案设置android:process,以activity或者service为单位(fragment宿主在Activity中不能设置为独立进程)
创建assets目录并放置html文件和js文件
demo.html
调用: showToast
日志输出工具的配置chrome log
==》CustomWebChromeClient 方法中重写onChromeMessage方法
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
Log.d(TAG, consoleMessage.message());
return super.onConsoleMessage(consoleMessage);
}
}
与JS 通信(html+javascrip+css)——命令模式
——html文件中导入js文件
——webviewprocess文件包含所有webView相关文件
——自定义WebView(addJavaScriptInterface)
——js代码调用:takeNativeAction(自定义WebView中定义的,一个参数,与IOS统一)
——HTML中的script响应
4.1 封装实现跨进程通信之openActivity
——html中添加一个跳转入口处
——对应JS中处理逻辑(命令 + 参数)
——WebView接口处理
——service连接:
——AIDL + 命令管理器
——ServiceConnection实现命令分发器
——命令接口用于扩展
——autoService获取数据迭代后取代注册
4.2 跨进程实现登录并获取返回结果