Сегодня в рубрике «как создать android application» мы с вами напишем небольшое приложение, которое сканирует и выводит на экран список Wi-Fi точек доступа в радиусе вашего устройства с указанием имени сети, типа защиты и уровня мощности сигнала.
Создаем проект в Андроид Студио. Вводим имя проекта. Шаблон для проекта выбираем Basic Activity. Все остальные настройки оставляем по умолчанию.

Откроем макет контента главного экрана. Здесь удалим ненужное текстовое поле и вместо него добавим виджет списка ListView. У нас на экране будет список, который будет заполняться информацией о точках доступа Wi-Fi, обнаруженных поблизости.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="info.fandroid.wifiscanner.MainActivity"
tools:showIn="@layout/activity_main">
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/listItem">
</ListView>
</RelativeLayout>
Поскольку стандартный вид списка нам не подходит, создадим макет пункта списка и настроим его под свои нужды.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="Имя сети" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="Тип защиты" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="Уровень сигнала" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:orientation="vertical">
<TextView
android:id="@+id/tvSSID"
android:layout_gravity="end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
tools:text="Имя сети" />
<TextView
android:id="@+id/tvSecurity"
android:maxLength="24"
android:layout_gravity="end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
tools:text="Тип защиты" />
<TextView
android:id="@+id/tvLevel"
android:layout_gravity="end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
tools:text="Уровень сигнала" />
</LinearLayout>
</RelativeLayout>
Сначала создадим файл макета с корневым элементом компоновки RelativeLayout. Добавим линейный компоновщик LinearLayout. Нам понадобятся три текстовых поля — одно с крупным текстом, и два с текстом помельче. Для этого идеально подойдут готовые виджеты, которые находятся на вкладке «Дизайн». Выберем один Large Text и два Small Text. Изменим текст, который содержат текстовые поля, на заголовки полей.
Идентификаторы этих полей нам не понадобятся — можно удалить. Эти поля будут отображать только заголовки. А для размещения информации о точках доступа создадим еще три текстовых поля — напротив уже созданных. Для этого скопируем весь линейный компоновщик с текстовыми полями и настроим его так, чтобы он отображался с правой стороны экрана. Здесь нам уже понадобятся идентификаторы, допишем их.
Также для всего макета добавим пространство имен tools, с помощью которого сделаем так, что текст полей справа будет отображаться только на предпросмотре.
А еще добавим свойство выравнивания текста по правому краю текстового поля, а также добавим ограничение длины поля «Тип защиты» — оно может вывадить длинные строки, и тест будет наезжать на другие поля.
Макет пункта списка готов, будем использовать его при построении списка.
Теперь откроем макет activity-main.xml и настроим отображение кнопки.
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 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"
android:fitsSystemWindows="true"
tools:context="info.fandroid.wifiscanner.MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<include layout="@layout/content_main" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
android:src="@drawable/accesspoint" />
</android.support.design.widget.CoordinatorLayout>
Мы будем использовать FAB для запуска сканирования точек доступа. Нужно заменить иконку на кнопке на более подходящую. Для этого я предварительно скачал иконку с сайта materialdesignicons.com. Вы можете скачать ее по ссылке (щелкните правой кнопкой мыши и выберите «Сохранить ссылку как…»). Скопируем иконку в папку drawable и заменим стандартную иконку для плавающей кнопки.
Прежде чем перейти к написанию кода приложения, добавим необходимые разрешения в манифест. Они нужны для получения доступа к списку сетей Wi-Fi, которые видит устройство.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="info.fandroid.wifiscanner">
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:configChanges="keyboardHidden|orientation|screenSize"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Начнем писать код. В основном пакете создаем класс Element.java. Этот класс будет хранить описание полей пункта списка и будет использоваться адаптером при создании списка.
public class Element {
private String title;
private String security;
private String level;
public Element(String title, String security, String level) {
this.title = title;
this.security = security;
this.level = level;
}
public String getTitle() {
return title;
}
public String getSecurity() {
return security;
}
public String getLevel() {
return level;
}
}
Здесь три строковых переменных — по количеству текстовых полей. Также создадим конструктор, инициализирующий все три поля. Комбинация клавиш Alt+Insert используется при этом. И с помощью нее же создадим геттеры для всех полей.
Всю основную реализацию напишем в классе главного экрана — MainActivity.java. Для начала создадим необходимые переменные.
private Element [] nets; private WifiManager wifiManager; private List<ScanResult> wifiList;
Затем в конце кода создадим внутренний класс адаптера, который будет заполнять список.
class AdapterElements extends ArrayAdapter<Object> {
Activity context;
public AdapterElements(Activity context) {
super(context, R.layout.items, nets);
this.context = context;
}
public View getView(int position, View convertView, ViewGroup parent){
LayoutInflater inflater = context.getLayoutInflater();
View item = inflater.inflate(R.layout.items, null);
TextView tvSsid = (TextView) item.findViewById(R.id.tvSSID);
tvSsid.setText(nets[position].getTitle());
TextView tvSecurity = (TextView)item.findViewById(R.id.tvSecurity);
tvSecurity.setText(nets[position].getSecurity());
TextView tvLevel = (TextView)item.findViewById(R.id.tvLevel);
String level = nets[position].getLevel();
try{
int i = Integer.parseInt(level);
if (i>-50){
tvLevel.setText("Высокий");
} else if (i<=-50 && i>-80){
tvLevel.setText("Средний");
} else if (i<=-80){
tvLevel.setText("Низкий");
}
} catch (NumberFormatException e){
Log.d("TAG", "Неверный формат строки");
}
return item;
}
}
Здесь для простоты мы создаем внутренний класс, более подробно о создании и настройке своего адаптера смотрите урок 54 на нашем канале, ссылка.
Объявим контекст. Также создадим конструктор, который будет принимать контекст в качестве параметра.
В конструкторе передаем контекст, макет пункта списка и массив элементов списка.
Далее пропишем реализацию заполнения пункта списка данными.
Создаем объект inflater с помощью которого будем заполнять данными макет пункта списка, находим макет по id.
Далее находим по идентификаторам каждое текстовое поле в отдельности и заполняем его данными.
Последнее поле — уровень сигнала — будем заполнять не полученными данными, а по условию. Для этого нам нужно вычислять уровень сигнала. Поскольку Wi-Fi менеджер возвращает строку, будем приводить ее к формату int и настроим условие, в зависимости от значения уровня будем выводить в текстовое поле три слова — высокий, средний и низкий уровень. Вся конструкция будет выполняться в блоке try-catch с отловом исключения NumberFormatException, чтобы в случае ошибки формата приложение не упало, а вывело нам сообщение в консоль.
Теперь напишем метод detectWifi, в котором будем считывать данные о доступных сетях, разбирать их и передавать адаптеру для наполнения списка.
public void detectWifi(){
this.wifiManager = (WifiManager)getSystemService(Context.WIFI_SERVICE);
this.wifiManager.startScan();
this.wifiList = this.wifiManager.getScanResults();
Log.d("TAG", wifiList.toString());
this.nets = new Element[wifiList.size()];
for (int i = 0; i<wifiList.size(); i++){
String item = wifiList.get(i).toString();
String[] vector_item = item.split(",");
String item_essid = vector_item[0];
String item_capabilities = vector_item[2];
String item_level = vector_item[3];
String ssid = item_essid.split(": ")[1];
String security = item_capabilities.split(": ")[1];
String level = item_level.split(":")[1];
nets[i] = new Element(ssid, security, level);
}
AdapterElements adapterElements = new AdapterElements(this);
ListView netList = (ListView) findViewById(R.id.listItem);
netList.setAdapter(adapterElements);
}
Сначала создаем Wi-Fi Manager и запускаем сканирование. Результат сканирования сохраняем в список wifilist.
Далее создаем массив элементов и начинаем разбор данных. Как я уже говорил, Wi-Fi Manager возвращает данные в строковом формате, гда различные праметры расположены по порядку и разделены запятыми. В цикле мы последовательно перебираем каждый параметр, и получаем его значение, которое отделено от имени параметра двоеточием. Нам нужны значения только трех параметров — идентификатор сети, тип защиты и уровень сигнала, заполняем ними каждый элемент массива.
В завершение создаем адаптер, находим макет списка и присваиваем адаптер списку.
Осталось только вызвать метод detectWifi в обработчике нажатия кнопки FAB.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
detectWifi();
Snackbar.make(view, "Сканирование...", Snackbar.LENGTH_SHORT)
.setAction("Action", null).show();
}
});
}
По умолчанию используемый шаблон настроен так, что при нажатии FAB выезжает снекбар с сообщением. Оставим так, но изменим текст сообщения и длительность его отображения.
Давайте запустим приложение на реальном устройстве и проверим, как оно работает.
Приложение работает, список нормально отображается.
Но есть один нюанс — при повороте экрана активити пересоздается, и список исчезает. В манифесте можно настроить активити так, чтобы оно не пересоздавалось при смене ориентации и в некоторых других распространенных случаях. В этом нам поможет атрибут configChanges с такими параметрами:
android:configChanges="keyboardHidden|orientation|screenSize"
Теперь при повороте экрана активити не пересоздается, и список не теряется.
Полный код класса MainActivity:
package ...
import android.app.Activity;
import android.content.Context;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private Element [] nets;
private WifiManager wifiManager;
private List<ScanResult> wifiList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
detectWifi();
Snackbar.make(view, "Сканирование...", Snackbar.LENGTH_SHORT)
.setAction("Action", null).show();
}
});
}
public void detectWifi(){
this.wifiManager = (WifiManager)getSystemService(Context.WIFI_SERVICE);
this.wifiManager.startScan();
this.wifiList = this.wifiManager.getScanResults();
Log.d("TAG", wifiList.toString());
this.nets = new Element[wifiList.size()];
for (int i = 0; i<wifiList.size(); i++){
String item = wifiList.get(i).toString();
String[] vector_item = item.split(",");
String item_essid = vector_item[0];
String item_capabilities = vector_item[2];
String item_level = vector_item[3];
String ssid = item_essid.split(": ")[1];
String security = item_capabilities.split(": ")[1];
String level = item_level.split(":")[1];
nets[i] = new Element(ssid, security, level);
}
AdapterElements adapterElements = new AdapterElements(this);
ListView netList = (ListView) findViewById(R.id.listItem);
netList.setAdapter(adapterElements);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
class AdapterElements extends ArrayAdapter<Object> {
Activity context;
public AdapterElements(Activity context) {
super(context, R.layout.items, nets);
this.context = context;
}
public View getView(int position, View convertView, ViewGroup parent){
LayoutInflater inflater = context.getLayoutInflater();
View item = inflater.inflate(R.layout.items, null);
TextView tvSsid = (TextView) item.findViewById(R.id.tvSSID);
tvSsid.setText(nets[position].getTitle());
TextView tvSecurity = (TextView)item.findViewById(R.id.tvSecurity);
tvSecurity.setText(nets[position].getSecurity());
TextView tvLevel = (TextView)item.findViewById(R.id.tvLevel);
String level = nets[position].getLevel();
try{
int i = Integer.parseInt(level);
if (i>-50){
tvLevel.setText("Высокий");
} else if (i<=-50 && i>-80){
tvLevel.setText("Средний");
} else if (i<=-80){
tvLevel.setText("Низкий");
}
} catch (NumberFormatException e){
Log.d("TAG", "Неверный формат строки");
}
return item;
}
}
}
Файл сборки модуля app:
apply plugin: 'com.android.application'
android {
compileSdkVersion 24
buildToolsVersion "24.0.2"
defaultConfig {
applicationId "info.fandroid.wifiscanner"
minSdkVersion 14
targetSdkVersion 24
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:24.2.1'
compile 'com.android.support:design:24.2.1'
}
Приложение нужно запускать на устройстве с версиями до Android 6.0. В 6-й версии изменили поход к работе с разрешениями — разрешения приложение запрашивает не в момент установки, а в момент использования функции, требующей наличие разрешения. Поэтому для работы этого приложения на версии Android 6.0 и выше необходимо написать проверку наличия необходимых разрешений и запроса разрешения при его отсутствии. Этим мы займемся на одном из последующих уроков, следите за новостями на нашем канале и сайте fandroid.info.

у меня заработало когда сменил контекст активити на контекст приложения в detectWifi
сделал так: this.wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
кроме того ошибки в String ssid = item_essid.split(«:»)[1]; //лишний пробел в стр 69 и 70
здравствуйте кто может отправить исходный файл проекта на почту очень нужно
какой должен быть SDK к этому проекту? 23 можно?
пожалуйста, перечислите ВСЕ что должно быть установлено в SDK.
я получаю ошибку в Main.activity.java
design.widget.FloatingActionButton;
design.widget.Snackbar;
.v7.app.AppCompatActivity;
.v7.widget.Toolbar;
R.
что у меня не установлено????
что надо установить чтобы работало?
В этом проекте использовалась 24 версия — смотрите, файл сборки добавлен в текст урока. И импорты проверьте в главном классе.
В 6-й версии изменили ПОХОД к работе с разрешениями — разрешения приложение запрашивает не в момент установки, а в момент использования функции, требующей наличие разрешения.