Сегодня в рубрике “как создать 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-й версии изменили ПОХОД к работе с разрешениями — разрешения приложение запрашивает не в момент установки, а в момент использования функции, требующей наличие разрешения.