Изучать программирование под Андроид я начал не так давно. После того, как Eclips выдал мой первый Hello Word, сразу захотелось большего: возникло много планов и грандиозных идей. Одной такой идеей было написание своего Браузера. Думаю, у многих начинающих программистов возникало такое желание. Вот какие требования были мной поставлены и что получилось в итоге.

  • Программа должна открывать ссылки глобальной сети, свободно переходить по страничкам вперёд и назад;
  • Иметь возможность скачивать файлы и загружать обратно в сеть;
  • Создавать закладки и сохранять их;
  • Иметь возможность загружать ссылки, отправленные с других приложений;
  • Должна быть кнопка домашней страницы, меню с различными настройками и т.д.

В общем, полноценный браузер своими руками. Воплотим это в код.

Программа написана на основе стандартного webview, входящего в Android. В качестве стартовой страницы использую Яндекс, это дело вкуса. В качестве основного Activity будет MainActivity.

Первым делом задаём разметку xml файла -activity_main.xml. В качестве главного контейнера используем LinearLayout — в него заворачиваем ProgressBar для отображения процесса загрузки. Далее создаём ещё один контейнер LinearLayout — в него заворачиваем наш Webview и FrameLayout (его используем для растягивания воспроизводимого видео на весь экран).

Посмотреть код
LinearLayout 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"
             android:orientation="vertical"
             tools:context=".MainActivity">
             
    
    <ProgressBar
        android:id="@+id/progress1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dip"
        android:indeterminateDrawable="@drawable/spinner_png"
        android:indeterminateOnly="true"
        android:layout_gravity="center_horizontal"/>
    
    
    <LinearLayout
         android:layout_width="fill_parent"
         android:layout_height="fill_parent"
         android:orientation="vertical">
    
    <WebView
            android:id="@+id/web_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
   
    <FrameLayout
            android:id="@+id/fullscreen_container"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/black"
            android:visibility="gone"/>
    
    
     </LinearLayout>

</LinearLayout>


Начнём писать код в MainActivity

Полный код MainActivity.

Посмотреть полный код
import java.io.File;

import android.R.menu;
import android.annotation.SuppressLint;
import android.app.ActionBar;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.DownloadManager;
import android.app.DownloadManager.Request;
import android.app.KeyguardManager;
import android.app.SearchManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Parcelable;
import android.os.PowerManager;
import android.preference.PreferenceManager;
import android.provider.MediaStore;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.webkit.ConsoleMessage;
import android.webkit.DownloadListener;
import android.webkit.ValueCallback;
import android.webkit.WebBackForwardList;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.FrameLayout;
import android.widget.SearchView;
import android.widget.Toast;
import android.graphics.Bitmap;
import android.webkit.URLUtil;

public class MainActivity extends Activity {


	//Логическая переменная для статуса соединения
	 Boolean isInternetPresent = false;
	 ConnectionDetector cd;
	
	
    private WebChromeClient.CustomViewCallback mFullscreenViewCallback;
    private FrameLayout mFullScreenContainer;
    private View mFullScreenView;
    private WebView mWebView;
    
    String urload;
    
    int cache = 1;
    
    SharedPreferences sPref;
    
    final Activity activity = this;
    public Uri imageUri;

    private static final int FILECHOOSER_RESULTCODE = 2888;
    private ValueCallback<Uri> mUploadMessage;
    private Uri mCapturedImageURI = null;



    private DownloadManager downloadManager;
   
    
    

    @Override
    protected  void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
       
        
        
        
      //Создаем пример класса connection detector:
        cd = new ConnectionDetector(getApplicationContext());

      
        
        // создаём кнопу home
        final ActionBar actionBar = getActionBar();
        actionBar.setHomeButtonEnabled(true);
        actionBar.setDisplayHomeAsUpEnabled(true);
        
        
        
        //  ловим intent что файл загружен и оповещаем
        BroadcastReceiver receiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
                    loadEnd();

                }
            }
        };

        //  ловим intent что файл загружен
        registerReceiver(receiver, new IntentFilter(
                DownloadManager.ACTION_DOWNLOAD_COMPLETE));
       

        mWebView = (WebView) findViewById(R.id.web_view);
        mFullScreenContainer = (FrameLayout) findViewById(R.id.fullscreen_container);
        mWebView.setWebChromeClient(mWebChromeClient);
      
        mWebView.loadUrl("http://yandex.ru");
        
        handleIntent(getIntent());
        
        class HelloWebViewClient extends WebViewClient {
        	
        	 @Override
             public  void onPageStarted(WebView view, String url, Bitmap favicon) {

                 super.onPageStarted(view, url, favicon);
                 findViewById(R.id.progress1).setVisibility(View.VISIBLE);
                 setTitle(url);
                 urload=mWebView.getUrl();
                 ConnectingToInternet ();
             }

             @Override
             public boolean shouldOverrideUrlLoading(WebView view, String url) {
                


                 view.loadUrl(url);
                 
                 // запускаем ссылки на маркет 
                 Uri uri = Uri.parse(url);


                 if (uri.getScheme().equals("market")) {
                     Intent i = new Intent(android.content.Intent.ACTION_VIEW);
                     i.setData(uri);
                     i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                     startActivity(i);

                     mWebView.canGoBack();
                     {
                         mWebView.goBack();
                     }
                 }
                               // запускаем email
                 if (uri.getScheme().equals("mailto")) {
                     Intent i = new Intent(android.content.Intent.ACTION_SEND);
                     i.setType("text/html");
                     i.putExtra(Intent.EXTRA_SUBJECT, "Введите тему");
                     i.putExtra(Intent.EXTRA_TEXT, "Введите текст");
                     i.putExtra(Intent.EXTRA_EMAIL, new String[]{url});
                     startActivity(i);
                     mWebView.canGoBack();
                     {
                         mWebView.goBack();
                     }

                }
                            // запускаем звонилку 
                 if (uri.getScheme().equals("tel")) {
                     Intent i = new Intent(android.content.Intent.ACTION_DIAL);
                     i.setData(uri);
                     startActivity(i);

                     mWebView.canGoBack();
                     {
                         mWebView.goBack();
                     }
                 }
                            // запускаем лoкцию 
                 if (uri.getScheme().equals("geo")) {
                     Intent i = new Intent(android.content.Intent.ACTION_VIEW);
                     i.setData(uri);
                     startActivity(i);

                     mWebView.canGoBack();
                     {
                         mWebView.goBack();
                     }
                 }

                 
             return true;
             }
             
             
             @Override
             public void onPageFinished(WebView view, String url) {
                 findViewById(R.id.progress1).setVisibility(View.GONE);

             }

             @Override
             public void onReceivedError(WebView view, int errorCode,
                                         String description, String failingUrl) {

            	 ConnectingToInternet ();
                 mWebView.loadUrl("file:///android_asset/error.png");

             }
           
        }
        mWebView.setWebViewClient(new HelloWebViewClient());
        
        
      // загрузка файлов на устройство
        
        mWebView.setDownloadListener(new DownloadListener() {
            @Override
            public void onDownloadStart(  final String url, String userAgent, String contentDisposition,  String mimetype, long contentLength) {

            	final String fileName = URLUtil.guessFileName(url, contentDisposition, mimetype);
            	
                final AlertDialog.Builder downloadDialog = new AlertDialog.Builder(MainActivity.this);

                downloadDialog.setTitle("Менеджер загрузок");
                downloadDialog.setMessage("Загрузить этот файл в папку Donwload ?" + '\n' + mimetype + '\n' + url);
                downloadDialog.setPositiveButton("Да", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialogInterface, int i) {


                    	doDownload( url, fileName);


                        dialogInterface.dismiss();
                    }
                });
                downloadDialog.setNegativeButton("Нет", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialogInterface, int i) {
                    }
                });

                downloadDialog.show();


            }


        });

    }
    // ****************************************
    //*****************************************
    //*****************************************
    
    public void  ConnectingToInternet (){
    	

        //Получаем статус Интернет соединения
          isInternetPresent = cd.ConnectingToInternet();
          
        //Проверяем Интернет статус:
          if (isInternetPresent) {
          //Интернет соединение есть
          //делаем HTTP запросы:
         
          } else {
          //Интернет соединения нет
          	Toast.makeText(this, " Интернет отвалился !!!", Toast.LENGTH_SHORT).show();
          }
    	
    }
    
    
    
    @SuppressLint("SetJavaScriptEnabled")
	@Override           // настройки
    public void onResume(){
        super.onResume();
        SharedPreferences sPref =
                PreferenceManager.getDefaultSharedPreferences(this);

        if (sPref.getBoolean("img", false)) {
           
            mWebView.getSettings().setLoadsImagesAutomatically(false);
        } else {
            
            mWebView.getSettings().setLoadsImagesAutomatically(true);
        }
        
        if (sPref.getBoolean("js", false)) {
            
            mWebView.getSettings().setJavaScriptEnabled(false);
        } else {
            
        	mWebView.getSettings().setJavaScriptEnabled(true);
        }

        if (sPref.getBoolean("cache", false)) {
            cache = 2;
        } else {
            cache = 1;
        }

    }
    // пишем закладку
    
    public void saveBm(String urlPage1, String urlTitle1) {

        Intent intent = new Intent(this, SaveBmActivity.class);
        intent.putExtra("urlTitle", urlTitle1);
        intent.putExtra("urlPage", urlPage1);

        startActivity(intent);
    }
    
    
    public void pref() {    //  настройки
        Intent intent = new Intent(this, PreferencesActivity.class);
        startActivity(intent);
    }
    
    // чистим кэш и историю
private void clCache(){
	clearCache(activity);
        mWebView.clearCache(true);
      
        mWebView.clearHistory();

        Toast.makeText(this, "Кеш и История очищены", Toast.LENGTH_SHORT).show();
    }

    @Override
    protected void onUserLeaveHint() {
        super.onUserLeaveHint();


    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {// кнопка назад
        if ((keyCode == KeyEvent.KEYCODE_BACK)) {

        	 mWebView.canGoBack();
             {
                 mWebView.goBack();
             }
                    return true;
            }
        return super.onKeyDown(keyCode, event);
       
       
    }
    
    // ловим url запустившей программы
    
    private boolean handleIntent(Intent intent) {
       
        String action = intent.getAction();

        if (Intent.ACTION_VIEW.equals(action)) {

            String url = intent.getDataString();
            Toast.makeText(this, url, Toast.LENGTH_SHORT).show();

            mWebView.loadUrl(url);// грузим страницу

            return true;
        }


        return false;
    }
    
    // менеджер загрузки

    private void doDownload(String url,String fileName) {
        Uri uriOriginal = Uri.parse(url);

        try {

        	
			
			Toast.makeText(MainActivity.this, "Downloading " + fileName, Toast.LENGTH_LONG).show();
			
			Request request = new DownloadManager.Request(Uri.parse(url));
			request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
			
			final DownloadManager dm = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
			dm.enqueue(request);


        } catch (Exception e) {

            Toast.makeText(this, "Ошибка", Toast.LENGTH_SHORT).show();
            Log.e("", "Problem downloading: " + uriOriginal, e);

        }


    }
    
        // тянем видео на весь экран
    private final WebChromeClient mWebChromeClient = new WebChromeClient() {
        @Override
        @SuppressWarnings("deprecation")
        public void onShowCustomView(View view, int requestedOrientation, CustomViewCallback callback) {
            onShowCustomView(view, callback);
        }

        @Override
        public void onShowCustomView(View view, CustomViewCallback callback) {
            if (mFullScreenView != null) {
                callback.onCustomViewHidden();
                return;
            }

            mFullScreenView = view;
            mWebView.setVisibility(View.GONE);

            mFullScreenContainer.setVisibility(View.VISIBLE);
            mFullScreenContainer.addView(view);
            mFullscreenViewCallback = callback;
           
        }

        @Override
        public void onHideCustomView() {
            super.onHideCustomView();
            if (mFullScreenView == null) {
                return;
            }
            mWebView.setVisibility(View.VISIBLE);
            mFullScreenView.setVisibility(View.GONE);
            mFullScreenContainer.setVisibility(View.GONE);
            mFullScreenContainer.removeView(mFullScreenView);
            mFullscreenViewCallback.onCustomViewHidden();
            mFullScreenView = null;
        }
    
    // ********************************************* грузим файлы в сеть
    // openFileChooser for Android 3.0+
    public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {

        // Сообщение об обновлении
        mUploadMessage = uploadMsg;

        try {

            // Создать AndroidExampleFolder в sdcard

            File imageStorageDir = new File(
                    Environment.getExternalStoragePublicDirectory(
                            Environment.DIRECTORY_PICTURES)
                    , "AndroidExampleFolder");

            if (!imageStorageDir.exists()) {
                // Создать AndroidExampleFolder в sdcard
                imageStorageDir.mkdirs();
            }

            // Создать камеру захваченное изображение путь к файлу и имя
            File file = new File(
                    imageStorageDir + File.separator + "IMG_"
                            + String.valueOf(System.currentTimeMillis())
                            + ".jpg");

            mCapturedImageURI = Uri.fromFile(file);

            // Камера захвата изображения  intent
            final Intent captureIntent = new Intent(
                    MediaStore.ACTION_IMAGE_CAPTURE);

            captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, mCapturedImageURI);

            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("image/*");

            // Создать файл селектора intent
            Intent chooserIntent = Intent.createChooser(i, "Image Chooser");

            // Установить камеры намерении выбора файлов
            chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS
                    , new Parcelable[]{captureIntent});

            // На выбор изображения обхода метода onactivityresult вызов метода активности
            startActivityForResult(chooserIntent, FILECHOOSER_RESULTCODE);

        } catch (Exception e) {
            Toast.makeText(getBaseContext(), "Exception:" + e,
                    Toast.LENGTH_LONG).show();
        }

    }

    // openFileChooser for Android < 3.0
    @SuppressWarnings("unused")
	public void openFileChooser(ValueCallback<Uri> uploadMsg) {
        openFileChooser(uploadMsg, "");
    }

    //
    @SuppressWarnings("unused")
	public void openFileChooser(ValueCallback<Uri> uploadMsg,
                                String acceptType,
                                String capture) {

        openFileChooser(uploadMsg, acceptType);
    }


  

    public boolean onConsoleMessage(ConsoleMessage cm) {

        onConsoleMessage(cm.message(), cm.lineNumber(), cm.sourceId());
        return true;
    }

    public void onConsoleMessage(String message, int lineNumber, String sourceID) {
        //Log.d("androidruntime", "Show console messages, Used for debugging: " + message);

    }; 
    };// End setWebChromeClient

    // Получаем результат


	@SuppressWarnings("unused")
	private Object data;    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data)
   
        {
		if (data == null) {
            return;
        }
		String urlPage2 = data.getStringExtra("urlPage2");
        mWebView.loadUrl(urlPage2);

        if (requestCode == FILECHOOSER_RESULTCODE) {

            if (null == this.mUploadMessage) {
                return;

            }

            Uri result = null;

            try {
                if (resultCode != RESULT_OK) {

                    result = null;

                } else {

                    // извлечь из собственной переменной, если намерение состоит в нуль
                    result = data == null ? mCapturedImageURI : data.getData();
                }
            } catch (Exception e) {
                Toast.makeText(getApplicationContext(), "activity :" + e,
                        Toast.LENGTH_LONG).show();
            }

            mUploadMessage.onReceiveValue(result);
            mUploadMessage = null;
            
            
            
            
        }

    }
    //*****************************
    
    
    public void loadEnd () {
        Toast.makeText(this, "Файл Загружен в папку Donwload", Toast.LENGTH_SHORT).show();
    }
    
    
    
    //  меню

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
    // *******************************************************
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {


        switch (item.getItemId()) {
        
        
            case android.R.id.home:// кнопка home

                    mWebView.loadUrl("http://yandex.ru");
                





                return true;
            case R.id.item1:// назад
               
                    mWebView.canGoBack();
                    {
                        mWebView.goBack();
                    }
                
                return true;
            case R.id.item2:
               
              // вперёд
                    mWebView.canGoForward();
                    {

                        mWebView.goForward();
                    }
                

                return true;
            case R.id.item3:
                // перезагрузка
                    mWebView.reload();
                    {
                        mWebView.reload();
                    }
                
                 

                return true;
            case R.id.item4:// чистим кеш
            	 mWebView.clearCache(true);
            	 clearCache(activity);
               

                 Toast.makeText(this, "Кэш чист.", Toast.LENGTH_SHORT).show();
               
                return true;
            case R.id.item5:
            	 mWebView.clearHistory();// чистим историю
            	 

                 Toast.makeText(this, "История чиста.", Toast.LENGTH_SHORT).show();
                return true;
            case R.id.item6:
               
            	saveBm(mWebView.getUrl(),  mWebView.getTitle());// пишим закладку

                return true;
            case R.id.item7:// панель закладок

            	 Intent intent1 = new Intent(this, SaveBmActivity.class);
                 startActivityForResult(intent1, 1);

                return true;
            case R.id.item8:

// стоп загрузка
               
                mWebView.stopLoading();


                return true;
            case R.id.item9:
               
            	pref();// настройки
                return true;
            case R.id.item10:
            	
  // пока пусто
                return true;
            case R.id.item11:// выход
                
            	 if (cache == 2) {
                     clCache();
                 }

            	 finish();
            	 

                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }
    @SuppressWarnings("deprecation")
	@Override
    public void onDestroy() {
        super.onDestroy();
        mWebView.stopLoading();
        mWebView.clearCache(true);
        mWebView.clearView();
        mWebView.freeMemory();
        mWebView.destroy();
        mWebView = null;
        
    }
    // чистим кеш
    void clearCache(Context context)
    {
        clearCacheFolder(context.getCacheDir());
    }

    void clearCacheFolder(final File dir) 
    {      
        if (dir!= null && dir.isDirectory()) 
        {         
            try 
            {             
                for (File child:dir.listFiles()) 
                {                  
                   //рекурсивно чистим сначала каталоги                 
                    if (child.isDirectory()) 
                        clearCacheFolder(child);
                    else //потом собственно файлы
                        child.delete(); 
                }         
            }         
            catch(Exception e) 
            {             
                      
            }     
        }     
    }
}



Проект можно скачать отсюда.

Комментарии (16)


  1. Assada
    28.01.2016 17:57
    +20

    Вы издеваетесь?
    Набор кода в спойлерах практически из документации, ссылка на сомнительный zip? Это статья для топ IT ресурса рунета?


  1. nizulko
    28.01.2016 18:00

    "// ловим url запустившей программы"

    Сейчас, главное, с «таким» минусов не наловить…


  1. Terranz
    28.01.2016 18:16
    +14

    >android.webkit.WebView
    >свой браузер
    вы издеваетесь или это замкнулась спираль тех, кто кидал на форму в делфи 5 компонент TWebBrowser с полем ввода да тремя кнопками и называл это «свой браузер»?


  1. teamfighter
    28.01.2016 18:16
    -4

    Сова


    1. Keyten
      28.01.2016 20:35

      Добавлю


      1. teamfighter
        29.01.2016 14:56

        Видимо, сова уже не комильфо =)


  1. trilodi
    28.01.2016 18:31
    +3

    Юмора ради, забыли упомянуть про:
    <uses-permission android:name=«android.permission.INTERNET»></uses-permission>


  1. firexel
    28.01.2016 18:47
    +6

    Не первый раз уже вижу пост от школьника/студента, который который показывается несколько часов пока его сольют и скроют в черновики. Мне кажется, что для сообществу, состоящему из инженеров разных мастей должно быть по плечу решить подобную системную проблему. Сам, когда получал аккаунт через песочницу 4 года назад, написал очень похожую статью, правда ее не слили.

    А давайте представим, что на хабре есть специальный раздел, где вот такие вот совсем зеленые ребята могут выкладывать свои поделки и получать действительно полезные комментарии от специалистов и профессионалов, а не эти стенания о том, что это топ-1 IT ресурс страны? И вот этот вот пост выложен в этом гипотетическом разделе. Вот, например, мои замечания:

    • Слишком громкий заголовок статьи. Проект на 1 класс, использующий стандартный системный компонент не может считаться браузером
    • Метод донесения информации статьи. Было бы неплохо услышать про процесс реализации, даже вот такого неказистого проекта. Просто вываленный код неинтересно смотреть
    • Метод донесения исходного кода. В 2016-м году никто не будет смотреть код из архивов в дропбоксе. Нужно выложить на публичный хост, например github.com
    • Стиль кода и реализация. Тут все понятно — автору нужно еще многому научиться. Обязательны к изучению такие книги как «Чистый код» Мартина, «Филисофия Java» Эккеля, и fundamentals из документации Android.


    1. bigfatbrowncat
      28.01.2016 20:46
      +4

      А давайте представим, что на хабре есть специальный раздел, где вот такие вот совсем зеленые ребята могут выкладывать свои поделки и получать действительно полезные комментарии от специалистов и профессионалов


      А давайте представим себе, что мы уже находимся в этом «специальном разделе». Я вполне допускаю, что на хабре есть много специалистов такого уровня, что для них всё, что напишу и я, и вы — это вот такой вот «свой браузер».

      Хотя, конечно, автору было бы неплохо немного скромнее формулировать. Вместо «пишем браузер» лучше назвать статью «учимся использовать WebView в Android». Больше никаких серьезных претензий к статье нет.

      С вашими рекомендациями согласен.


    1. lazexe
      29.01.2016 10:48

      Абсолютно согласен, и хотелось бы подробнее расписать что именно не так в коде:
      0. А отступы?
      1. Зачем столько пустых линий? Код не стал читабельнее.
      2. Обоснованные именна методов. Это что вообще такое?))

      public void pref() { // настройки
      Intent intent = new Intent(this, PreferencesActivity.class);
      startActivity(intent);
      }

      3. Ах да, и скобки {} лучше бы были все в одном стиле.
      Коментарии сверху отностятся больше к «Чистому коду» Р. Мартина.

      А теперь по Android:
      0. Смена ориентации экрана? Нет, не слышал.
      1. Почему строки/сообщения не находятся в strings.xml?
      2. Зачем это? Аннотация говорит сама за себя))
      @SuppressWarnings(«unused»)
      private Object data;

      3. И много других.
      Да, браузер слабоват. + я уверен на 99%, что потоковое видео здесь не будет воспроизводиться так как нужно(проверять откровенно поленился).


  1. pfemidi
    28.01.2016 19:12

    Чёрт! Это реально достойно хаба «Разработка под Android»! Такие хитрые твики и недокументированные подходы описаны… Ух!


  1. SpyceR
    29.01.2016 00:18
    +2

    Очень нравятся полезные комментарии к коду. Только вот слишком мало их. На каждую строчку бы.


    1. orex5
      29.01.2016 00:43
      -1

      Согласен с Вами, моё упущение, нужно подробнее было коментировать


  1. zagayevskiy
    29.01.2016 01:57
    +4

    Когда, говорите, вам «Eclips» выдал хэллоу ворлд? Android studio, чтоб всё как у людей.

    Текст пишите всегда в ресурсах.

    Откажитесь от God-класса.

    Уберите это с хабра.


  1. Suvitruf
    29.01.2016 03:10
    +6

    Меня больше поражает не сама статья, а люди, которые подобные статьи из песочницы пропускают…


    1. IRainman
      29.01.2016 15:23
      +3

      Приглашен:
      28 января 2016 в 17:51 по приглашению НЛО

      P.S. я правда чувствую, что сейчас сам окажусь на месте автора ибо «критика власти запрещена», но всё же это факт.