Android WIFI使用简述

61 篇文章 129 订阅
订阅专栏

前言

  随着Android版本的更新,目前最新的版本是Android 13,并且已经有部分国产手机更新了此版本,对于Android开发者来说,变化其实不那么大,而对于本文章来说就有一些变化。

正文

  在Android 12版本中,增加了对于蓝牙操作的动态权限,而在Android 13中,增加了对于WIFI操作的动态权限,日常工作生活中,我们用到WIFI功能是很多的,例如手机、电脑、电视等设备。而使用WIFI是一回事,WIFI开发又是另一回事,和蓝牙是一个道理,它们之间也有很多相似的地方。

一、创建项目

  首先创建项目,这里我使用的Android Studio版本为Android Studio Electric Eel | 2022.1.1,创建一个名为Android13Wifi的项目。

在这里插入图片描述

项目创建好之后,最低的API为24对应Android 7,最高33对应Android 13,Gradle插件版本为:7.5。

二、配置项目

作为WIFI项目我们首先要配置项目的静态权限,在AndroidManifest.xml中增加如下代码:

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

  在 Android 13 中,Google 将 Wi-Fi 扫描与位置相关内容分离, Android 13 为管理设备与周围 Wi-Fi 热点连接的应用添加 NEARBY_WIFI_DEVICES 运行时权限 (属于 NEARBY_DEVICES权限组),从而在不需要 ACCESS_FINE_LOCATION 权限的情况下,也可以让应用访问附近的 Wi-Fi 设备。

  这和Android 12中增加的三个蓝牙权限如出一辙,此前扫描蓝牙和WIFI需要定位权限一直是Google的痛点,也一直被诟病。

  所以对于仅需要连接 Wi-Fi 设备,但实际上并不需要了解设备位置的应用来说,以 Android 13 (33)为目标平台的应用现在可以通过 “neverForLocation” 属性来完善申请 NEARBY_WIFI_DEVICES 权限。

  同时我们还应该关注Android 13以下的设备使用,因此ACCESS_FINE_LOCATION权限也要配置,在AndroidManifest.xml中增加如下代码:

    <!--Android 6 ~ 12 使用定位权限-->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
        tools:ignore="CoarseFineLocation" />

    <!--Android 13及以上使用权限-->
    <uses-permission
        android:name="android.permission.NEARBY_WIFI_DEVICES"
        android:usesPermissionFlags="neverForLocation"
        tools:targetApi="Tiramisu" />

然后可以开启ViewBinding,在app下的build.gradle的android{}闭包中增加如下代码:

    buildFeatures {
        viewBinding true    //开启ViewBinding
    }

添加位置如下图所示:

在这里插入图片描述

然后我们修改以下activity_main.xml,里面的代码如下所示:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btn_open_wifi"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="8dp"
        android:text="打开WIFI"
        app:layout_constraintEnd_toStartOf="@+id/btn_scan_wifi"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn_scan_wifi"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="8dp"
        android:text="扫描WIFI"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@+id/btn_open_wifi"
        app:layout_constraintTop_toTopOf="@+id/btn_open_wifi" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_wifi"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="#EEE"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_open_wifi" />

</androidx.constraintlayout.widget.ConstraintLayout>

  就只有两个按钮(用于打开/关闭WIFI,扫描WIFI),一个列表(显示WIFI设备,连接WIFI)。现在xml已经有了,我们先搞定ViewBinding,修改MainActivity 的代码如下所示:

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

    }
}

下面就是写功能了,首先是WIFI的打开和关闭,在此之前需要获取WIFI的开关状态。

三、WIFI开关

在使用Wifi之前,我们首先要打开Wifi,而打开Wifi在不同的版本上方式不同,首先在MainActivity中声明变量

    private WifiManager wifiManager;//Wifi管理者
    private ActivityResultLauncher<Intent> openWifi;    //打开Wifi意图

然后通过系统Wifi服务获取wifiManager,在onCreate()方法中添加如下代码:

    wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);

  通过wifiManager.getWifiState()可以得到Wifi的状态,所以可以在MainActivity中 checkWifiState() 方法,代码如下所示:

    public void checkWifiState() {
        String msg;
        switch (wifiManager.getWifiState()) {
            case WifiManager.WIFI_STATE_DISABLING:
                msg = "Wifi正在关闭";
                break;
            case WifiManager.WIFI_STATE_DISABLED:
                msg = "Wifi已经关闭";
                binding.btnOpenWifi.setText("打开Wifi");
                break;
            case WifiManager.WIFI_STATE_ENABLING:
                msg = "Wifi正在开启";
                break;
            case WifiManager.WIFI_STATE_ENABLED:
                msg = "Wifi已经开启";
                binding.btnOpenWifi.setText("关闭Wifi");
                break;
            default:
                msg = "没有获取到WiFi状态";
                break;
        }
        showMsg(msg);
    }

  这里我在Wifi开启和关闭的时候修改了按钮的文字,因为涉及到Android版本的判断,所以在MainActivity中增加isAndroidTarget() 方法,代码如下所示:

    private boolean isAndroidTarget(int targetVersion) {
        return Build.VERSION.SDK_INT >= targetVersion;
    }

通过这个方法我们只要传递目标Android版本进去即可,最后再增加一个showMsg()方法,用于弹出Toast进行提示。

    private void showMsg(CharSequence msg) {
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
    }

  在Android10及以上版本打开蓝牙开关需要进行一个意图处理,这里我们通过Activity Result API来进行处理,在MainActivity中声明变量:

    private ActivityResultLauncher<Intent> openWifi;    //打开Wifi意图

然后新增一个registerIntent()方法,代码如下所示:

    private void registerIntent() {
        //打开Wifi开关
        openWifi = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
            checkWifiState();
        });
    }

  在返回结果时,调用checkWifiState()方法检查Wifi的状态,registerIntent()方法需要在Activity创建之前进行调用,修改onCreate()方法,代码如下:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    	//注册意图
        registerIntent();
        super.onCreate(savedInstanceState);
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        //通过Wifi服务获取wifi管理者对象
        wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
        //初始化视图
        initView();
    }

  这里有一个initView()方法,在MainActivity中创建它,在此方法中将对于按钮的点击事件进行处理,代码如下所示:

    private void initView() {
        //打开/关闭Wifi
        binding.btnOpenWifi.setOnClickListener(v -> {
            //Android10及以上版本
            if (isAndroidTarget(Build.VERSION_CODES.Q)) {
                openWifi.launch(new Intent(Settings.Panel.ACTION_WIFI));
            } else {
                wifiManager.setWifiEnabled(!wifiManager.isWifiEnabled());
                checkWifiState();
            }
        });
    }

  这里在点击事件中判断了当前的Android版本,10及以上版本采用意图的方式,以下的版本采用wifiManager.setWifiEnabledAPI的方式,下面我们运行一下:

在这里插入图片描述

四、WIFI扫描

  WIFI开关搞定之后,我们来做WIFI的扫描,这里的WIFI扫描是通过广播来接收结果,结果对象是ScanResult,这个名字和蓝牙扫描的ScanResult一样,不要导错了包,扫描的结果以列表的形式展现,所以我们可以根据这个结果对象来写一个Wifi适配器,适配器中就显示Wifi的名称,状态,信号强度信息。这里会用到比较多的图片资源,用来标识信号强度等级的,从我的源码中去获取即可。

在这里插入图片描述

  根据Wifi的加密与否,分为两种:加密与开放,每一种有五个图标来分别表示不同的信号强度,这里我做了两个level-list,是wifi_level.xml和wifi_lock_level.xml,在代码中可以通过信号强度得到不同的level,然后根据加密状态设置level资源图标即可。

① Wifi适配器

要创建适配器,首先要创建item的xml,在layout下创建一个item_wifi_rv.xml,代码如下所示:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="1dp"
    android:background="@color/white">

    <TextView
        android:id="@+id/tv_wifi_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:text="Wifi 名称"
        android:textColor="@color/black"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_wifi_state"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="2dp"
        android:layout_marginBottom="16dp"
        android:text="Wifi 状态"
        android:textSize="12sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tv_wifi_name" />

    <ImageView
        android:id="@+id/iv_signal"
        android:layout_width="36dp"
        android:layout_height="36dp"
        android:layout_marginEnd="16dp"
        app:layout_constraintBottom_toBottomOf="@+id/tv_wifi_state"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="@+id/tv_wifi_name"
        android:src="@drawable/wifi_level" />
</androidx.constraintlayout.widget.ConstraintLayout>

如我前面所说的,就是三个内容

在这里插入图片描述

下面写适配器,在com.llw.wifi下创建一个WifiAdapter类,代码如下所示:

public class WifiAdapter extends RecyclerView.Adapter<WifiAdapter.ViewHolder> {

    private final List<ScanResult> lists;

    public WifiAdapter(List<ScanResult> lists) {
        this.lists = lists;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        ItemWifiRvBinding binding = ItemWifiRvBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
        return new ViewHolder(binding);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        //Wifi名称
        holder.binding.tvWifiName.setText(lists.get(position).SSID);
        //Wifi功能
        String capabilities = lists.get(position).capabilities;
        //Wifi状态标识 true:加密,false:开放
        boolean wifiStateFlag = capabilities.contains("WEP") || capabilities.contains("PSK") || capabilities.contains("EAP");
        //Wifi状态描述
        String wifiState = wifiStateFlag ? "加密" : "开放";
        holder.binding.tvWifiState.setText(wifiState);
        //信号强度
        int imgLevel;
        int level = lists.get(position).level;
        if (level <= 0 && level >= -50) {
            imgLevel = 5;
        } else if (level < -50 && level >= -70) {
            imgLevel = 4;
        } else if (level < -70 && level >= -80) {
            imgLevel = 3;
        } else if (level < -80 && level >= -100) {
            imgLevel = 2;
        } else {
            imgLevel = 1;
        }
        //根据是否加密设置不同的图片资源
        holder.binding.ivSignal.setImageResource(wifiStateFlag ? R.drawable.wifi_lock_level : R.drawable.wifi_level);
        //设置图片等级
        holder.binding.ivSignal.setImageLevel(imgLevel);
    }

    @Override
    public int getItemCount() {
        return lists.size();
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {

        public ItemWifiRvBinding binding;

        public ViewHolder(@NonNull ItemWifiRvBinding itemWifiRvBinding) {
            super(itemWifiRvBinding.getRoot());
            binding = itemWifiRvBinding;
        }
    }
}

  这里就简单用了一下ViewBinding,核心的内容在onBindViewHolder()方法中,这里我们根据扫描结果进行三个内容的渲染。

② 扫描结果处理

下面我们回到MainActivity,声明变量:

    private final List<ScanResult> wifiList = new ArrayList<>();    //Wifi结果列表
    private WifiAdapter wifiAdapter;    //Wifi适配器

扫描成功或者适配都可以处理,在MainActivity中新增如下方法代码:

    private void scanSuccess() {
        //扫描成功:新的扫描结果
        wifiList.clear();
        wifiList.addAll(wifiManager.getScanResults());
        wifiAdapter.notifyDataSetChanged();
    }

    private void scanFailure() {
        // 处理失败:新的扫描没有成功,使用旧的扫描结果
        wifiList.clear();
        wifiList.addAll(wifiManager.getScanResults());
        wifiAdapter.notifyDataSetChanged();
    }

  你会发现两个方法的内容是一样的,但是含义不同,当然你如果了解的话,就使用一个方法也行。下面在MainActivity中新增如下代码:

    /**
     * Wifi扫描广播接收器
     */
    private final BroadcastReceiver wifiScanReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context c, Intent intent) {
            boolean success = intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false);
            if (success) {
                scanSuccess();
            } else {
                scanFailure();
            }
        }
    };

通过接收广播来刷新数据,在MainActivity中再增加一个initScan(),代码如下所示:

    private void initScan() {
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
        registerReceiver(wifiScanReceiver, intentFilter);
    }

在页面初始化之后就注册广播接收器,在onCreate()中调用。

在这里插入图片描述

下面需要配置一下适配器和RecyclerView,在initView()方法中增加如下代码:

	wifiAdapter = new WifiAdapter(wifiList);
	binding.rvWifi.setLayoutManager(new LinearLayoutManager(this));
    binding.rvWifi.setAdapter(wifiAdapter);

③ 启动扫描

  启动扫描之前要做的是权限的处理,在MainActivity中声明变量:

    private ActivityResultLauncher<String[]> requestPermission;     //请求权限意图

然后在registerIntent()方法中添加如下代码:

        requestPermission = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), result -> {
            if (Boolean.TRUE.equals(result.get(Manifest.permission.NEARBY_WIFI_DEVICES))
                    || Boolean.TRUE.equals(result.get(Manifest.permission.ACCESS_FINE_LOCATION))) {
                //扫描Wifi
                showMsg(wifiManager.startScan() ? "扫描Wifi中" : "开启扫描失败");
            } else {
                showMsg("扫描设备需要此权限");
            }
        });

最后就是扫描Wifi按钮的点击事件,同样是在initView()方法中添加,代码如下:

        //扫描Wifi 按钮点击事件
        binding.btnScanWifi.setOnClickListener(v -> {
            //是否打开Wifi
            if (wifiManager.getWifiState() != WifiManager.WIFI_STATE_ENABLED) return;
            //Android13及以上版本
            if (isAndroidTarget(Build.VERSION_CODES.TIRAMISU)) {
                if (!hasPermission(Manifest.permission.NEARBY_WIFI_DEVICES) && !hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)) {
                    requestPermission.launch(new String[]{Manifest.permission.NEARBY_WIFI_DEVICES, Manifest.permission.ACCESS_FINE_LOCATION});
                    return;
                }
            } else {
                if (!hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)) {
                    requestPermission.launch(new String[]{Manifest.permission.ACCESS_FINE_LOCATION});
                    return;
                }
            }
            //扫描Wifi
            showMsg(wifiManager.startScan() ? "扫描Wifi中" : "开启扫描失败");
        });

  这里我在Android 13以上版本同时请求了定位和Wifi权限,如果不这么做的话,调用wifiManager.startScan()就会返回false,而在Android 13以下就只请求定位权限即可,这里还需要给MainActivity添加一个@SuppressLint("MissingPermission")注解,如下图所示:

在这里插入图片描述

  这样在api 33中使用wifi相关的api时就不会提示错误了,不过你得注意一点,就是你在使用之前确保权限已经获取到,否则会报错闪退。wifiManager.startScan()调用会启动系统扫描,通过系统在扫描结束后,会发出WifiManager.SCAN_RESULTS_AVAILABLE_ACTION的广播,当我们的接收者接收到这个广播的时候,通过WifiManager的getScanResults()就能获取到扫描结果的集合了。如果扫描失败就会返回之前的值,成功最近最新的值。

下面我们运行看一下:

在这里插入图片描述

  这样看起来还是不错吧,现在有一个问题,就是这个扫描的wifi没有排序,同时没有wifi名称的我们应该过滤掉。

④ 排序与过滤

  现在我们已经知道扫描成功和失败的结果区别了,所以就合并以下,同时增加过滤掉空名称的WIFI兵器信号强度进行排序,修改一下广播接收器中的代码,如下所示:

    private final BroadcastReceiver wifiScanReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context c, Intent intent) {
            boolean success = intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false);
            Log.e(TAG, "onReceive: " + (success ? "成功" : "失败") );
            //处理扫描结果
            wifiList.clear();
            for (ScanResult scanResult : wifiManager.getScanResults()) {
                if (!scanResult.SSID.isEmpty()) {
                    wifiList.add(scanResult);
                }
            }
            sortByLevel(wifiList);
            wifiAdapter.notifyDataSetChanged();
        }
    };

这里的scanSuccess()和scanFailure()就可以删掉了,再增加一个sortByLevel()方法,代码如下:

    private void sortByLevel(List<ScanResult> list) {
        Collections.sort(list, (lhs, rhs) -> rhs.level - lhs.level);
    }

下面我们再运行一下看看:

在这里插入图片描述

五、WIFI连接

  Wifi的连接相对扫描来说复杂一点,假设现在有三个Wifi,分别是A、B、C。刚开始三个Wifi都没有连接过,在第一次连接A的时候,我们需要输入Wifi密码,密码正确才会建立连接,连接成功后,我们连接B,同样输入密码,此时A就会断开,连接B成功,此时我再转头去连接A,因为之前成功连接过,有保存记录,所以再连接A的时候直接连接就可以了,不再需要密码了。

① Wifi连接工具类

  基于这个情况我写了一个工具类,在com.llw.wifi下新建一个EasyWifi类,代码如下所示:

public class EasyWifi {

    private static final String TAG = EasyWifi.class.getSimpleName();

    private final ConnectivityManager connectivityManager;//连接管理者

    private final WifiManager wifiManager;//Wifi管理者

    private WifiConnectCallback wifiConnectCallback;

    @SuppressLint("StaticFieldLeak")
    private static volatile EasyWifi mInstance;

    private final Context mContext;

    public EasyWifi(Context context) {
        mContext = context;
        wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
        connectivityManager = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
    }

    public static EasyWifi initialize(Context context) {
        if (mInstance == null) {
            synchronized (EasyWifi.class) {
                if (mInstance == null) {
                    mInstance = new EasyWifi(context);
                }
            }
        }
        return mInstance;
    }

    public void setWifiConnectCallback(WifiConnectCallback wifiConnectCallback) {
        this.wifiConnectCallback = wifiConnectCallback;
    }

    /**
     * 连接Wifi
     *
     * @param scanResult 扫描结果
     * @param password   密码
     */
    public void connectWifi(ScanResult scanResult, String password) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            connectByNew(scanResult.SSID, password);
        } else {
            connectByOld(scanResult, password);
        }
    }

    /**
     * Android 10 以下使用
     *
     * @param scanResult 扫描结果
     * @param password   密码
     */
    private void connectByOld(ScanResult scanResult, String password) {
        String ssid = scanResult.SSID;
        boolean isSuccess;
        WifiConfiguration configured = isExist(ssid);
        if (configured != null) {
            //在配置表中找到了,直接连接
            isSuccess = wifiManager.enableNetwork(configured.networkId, true);
        } else {
            WifiConfiguration wifiConfig = createWifiConfig(ssid, password, getCipherType(scanResult.capabilities));
            int netId = wifiManager.addNetwork(wifiConfig);
            isSuccess = wifiManager.enableNetwork(netId, true);
        }
        Log.d(TAG, "connectWifi: " + (isSuccess ? "成功" : "失败"));
    }

    /**
     * Android 10及以上版本使用此方式连接Wifi
     *
     * @param ssid     名称
     * @param password 密码
     */
    @SuppressLint("NewApi")
    private void connectByNew(String ssid, String password) {
        WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier.Builder()
                .setSsid(ssid)
                .setWpa2Passphrase(password)
                .build();
        //网络请求
        NetworkRequest request = new NetworkRequest.Builder()
                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
                .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
                .addCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
                .setNetworkSpecifier(wifiNetworkSpecifier)
                .build();
        //网络回调处理
        ConnectivityManager.NetworkCallback networkCallback = new ConnectivityManager.NetworkCallback() {
            @Override
            public void onAvailable(@NonNull Network network) {
                super.onAvailable(network);
                if (wifiConnectCallback != null) {
                    wifiConnectCallback.onSuccess(network);
                }
            }

            @Override
            public void onUnavailable() {
                super.onUnavailable();
                if (wifiConnectCallback != null) {
                    wifiConnectCallback.onFailure();
                }
            }
        };
        //请求连接网络
        connectivityManager.requestNetwork(request, networkCallback);
    }

    @SuppressLint("NewApi")
    private void connectBySug(String ssid, String password) {
        WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
                .setSsid(ssid)
                .setWpa2Passphrase(password)
                .setIsAppInteractionRequired(true)
                .build();
        List<WifiNetworkSuggestion> suggestionList = new ArrayList<>();
        suggestionList.add(suggestion);
        int status = wifiManager.addNetworkSuggestions(suggestionList);
        if (status != WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS) {
            return;
        }
        IntentFilter intentFilter = new IntentFilter(WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION);
        BroadcastReceiver wifiScanReceiver = new BroadcastReceiver() {

            @Override
            public void onReceive(Context context, Intent intent) {
                if (!intent.getAction().equals(WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION)) {
                    return;
                }
            }
        };
        mContext.registerReceiver(wifiScanReceiver, intentFilter);
    }

    /**
     * 创建Wifi配置
     *
     * @param ssid     名称
     * @param password 密码
     * @param type     类型
     */
    private WifiConfiguration createWifiConfig(String ssid, String password, WifiCapability type) {
        WifiConfiguration config = new WifiConfiguration();
        config.allowedAuthAlgorithms.clear();
        config.allowedGroupCiphers.clear();
        config.allowedKeyManagement.clear();
        config.allowedPairwiseCiphers.clear();
        config.allowedProtocols.clear();
        config.SSID = "\"" + ssid + "\"";
        WifiConfiguration configured = isExist(ssid);
        if (configured != null) {
            wifiManager.removeNetwork(configured.networkId);
            wifiManager.saveConfiguration();
        }

        //不需要密码的场景
        if (type == WifiCapability.WIFI_CIPHER_NO_PASS) {
            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
            //以WEP加密的场景
        } else if (type == WifiCapability.WIFI_CIPHER_WEP) {
            config.hiddenSSID = true;
            config.wepKeys[0] = "\"" + password + "\"";
            config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
            config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED);
            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
            config.wepTxKeyIndex = 0;
            //以WPA加密的场景,自己测试时,发现热点以WPA2建立时,同样可以用这种配置连接
        } else if (type == WifiCapability.WIFI_CIPHER_WPA) {
            config.preSharedKey = "\"" + password + "\"";
            config.hiddenSSID = true;
            config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
            config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
            config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);
            config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
            config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
            config.status = WifiConfiguration.Status.ENABLED;
        }
        return config;
    }

    /**
     * 网络是否连接
     */
    public static boolean isNetConnected(ConnectivityManager connectivityManager) {
        return connectivityManager.getActiveNetwork() != null;
    }

    /**
     * 连接网络类型是否为Wifi
     */
    public static boolean isWifi(ConnectivityManager connectivityManager) {
        if (connectivityManager.getActiveNetwork() == null) {
            return false;
        }
        NetworkCapabilities networkCapabilities = connectivityManager.getNetworkCapabilities(connectivityManager.getActiveNetwork());
        if (networkCapabilities != null) {
            return false;
        }
        return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI);
    }

    /**
     * 配置表是否存在对应的Wifi配置
     * @param SSID
     * @return
     */
    @SuppressLint("MissingPermission")
    private WifiConfiguration isExist(String SSID) {
        List<WifiConfiguration> existingConfigs = wifiManager.getConfiguredNetworks();
        for (WifiConfiguration existingConfig : existingConfigs) {
            if (existingConfig.SSID.equals("\"" + SSID + "\"")) {
                return existingConfig;
            }
        }
        return null;
    }

    private WifiCapability getCipherType(String capabilities) {
        if (capabilities.contains("WEB")) {
            return WifiCapability.WIFI_CIPHER_WEP;
        } else if (capabilities.contains("PSK")) {
            return WifiCapability.WIFI_CIPHER_WPA;
        } else if (capabilities.contains("WPS")) {
            return WifiCapability.WIFI_CIPHER_NO_PASS;
        } else {
            return WifiCapability.WIFI_CIPHER_NO_PASS;
        }
    }

    /**
     * wifi连接回调接口
     */
    public interface WifiConnectCallback {

        void onSuccess(Network network);

        void onFailure();
    }

    public enum WifiCapability {
        WIFI_CIPHER_WEP, WIFI_CIPHER_WPA, WIFI_CIPHER_NO_PASS
    }
}

  这里对于Wifi的处理,主要是连接方面的,你当然也可以把扫描wifi放进来,对于wifi的连接,需要区分版本进行不同的处理,Android 10 及以上和Android 10以下是不同的方式,下面我们来使用这个工具类。

② 适配器点击处理

  下面在WifiAdapter中增加一个接口,代码如下所示:

    public interface OnItemClickListener {
        void onItemClick(int position);
    }

然后提供一个set方法,供使用的地方进行回调处理,代码如下:

    public void setOnItemClickListener(OnItemClickListener listener) {
        this.listener = listener;
    }

然后在onCreateViewHolder()方法中添加接口方法的使用,代码如下:

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        ItemWifiRvBinding binding = ItemWifiRvBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
        ViewHolder viewHolder = new ViewHolder(binding);
        //添加视图点击事件
        binding.getRoot().setOnClickListener(v -> {
            if (listener != null) {
                listener.onItemClick(viewHolder.getAdapterPosition());
            }
        });
        connectivityManager = (ConnectivityManager) parent.getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
        return viewHolder;
    }

最后回到MainActivity中进行注册监听

在这里插入图片描述

在这里插入图片描述

然后实现方法:

    @Override
    public void onItemClick(int position) {
        ScanResult scanResult = wifiList.get(position);
        //获取Wifi扫描结果
        String capabilities = scanResult.capabilities;
        //Wifi状态标识 true:加密,false:开放
        boolean wifiStateFlag = capabilities.contains("WEP") || capabilities.contains("PSK") || capabilities.contains("EAP");

        if (wifiStateFlag) {
            
        } else {
            
        }
    }

  在这个方法中我们根据是否需要密码进行不同的处理,先看不需要密码的处理,我们这里需要使用工具类,在MainActivity中声明变量:

    private EasyWifi easyWifi;

然后在onCreate()方法中进行初始化和设置连接监听。

	easyWifi = EasyWifi.initialize(this);
    easyWifi.setWifiConnectCallback(this);

在这里插入图片描述

然后实现回调方法。

    @Override
    public void onSuccess(Network network) {
        showMsg("连接成功");
    }

    @Override
    public void onFailure() {
        showMsg("连接失败");
    }

  这里我们只是提示一下连接成功和失败。现在就是不需要密码时的处理了,在修改适配器Item点击事件中的if判断,代码如下:

	if (wifiStateFlag) {

	} else {
		easyWifi.connectWifi(scanResult,"");
	}

这里不需要密码,而需要密码则麻烦一些,我们需要写一个弹窗来输入密码。

③ 密码弹窗

首先我们需要创建弹窗所需要的布局文件,在layout下新建一个dialog_connect_wifi.xml文件,代码如下所示:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <com.google.android.material.appbar.MaterialToolbar
        android:id="@+id/materialToolbar"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="@color/white"
        android:minHeight="?attr/actionBarSize"
        android:theme="?attr/actionBarTheme"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:title="Wifi名称"
        app:titleCentered="true"
        app:titleTextColor="@color/black" />

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/pwd_layout"
        style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        android:textColorHint="#989898"
        app:boxStrokeColor="@color/black"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/materialToolbar">

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/et_pwd"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="密码"
            android:inputType="textPassword|textCapCharacters"
            android:lines="1"
            android:singleLine="true" />

    </com.google.android.material.textfield.TextInputLayout>

    <com.google.android.material.button.MaterialButton
        android:id="@+id/btn_cancel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="取消"
        app:cornerRadius="24dp"
        app:layout_constraintEnd_toStartOf="@+id/btn_connect"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="@+id/pwd_layout"
        app:layout_constraintTop_toTopOf="@+id/btn_connect" />

    <com.google.android.material.button.MaterialButton
        android:id="@+id/btn_connect"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:layout_marginBottom="16dp"
        android:text="连接"
        app:cornerRadius="24dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="@+id/pwd_layout"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@+id/btn_cancel"
        app:layout_constraintTop_toBottomOf="@+id/pwd_layout" />
</androidx.constraintlayout.widget.ConstraintLayout>

这个布局很简单,就一个输入框,两个按钮,下面我们回到MainActivity中,增加如下方法代码:

    private void showConnectWifiDialog(ScanResult scanResult) {
        BottomSheetDialog dialog = new BottomSheetDialog(this);
        DialogConnectWifiBinding binding = DialogConnectWifiBinding.inflate(LayoutInflater.from(this), null, false);
        binding.materialToolbar.setTitle(scanResult.SSID);
        binding.btnCancel.setOnClickListener(v -> dialog.dismiss());
        binding.btnConnect.setOnClickListener(v -> {
            String password = binding.etPwd.getText().toString().trim();
            if (password.isEmpty()) {
                showMsg("请输入密码");
                return;
            }
            easyWifi.connectWifi(scanResult, password);
            dialog.dismiss();
        });
        dialog.setContentView(binding.getRoot());
        dialog.show();
    }

  这个方法就是显示密码输入弹窗,当输入密码之后就连接wifi,连接过程中就会触发之前工具类中的回调,下面我们需要调用这个连接方法,还是之前的那个if语句,代码如下所示:

        if (wifiStateFlag) {
            showConnectWifiDialog(scanResult);
        } else {
            easyWifi.connectWifi(scanResult,"");
        }

  现在可以运行了,因为Wifi连接涉及到隐私信息,所以我就不做动图演示了,连接成功之后会有提示,然后你打开系统Wifi页面会看到如下图所示的:

在这里插入图片描述

  你会看到这里连接的wifi下面提示了是通过Android13Wifi这个软件进行的wifi连接,当我们的程序被杀死,wifi就会断连,这是因为我们走的不是系统的wifi连接的方式。

六、源码

  文章中的wifi使用还是比较浅显的,简单了解一下,而如果你是专门从事WIFI应用开发的话,则需要花心思去研究了,不能流于表面,或者全部靠别人来帮你解决,能帮你的只有自己,山高水长,后会有期~

如果对你有帮助的话,不妨Star一下~

源码地址 : Android13Wifi

Android WiFi直连 双向通信
奋斗的菜鸟ing
09-02 1万+
本文主要说一下,Android通过WIFi直连的方式实现图片双向传输(图片可以传输,也可以把它修改下传输聊天信息了)。 WiFi直连概述 WiFi直连也就是WiFi设备点对点连接(WiFi P2P),它允许具有适当硬件的Android 4.0(API级别14)或更高版本的设备通过Wi-Fi直接相互连接,而无需中间接入点。使用这些API,您可以发现并连接到其他设备(前提是每个设备支持Wi-F...
Mob短信验证码登录(Android
weixin_47024334的博客
10-08 3948
作为小白,我也踩了不少坑。但是我们要学会爬起来,希望本篇文章会对你有所帮助!目录前言一、创建项目1、通过官网进行注册2.如何寻找包名和MD5签名二、使用步骤1.配置你的MobSDK的设置2.加入对应的依赖包3.配置settings.gradle中的设置4.添加权限。
Android NDK开发详解连接性之请求对附近 Wi-Fi 设备的访问权限
hnjzfwy的博客
11-17 391
原生开发套件 (NDK) 是一套工具,使您能够在 Android 应用中使用 C 和 C++ 代码,并提供众多平台库,您可使用这些平台库管理原生 activity 和访问实体设备组件,例如传感器和触控输入。NDK 可能不适合大多数 Android 编程初学者,这些初学者只需使用 Java 代码和框架 API 开发应用
Android Wi-Fi
最新发布
liuning1985622的博客
05-07 958
WLAN是Wireless Local Area Network的简称,指应用无线通信技术将计算机设备互联起来,构成可以互相通信和实现资源共享的网络体系。无线局域网本质的特点是不再使用通信电缆将计算机与网络连接起来,而是通过无线的方式连接,从而使网络的构建和终端的移动更加灵活。它是相当便利的数据传输系统,它利用射频(Radio Frequency;
android wifi工具类(包括静态IP设置)
06-17
整理androidwifi相关的工具类,整理成项目可运行。 包括wifi的打开、关闭、连接、切换、监听、静态IP设置、网关、DNS。
Android - 抑制lint的Android XML的警告:tools:ignore
热门推荐
Mystra
12-29 4万+
抑制lint的Android XML的警告:tools:ignore本文地址:http://blog.csdn.net/caroline_wendyAndroid的XML经常会出现警告,对于一个良好的程序,应该认真对待所有的警告。除非我们可以确认警告,才可以排除。显示所有警告的方法:Analyze-> Inspect Code; 就可以检查出所有的警告;抑制警告使用: tools:ignore.
Android13 适配指南
weixin_42504805的博客
05-25 2773
于2022年8月15日正式发布(发布时间较往年早了一些),正式版Release源代码也于当日被推送到Android开源项目。截止到笔者撰写这篇文章时,国内部分应用软件开发厂商已逐步接到手机厂商(华米OV等)的新版本适配要求。当前,对于Android应用开发者来说,Android 13 的软件兼容适配已需提上工作日程。为了贴合这篇文章的标题,本篇文章结合Android Developer官方文档,围绕Android13适配点与Android13新特性两个方面进行详细说明。
简述Android重力感应实现方式
01-20
Android一词的本义指"机器人",同时也是Google于2007年11月5日宣布的基于Linux平台的开源手机操作系统的名称,该平台由操作系统、中间件、用户界面和应用软件组成,号称是为移动终端打造的真正开放和完整的移动软件。...
Android四大组件简述
03-26
Android四大组件简述demo用例。
Android系统自动化测试简述
03-23
入职两月有余,从之前的androidapp开发到现在的测试框架开发,工作中遇到很多问题,趁这次机会分享一下。  Android自动化测试目前可借鉴的经验不多,现在采取的方式就是通过java代码对Activity和View进行操作,目前...
简述Android应用之蓝牙传感应用
01-20
Android是基于Linux开放性内核的操作系统,是Google公司在2007年11月5日公布的手机操作系统。早期由原名为"Android"的公司开发,谷歌在2005年收购"Android.Inc"后,继续进行对Android系统开发运营,它采用了软件堆层...
Android版本更新、判断网络(WiFi,本地网络)工具类
10-10
安卓端版本更新的,判断网络连接的工具类,带有demo使用的方法
android wifi 热点连接以及Socket通信(经测试有效)
07-31
wifi热点的建立 连接 以及通信(聊天室设计,目前由于手机有限,仅在两手机测试通过)
简述Android中SELinux的TE
08-27
SELinux使用类型强制来改进强制访问控制。这篇文章给大家介绍了Android中SELinux的TE的相关知识,感兴趣的朋友一起看看吧
android 获取wifi的加密类型,wifi加密类型
weixin_39638048的博客
05-26 1481
最近开发wifi方面的功能,需要根据wifi加密类型来展现UI。遇到了一个比较蛋疼的问题:如何判断wifi加密类型wifi的管理需要通过WifiManager来操作WifiManager mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);开启和关闭wifimWifiManager.setWifiE...
安卓实战开发之——使用 WIFI 进行设备搜索并获取相应信息
静水流深,沧笙踏歌
06-15 2970
实现使用 WIFI 进行连接设备搜索并获取相应信息的功能案例
手机网络怎么共享给电脑_亲测成功,旧手机共享网络的几种方法
weixin_39942785的博客
10-27 6315
前面写过文章,介绍了把手机网络共享给电脑或其他手机等无线设备的方法。下面简单总结一下。1、手机开启移动数据上网,打开手机设置,开启便携式热点,移动网络共享,配置好wifi名称和密码,如下图所示。电脑或其他手机等无线设备接入该WIFI热点即可上网。这种方法电脑必须有无线网卡,手机WIFI热点可以接入多台电脑或无线设备。这种方法手机相当于变成了一台4G无线路由器。手机开wifi热点,共享网络给电脑,变...
简化Android应用开发:使用WifiUtils工具类轻松管理WiFi网络
小北的博客
11-03 307
这个工具类还提供了一些辅助方法,比如获取当前连接的WiFi信息、检查是否有当前可用的WiFi连接、添加WiFi配置到系统、创建WiFi配置、获取是否已经存在的配置、移除同名WiFi、判断扫描结果中是否包含了特定名称的WiFi等。通过使用这个工具类,开发人员可以更专注于应用的核心功能,而无需过多关注底层的WiFi管理细节。这个工具类提供了一系列方法,可以方便地执行诸如检查WiFi状态、打开或关闭WiFi、扫描可用的WiFi网络、连接到指定的WiFi网络、断开当前连接的WiFi网络等操作。
Android开发 无线Wifi+WifiUtil工具类(1),2024Android面试心得
2401_84149845的博客
04-07 311
Android 详细知识点思维脑图(技能树)】我个人是做Android开发,已经有十来年了,目前在某创业公司任职CTO兼系统架构师。虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。
简述Android中如何使用列表视图listview
04-20
Android使用列表视图 ListView 可以通过以下步骤实现: 1. 在 XML 中定义 ListView 控件; 2. 在 Java 代码中创建 Adapter 对象,继承自 BaseAdapter; 3. 在 Adapter 实现中,重写 getCount() 方法获取列表项数量,getView() 方法返回列表项视图; 4. 将 Adapter 对象设置给 ListView 控件,调用 setAdapter() 方法即可实现列表视图。 在实际开发中,还可以通过设置布局管理器来实现各种复杂的列表视图布局,例如网格布局、瀑布流布局等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
写文章

热门文章

  • JDK 安装与环境变量配置(Win10详细版) 445482
  • Android Studio 安装配置教程 - Windows(详细版) 238423
  • Android Studio 打包APK(详细版) 135743
  • Android 高德地图API(详细步骤+源码) 78649
  • Android Studio 安装APK在虚拟机时报 Installation failed due to: 'null' 解决 37526

分类专栏

  • Flutter 5篇
  • 蓝牙 21篇
  • 天气APP 36篇
  • 杂谈 6篇
  • AOSP Framework 1篇
  • 自定义View 6篇
  • Compose 9篇
  • MVVM 13篇
  • 垃圾分类App 7篇
  • App功能 5篇
  • 微信小程序 2篇
  • Android知识点 61篇
  • Demo 31篇
  • 音乐APP 6篇
  • 鸿蒙 12篇
  • Material UI 2篇
  • 自定义控件样式 6篇
  • Kotlin 11篇
  • Android TV 2篇
  • BUG 14篇
  • 工具类 8篇

最新评论

  • Android 高德地图API(详细步骤+源码)

    初学者-Study: 用真机

  • Android 高德地图API(新版)

    Shansense: 太牛批了,太有帮助了

  • Android 高德地图API(详细步骤+源码)

    2401_83672084: 姐妹我问题跟你一样 请问你怎么解决啦表情包表情包表情包表情包

  • Android 高德地图API(详细步骤+源码)

    2401_83672084: 有进群的小伙伴可以拉我一下吗 打不开表情包

  • Android 高德地图API(详细步骤+源码)

    2401_83672084: 你好博主 用你的代码去运行后没有出现错误 但是在虚拟机上运行地图是黑屏的 然后查看日志说是鉴权错误 叫我去官网搜索service not exist 但是搜索过了没有这样的问题 检查过key、SHA1和包名的对应关系了 so包导入也重复检查过了没问题 究竟是为什么黑屏了表情包表情包表情包表情包表情包表情包表情包表情包表情包

大家在看

  • 光照药物稳定性试验箱百科
  • 用Python编写网页自动答题工具,满分轻松到手,你就是全班最靓的仔!
  • 【会议征稿, IEEE出版】第七届计算机信息科学与应用技术国际学术会议(CISAT 2024, 7/12-14) 948
  • 奇偶数组求解
  • 用python爬豆瓣电影TOP250排行榜!

最新文章

  • Android 高德地图API(新版)
  • Android 项目Gradle文件讲解(Groovy和Kotlin)
  • Flutter 多语言、主题切换之GetX库
2024年7篇
2023年28篇
2022年35篇
2021年55篇
2020年95篇
2019年26篇

目录

目录

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

初学者-Study

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或 充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值

哆哆女性网抉择之地寿终正寝的意思王孟起名男孩建材公司起什么名字好中央人民政府网站梦见起名字江南人民公社汽车配套东森新闻网在网上起一个名字2013年的女宝宝起名银行卡余额查询红衣小女孩2男孩子起名好听的叶子起名打分囚犯2014武家栋梁华尔街之狼爆刘继芬名字怎么起的中信银行理财产品公司起名 推荐给未出生的宝宝起名字曾姓起名四个字装修公司起什么名字好?给男孩女儿起名字大全兽人战士名字带水女孩的起名字大全办公室恋情姓马起名字英雄无敌系列淀粉肠小王子日销售额涨超10倍罗斯否认插足凯特王妃婚姻不负春光新的一天从800个哈欠开始有个姐真把千机伞做出来了国产伟哥去年销售近13亿充个话费竟沦为间接洗钱工具重庆警方辟谣“男子杀人焚尸”男子给前妻转账 现任妻子起诉要回春分繁花正当时呼北高速交通事故已致14人死亡杨洋拄拐现身医院月嫂回应掌掴婴儿是在赶虫子男孩疑遭霸凌 家长讨说法被踢出群因自嘲式简历走红的教授更新简介网友建议重庆地铁不准乘客携带菜筐清明节放假3天调休1天郑州一火锅店爆改成麻辣烫店19岁小伙救下5人后溺亡 多方发声两大学生合买彩票中奖一人不认账张家界的山上“长”满了韩国人?单亲妈妈陷入热恋 14岁儿子报警#春分立蛋大挑战#青海通报栏杆断裂小学生跌落住进ICU代拍被何赛飞拿着魔杖追着打315晚会后胖东来又人满为患了当地回应沈阳致3死车祸车主疑毒驾武汉大学樱花即将进入盛花期张立群任西安交通大学校长为江西彩礼“减负”的“试婚人”网友洛杉矶偶遇贾玲倪萍分享减重40斤方法男孩8年未见母亲被告知被遗忘小米汽车超级工厂正式揭幕周杰伦一审败诉网易特朗普谈“凯特王妃P图照”考生莫言也上北大硕士复试名单了妈妈回应孩子在校撞护栏坠楼恒大被罚41.75亿到底怎么缴男子持台球杆殴打2名女店员被抓校方回应护栏损坏小学生课间坠楼外国人感慨凌晨的中国很安全火箭最近9战8胜1负王树国3次鞠躬告别西交大师生房客欠租失踪 房东直发愁萧美琴窜访捷克 外交部回应山西省委原副书记商黎光被逮捕阿根廷将发行1万与2万面值的纸币英国王室又一合照被质疑P图男子被猫抓伤后确诊“猫抓病”

哆哆女性网 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化