当前位置: 首页 > news >正文

Android bind service使用Binder 池的方法

一、aidl 服务端实现

1.创建aidl文件,创建单独的aidl仓库,在aidl仓库添加如下aidl文件 包括:
1.1 主aidl文件 IIpcService.aidl(IpcService.onBind直接返回IIpcService接口实现),提供Binder 池获取方法。

import android.os.ParcelFileDescriptor;
import com.android.demo.aidl.IIpcServiceListener;
// Declare any non-default types here with import statementsinterface IIpcService {/*** 根据binder代码查询对应的Binder对象* @param binderCode 服务标识码* @return 对应的Binder对象*/IBinder queryBinder(int binderCode);/*** 注册客户端监听器到服务端* @param packageName 客户端包名* @param ipcServiceListener 客户端监听器*/void register2Server(String packageName,IIpcServiceListener ipcServiceListener);/*** 从服务端注销客户端监听器* @param packageName 客户端包名*/void unregister2Server(String packageName);/*** 处理客户端请求并返回结果* @param packageName 客户端包名* @param clientRequest 客户端请求内容* @param pfd 用于传输大数据的文件描述符* @return 处理结果*/String processClientRequest(String packageName,String clientRequest,inout ParcelFileDescriptor pfd);/*** 处理客户端请求(单向异步且串行调用,无返回值)* @param packageName 客户端包名* @param clientRequest 客户端请求内容*/oneway void processClientRequestOneway(String packageName,String clientRequest);

1.2 客户端监听aidl文件 IIpcServiceListener.aidl,用于客户端向服务端注册监听。

interface IIpcServiceListener {/*** Demonstrates some basic types that you can use as parameters* and return values in AIDL.*/
//    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
//            double aDouble, String aString);String processServiceRequest(String serviceRequest);
}

1.3  Binder 池中模块1 aidl文件 IBinderModule1.aidl,处理模块1业务。

interface IBinderModule1 {/*** Demonstrates some basic types that you can use as parameters* and return values in AIDL.*/
//    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
//            double aDouble, String aString);String doWork(String serviceRequest);
}

1.4  Binder 池中模块2 aidl文件 IBinderModule2.aidl,处理模块2业务。

interface IBinderModule2 {/*** Demonstrates some basic types that you can use as parameters* and return values in AIDL.*/
//    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
//            double aDouble, String aString);String doWork(String serviceRequest);
}

2.创建service IpcService.java 及 服务端binder请求处理类LocalIpcRepositoryImpl。
IpcService.java 中创建了IIpcService.Stub实现类,该实现类queryBinder方法,基于客户端请求的binderCode,返回创建的对应的binder模块实现类。

public class IpcService extends BaseService {private static final String TAG = AppConstants.APP_TAG + "IpcService ";private NetBroadcastReceiver mNetBroadcastReceiver;HandlerThread mHandlerThread = new HandlerThread("IpcService");Handler mWorkHandler;@Overridepublic IBinder onBind(Intent intent) {return mIpcService;}@Overridepublic void onCreate() {super.onCreate();Log.d(TAG,  "onCreate() ");//registerReceiver();}@Overridepublic void onDestroy() {super.onDestroy();Log.d(TAG,  "onDestroy");//unregisterReceiver();}private IIpcService.Stub mIpcService = new IIpcService.Stub() {@Overridepublic IBinder queryBinder(int binderCode) throws RemoteException {IBinder binder = null;switch (binderCode) {case BINDER_MODULE1:binder = new BinderModule1Impl();break;case BINDER_MODULE2:binder = new BinderModule2Impl();break;default:Log.d(TAG,  "queryBinder invalid binderCode=" + binderCode);break;}return binder;}@Overridepublic void register2Server(String packageName, IIpcServiceListener ipcServiceListener) throws RemoteException {Looper.prepare();LocalIpcRepositoryImpl.getInstance().register2Server(packageName, ipcServiceListener);}@Overridepublic void unregister2Server(String packageName) throws RemoteException {LocalIpcRepositoryImpl.getInstance().unregister2Server(packageName);}@Overridepublic String processClientRequest(String packageName, String clientRequest, ParcelFileDescriptor pfd) throws RemoteException {Log.d(TAG,  "processClientRequest");return LocalIpcRepositoryImpl.getInstance().processClientRequest(packageName, clientRequest,pfd);}@Overridepublic void processClientRequestOneway(String packageName, String clientRequest) throws RemoteException {Log.d(TAG,  "processClientRequestOneway");LocalIpcRepositoryImpl.getInstance().processClientRequestOneway(packageName, clientRequest);}};}
public class LocalIpcRepositoryImpl implements LocalIpcRepository {private static final String TAG = AppConstants.APP_TAG + "LocalIpcRepositoryImpl";private HashMap<String, IIpcServiceListener> mServiceListenerMap = new HashMap<>();private IUiShow mUiShow = null;private static volatile LocalIpcRepositoryImpl mInstance;private MutableLiveData<String> mTestLiveData1 = new MutableLiveData<>();private MutableLiveData<String> mTestLiveData2 = new MutableLiveData<>();//在创建一个聚合类MediatorLiveDataprivate MediatorLiveData<String> mMediatorLiveData = new MediatorLiveData<>();private Handler mHandler = new Handler();public LocalIpcRepositoryImpl() {//分别把LiveData合并到mediatorLiveData中mMediatorLiveData.addSource(mTestLiveData1, new Observer<String>() {@Overridepublic void onChanged(String testLiveData1Str) {Log.i(TAG, "mMediatorLiveData.onChanged mTestLiveData1 testLiveData1Str:" + testLiveData1Str);mMediatorLiveData.postValue(testLiveData1Str);}});mMediatorLiveData.addSource(mTestLiveData2, new Observer<String>() {@Overridepublic void onChanged(String testLiveData2Str) {Log.i(TAG, "mMediatorLiveData.onChanged mTestLiveData2 testLiveData2Str:" + testLiveData2Str);mMediatorLiveData.postValue(testLiveData2Str);}});}public static LocalIpcRepositoryImpl getInstance() {if (mInstance == null) {synchronized (LocalIpcRepositoryImpl.class) {if (mInstance == null) {mInstance = new LocalIpcRepositoryImpl();}}}return mInstance;}@Overridepublic void register2Server(String packageName, IIpcServiceListener iFtIpcServiceListener) {boolean containsKey = mServiceListenerMap.containsKey(packageName);Log.i(TAG, "register2Server packageName:" + packageName + " containsKey?" + containsKey);mServiceListenerMap.put(packageName, iFtIpcServiceListener);}@Overridepublic void unregister2Server(String packageName) {boolean containsKey = mServiceListenerMap.containsKey(packageName);Log.i(TAG, "unregister2Server packageName:" + packageName + " containsKey?" + containsKey);if (containsKey) {mServiceListenerMap.remove(packageName);}}public void registerUiShow(IUiShow iUiShow) {mUiShow = iUiShow;}/*** 处理客户端请求** @param packageName   package name.* @param clientRequest response json from client.* @return*/@Overridepublic String processClientRequest(String packageName, String clientRequest, ParcelFileDescriptor pfd) {Log.i(TAG, "processClientRequest 11 packageName:" + packageName+ " clientRequest:" + clientRequest + " pfd:" + pfd);String ret = clientRequest;FileDescriptor fileDescriptor = pfd.getFileDescriptor();FileInputStream fis = null;try {fis = new FileInputStream(fileDescriptor);if ("text".equals(clientRequest)) {byte[] content = new byte[5];fis.read(content);Log.i(TAG, "processClientRequest 111 content:" + content);for (int i = 0; i < content.length; i++) {Log.i(TAG, "processClientRequest 113 content[" + i + "]=" + content[i]);}} else {Bitmap rawBitmap = BitmapFactory.decodeStream(fis);ret += " process success!";Log.i(TAG, "processClientRequest 222 rawBitmap ByteCount:" + rawBitmap.getByteCount() + " mUiShow:" + mUiShow);if (null != mUiShow) {mUiShow.showBitmap(rawBitmap);}}} catch (Exception e) {Log.i(TAG, "processClientRequest 33 error:" + e);e.printStackTrace();} finally {try {if (fis != null) {fis.close();}} catch (IOException e) {Log.i(TAG, "processClientRequest 44 error:" + e);}}Log.i(TAG, "processClientRequest 55 end  ret:" + ret);return ret;}/*** 处理客户端请求** @param packageName   package name.* @param clientRequest response json from client.* @return*/@Overridepublic boolean processClientRequestOneway(String packageName, String clientRequest) {Log.i(TAG, "processClientRequestOneway 11 packageName:" + packageName+ " clientRequest:" + clientRequest);try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}Log.i(TAG, "processClientRequestOneway 22 end  clientRequest:" + clientRequest);return true;}@Overridepublic String callServiceClient(String serviceRequest) {//TODO base on requestJson to call dvr or cvbox IIpcServiceListener process request,if (null == mServiceListenerMap || mServiceListenerMap.size() == 0) {Log.i(TAG, "callServiceClient 00 mServiceListenerMap size is 0 return!");return null;}Log.i(TAG, "callServiceClient 11 mServiceListenerMap.size():"+ mServiceListenerMap.size());String clientResponse = null;Iterator it = mServiceListenerMap.entrySet().iterator();boolean dvrConnected = false;while (it.hasNext()) {Map.Entry entry = (Map.Entry) it.next();String packageName = (String) entry.getKey();IIpcServiceListener ftIpcServiceListener = (IIpcServiceListener) entry.getValue();Log.i(TAG, "callServiceClient * 22 packageName:" + packageName+ " ftIpcServiceListener" + ftIpcServiceListener);try {clientResponse = ftIpcServiceListener.processServiceRequest(serviceRequest);} catch (DeadObjectException e) {Log.i(TAG, "callServiceClient 33 call it.remove error:" + e);it.remove();e.printStackTrace();} catch (RemoteException e) {e.printStackTrace();}Log.i(TAG, "callServiceClient 44 mServiceListenerMap.size():"+ mServiceListenerMap.size());}return clientResponse;}public LiveData<String> getTestLiveData() {
//        testDelaySendLiveData1(3);
//        testDelaySendLiveData2(6);return mMediatorLiveData;}private void testDelaySendLiveData1(int second) {Log.i(TAG, "testDelaySendLiveData1 11 second:" + second);mHandler.postDelayed(() -> {Log.i(TAG, "testDelaySendLiveData1 22 call setValue second:" + second);mTestLiveData1.setValue("delay " + second + " seconds");}, 1000 * second);}private void testDelaySendLiveData2(int second) {Log.i(TAG, "testDelaySendLiveData2 11 second:" + second);mHandler.postDelayed(() -> {Log.i(TAG, "testDelaySendLiveData2 22 call setValue second:" + second);mTestLiveData2.setValue("delay " + second + " seconds");}, 1000 * second);}//模拟数据变化调用public void delaySetValue() {Log.i(TAG, "delaySetValue ");testDelaySendLiveData1(3);testDelaySendLiveData2(6);}}

3.创建服务端IBinderModule1.aidl实现类BinderModule1Impl和IBinderModule2.aidl实现类BinderModule2Impl

public class BinderModule1Impl extends IBinderModule1.Stub {private static final String TAG = "BinderModule1Impl ";@Overridepublic String doWork(String serviceRequest) throws RemoteException {Log.i(TAG, "doWork: " + "serviceRequest=" + serviceRequest);return "BinderModule1Impl.doWork";}
}
public class BinderModule2Impl extends IBinderModule2.Stub {private static final String TAG = "BinderModule2Impl ";@Overridepublic String doWork(String serviceRequest) throws RemoteException {Log.i(TAG, "doWork: " + "serviceRequest=" + serviceRequest);return "BinderModule2Impl.doWork";}
}

二、aidl 请求工具类

1.创建绑定服务请求封装类IpcManager.java 和绑定服务常量类IpcServiceConstant.java添加到 aidl仓库。

public class IpcManager {private static final String TAG = "IpcManager ";private static volatile IpcManager mInstance;private Context mContext;private String mPackageName;private IIpcService mIpcService;private IIpcServiceListener mIpcServiceListener;private ServiceConnectListener mServiceConnectListener;public static final int BINDER_MODULE1 = 1;public static final int BINDER_MODULE2 = 2;private IpcManager(Context context) {mContext = context;mPackageName = (null == mContext ? null : mContext.getPackageName());}/*** getInstance.** @param context context.* @return FileTransmissionManager.*/public static IpcManager getInstance(Context context) {if (null == mInstance) {synchronized (IpcManager.class) {if (null == mInstance) {mInstance = new IpcManager(context);}}}return mInstance;}/*** connectService.* should call this function first.** @param serviceConnectListener ServiceConnectListener.* @param iFtIpcServiceListener  IIpcServiceListener.*/public void connectService(ServiceConnectListener serviceConnectListener,IIpcServiceListener iFtIpcServiceListener) {Log.i(TAG, "connectService: " + "mPackageName=" + mPackageName);mServiceConnectListener = serviceConnectListener;mIpcServiceListener = iFtIpcServiceListener;bindService();}/*** disconnectService.*/public void disconnectService() {Log.i(TAG, "disconnectService");unregister(mPackageName);unbindService();}/*** processClientRequest** @param clientRequest*/public String processClientRequest(String clientRequest, ParcelFileDescriptor pfd) {Log.i(TAG, "processClinentRequest 11 ** mPackageName:" + mPackageName+ " mFtIpcService:" + mIpcService + " clientRequest:" + clientRequest + " pfd:" + pfd);if (null != mIpcService) {try {return mIpcService.processClientRequest(mPackageName, clientRequest, pfd);} catch (RemoteException e) {Log.e(TAG, "processClinentRequest 22 e:" + e);e.printStackTrace();}}return "error";}/*** processClientRequest** @param clientRequest*/public void processClientRequestOneway(String clientRequest) {Log.i(TAG, "processClientRequestOneway 11 ** mPackageName:" + mPackageName+ " mFtIpcService:" + mIpcService + " clientRequest:" + clientRequest);if (null != mIpcService) {try {mIpcService.processClientRequestOneway(mPackageName, clientRequest);} catch (RemoteException e) {Log.e(TAG, "processClientRequestOneway 22 e:" + e);e.printStackTrace();}}}//region 客户端Binder池调用测试/*** 从服务端Binder池中获取binderCode对应的IBinder,并进行测试调用。** @param binderCode*/public String queryBinderAndTestCall(int binderCode) {String ret = "";IBinder binder = null;try {binder = mIpcService.queryBinder(binderCode);} catch (RemoteException e) {throw new RuntimeException(e);}if (null == binder) {Log.e(TAG, "queryBinderAndTestCall binder is null for binderCode:" + binderCode);return "null == binder";}switch (binderCode) {case BINDER_MODULE1:ret = testBinderModule1(binder);break;case BINDER_MODULE2:ret = testBinderModule2(binder);break;}return ret;}private String testBinderModule1(IBinder binder) {String result = "";IBinderModule1 module1 = IBinderModule1.Stub.asInterface(binder);try {result = module1.doWork("testBinderModule1");Log.i(TAG, "testBinderModule1 result:" + result);} catch (RemoteException e) {Log.e(TAG, "testBinderModule1 e:" + e);e.printStackTrace();}return result;}private String testBinderModule2(IBinder binder) {String result = "";IBinderModule2 module2 = IBinderModule2.Stub.asInterface(binder);try {result = module2.doWork("testBinderModule2");Log.i(TAG, "testBinderModule2 result:" + result);} catch (RemoteException e) {Log.e(TAG, "testBinderModule2 e:" + e);e.printStackTrace();}return result;}//endregionprivate void bindService() {Log.i(TAG, "bindService mPackageName:" + mPackageName);Intent intent = new Intent();intent.setComponent(new ComponentName(IpcServiceConstant.SERVICE_PKG_NAME,IpcServiceConstant.SERVICE_DATA_COLLECTION_NAME));startService(intent);mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);}private void unbindService() {Log.i(TAG, "unbindService mPackageName:" + mPackageName);if (isServiceConnected()) {mContext.unbindService(mConnection);mIpcService = null;}}private void startService(Intent intent) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {mContext.startForegroundService(intent);} else {mContext.startService(intent);}}private void reConnect() {if (!isServiceConnected()) {Log.i(TAG, "reConnect ");bindService();}}private boolean isServiceConnected() {return null != this.mIpcService;}private ServiceConnection mConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {Log.i(TAG, "onServiceConnected name = " + name);mIpcService = IIpcService.Stub.asInterface(service);if (null != mServiceConnectListener) {mServiceConnectListener.onConnected();register(mPackageName, mIpcServiceListener);}}@Overridepublic void onServiceDisconnected(ComponentName name) {Log.i(TAG, "onServiceDisconnected name = " + name + " mServiceConnectListener:" + mServiceConnectListener);mIpcService = null;if (null != mServiceConnectListener) {mServiceConnectListener.onDisconnected();unregister(mPackageName);}reConnect();}};private void register(String packageName, IIpcServiceListener iFtIpcServiceListener) {Log.i(TAG, "register: packageName = " + packageName+ " iFtIpcServiceListener:" + iFtIpcServiceListener);if (isServiceConnected()) {try {mIpcService.register2Server(packageName, iFtIpcServiceListener);} catch (RemoteException e) {Log.e(TAG, e.getMessage(), e);}}}private void unregister(String packageName) {Log.i(TAG, "unregister: packageName = " + packageName);if (isServiceConnected()) {try {mIpcService.unregister2Server(packageName);} catch (RemoteException e) {Log.e(TAG, e.getMessage(), e);}}}public interface ServiceConnectListener {void onConnected();void onDisconnected();}}
public class IpcServiceConstant {public static final String SERVICE_PKG_NAME = "com.android.demo.lileidemo";public static final String SERVICE_DATA_COLLECTION_NAME = SERVICE_PKG_NAME + ".service.IpcService";
}


三、aidl 客户端 

客户端引用 aidl仓库编译的aidl-debug.aar
1.创建客户端请求类 IpcUtil.java

public class IpcUtil {private static final String TAG = AppConstants.APP_TAG + "IpcUtil";private static volatile IpcUtil mInstance;private IpcManager mFtIpcManager;public static IpcUtil getInstance() {if (null == mInstance) {synchronized (IpcUtil.class) {if (null == mInstance) {mInstance = new IpcUtil();}}}return mInstance;}public void disconnectService() {Log.i(TAG, "disconnectService() ");if (null != mFtIpcManager) {mFtIpcManager.disconnectService();}}public void connectService() {Log.i(TAG, "connectService() ");if (null == mFtIpcManager) {mFtIpcManager = IpcManager.getInstance(FtClientApp.getAppContext());}mFtIpcManager.connectService(mServiceConnectListener, mFtIpcServiceListener);}public void testBinderPool(int binderCode) {Log.i(TAG, "testBinderPool() 11 binderCode:" + binderCode);String serverRet = mFtIpcManager.queryBinderAndTestCall(binderCode);Log.i(TAG, "testBinderPool() 22 serverRet:" + serverRet);}public Bitmap getTestBitmap() {File file = new File(path);if (!file.exists()) {Log.i(TAG, "getTestImage() file is null for file:" + file);return null;}// First decode with inJustDecodeBounds=true to check dimensionsfinal BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = true;BitmapFactory.decodeFile(file.getAbsolutePath(), options);options.inJustDecodeBounds = false;return BitmapFactory.decodeFile(file.getAbsolutePath(), options);}String path = "/sdcard/lilei/20230207161749238.jpg";public ParcelFileDescriptor getImagePfd() {ParcelFileDescriptor pfd = null;try {pfd = ParcelFileDescriptor.open(new File(path), MODE_READ_WRITE);} catch (FileNotFoundException e) {throw new RuntimeException(e);}Log.i(TAG, "getPfd() pfd:" + pfd);return pfd;}public ParcelFileDescriptor getBitmapPfd() {ParcelFileDescriptor pfd = null;Bitmap bitmap = BitmapFactory.decodeResource(FtClientApp.getAppContext().getResources(), R.drawable.large2);//将Bitmap转成字节数组ByteArrayOutputStream stream = new ByteArrayOutputStream();bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);byte[] byteArray = stream.toByteArray();try {MemoryFile memoryFile = new MemoryFile("test", bitmap.getByteCount());Method method = MemoryFile.class.getDeclaredMethod("getFileDescriptor");FileDescriptor des = (FileDescriptor) method.invoke(memoryFile);pfd = ParcelFileDescriptor.dup(des);//向内存中写入字节数组memoryFile.getOutputStream().write(byteArray);//关闭流memoryFile.getOutputStream().close();memoryFile.close();} catch (IOException e) {throw new RuntimeException(e);} catch (InvocationTargetException e) {throw new RuntimeException(e);} catch (NoSuchMethodException e) {throw new RuntimeException(e);} catch (IllegalAccessException e) {throw new RuntimeException(e);}Log.i(TAG, "getPfd() pfd:" + pfd + " getByteCount:" + bitmap.getByteCount());return pfd;}public ParcelFileDescriptor getTextPfd() {ParcelFileDescriptor pfd = null;try {MemoryFile memoryFile = new MemoryFile("test", 1024);Method method = MemoryFile.class.getDeclaredMethod("getFileDescriptor");FileDescriptor des = (FileDescriptor) method.invoke(memoryFile);pfd = ParcelFileDescriptor.dup(des);//向内存中写入字节数组memoryFile.getOutputStream().write(new byte[]{1, 2, 5, 4, 3});//关闭流memoryFile.getOutputStream().close();memoryFile.close();} catch (IOException e) {throw new RuntimeException(e);} catch (InvocationTargetException e) {throw new RuntimeException(e);} catch (NoSuchMethodException e) {throw new RuntimeException(e);} catch (IllegalAccessException e) {throw new RuntimeException(e);}Log.i(TAG, "getTextPfd() pfd:" + pfd);return pfd;}public void sendFileOneway(String responseJson) {Log.i(TAG, "sendFileOneway() responseJson:" + responseJson);if (null != mFtIpcManager) {mFtIpcManager.processClientRequestOneway(responseJson);}}/*** DVR or CVBOX generates the JSON format required by* the mobile app from the files to be sent.** @param requestJson json to be send to phone.*/public String sendFile(String requestJson) {Log.i(TAG, "sendFile() requestJson:" + requestJson);if (null != mFtIpcManager) {return mFtIpcManager.processClientRequest(requestJson, getBitmapPfd());}return "error";}private IpcManager.ServiceConnectListener mServiceConnectListener = new IpcManager.ServiceConnectListener() {@Overridepublic void onConnected() {Log.i(TAG, "onConnected() ");}@Overridepublic void onDisconnected() {Log.i(TAG, "onDisconnected() recall connectService");connectService();}};private IIpcServiceListener.Stub mFtIpcServiceListener = new IIpcServiceListener.Stub() {@Overridepublic String processServiceRequest(String serviceRequest) throws RemoteException {String response = "response from ft client";Log.i(TAG, "processJsonRequest() serviceRequest:" + serviceRequest);return response;}//        @Override
//        public String processJsonRequest(String requestJson) throws RemoteException {
//            String responseJson = IpcTestDataUtil.getInstance().processJsonRequest(requestJson);
//            Log.i(TAG, "processJsonRequest() requestJson:" + requestJson);
//            return responseJson;
//        }};}

2.界面调用Binder 池中的对象及接口方法。
IpcUtil.getInstance().testBinderPool(IpcManager.BINDER_MODULE1);
IpcUtil.getInstance().testBinderPool(IpcManager.BINDER_MODULE2);

http://www.dtcms.com/a/522028.html

相关文章:

  • 没有网页快照对网站有什么影响如何自己建设网站
  • 让别人做网站推广需要多少钱昆山网站制作哪家强
  • 在荔浦找事情做投简历那个网站餐饮网站建设需求分析
  • 网站制作在线版英文建站
  • 莱芜摩托车网站php网站开发程序编译软件
  • 联通公网ip申请 做网站网站广东省备案系统
  • 北京网站建设公司怎么排版2019做网站的出路
  • 高端网站建设推来客地址wordpress怎么编辑网站
  • 服务器禁止ip访问网站外网访问wordpress
  • 镇江百度网站购物网站开发uml图
  • 加密的网站使用jmeter做压测东营信息发布平台
  • jquery网站引导插件店面门头设计网站
  • 做sns网站需要什么网页程序开发采购
  • 关于网站建设与维护论文德州网站建设价格
  • 旋转器(Spinner)详细介绍
  • 做ppt找图片网站网站建设公司推广广告语
  • 昆明seo网站建设金华企业网站建站模板
  • 网站建设免费国外泰安直聘网官网
  • 网站seo分析报告案例设计官网有什么好处
  • 上海建设协会网站徐州网站外包
  • 如何检测网站死链如何在国外网站做翻译兼职
  • 设置网站关键词怎么做淘宝客模板wordpress
  • 鄂尔多斯市住房和城乡建设厅网站做微信网站价格
  • 石家庄商城网站建设网站管理员怎么做联系方式
  • ios风格网站模板新闻热点事件摘抄2022
  • 旅游网--个人网站建设 论文个人简历html代码
  • 怎么用织梦做自己的网站网站外链隐形框架
  • 网站建设经典范例wordpress extended rss
  • 网站如何做监测链接如何做国际贸易网站
  • 通付盾 公司网站建设建筑企业分公司使用总公司资质