Como eu analisei a programação

Oi Habr!

Caro leitor! Se você está interessado na análise de html e no desenvolvimento do Android, este artigo é para você. Espero que você encontre muitas coisas interessantes e úteis. Nele, quero compartilhar minha experiência neste campo.

Descrição do problema


Um pouco sobre mim. Sou estudante do terceiro ano do ITA SFU. Como todos os alunos, preciso olhar para o horário das aulas todos os dias. E preciso conhecer a programação não apenas no dia seguinte, mas também com uma ou duas semanas de antecedência.

Parece, por que não salvar o cronograma e usá-lo? Infelizmente, existem várias razões que impedem isso, a saber:

  • A programação para uma semana pode ser muito diferente da programação para outra
  • A programação não é constante e pode mudar

Obviamente, existe um site com uma programação, mas não é muito conveniente, pois apenas exibe uma tabela bruta com uma programação por 20 semanas. O aluno tem que virar uma página grande, procurando uma programação para o dia desejado. Além disso, no modo offline, a programação fica indisponível.
Decidi fazer um pequeno aplicativo que poderia analisar o site com a programação do meu instituto e teria o seguinte conjunto de guloseimas:

  • Exibição: número da semana atual, data, dia da semana e programação para esse dia
  • A capacidade de rolar a programação com os botões "voltar" e "próximo"
  • Se não houver Internet, mostre a versão offline mais recente da programação

Prossiga para a execução


Então, arregaçando as mangas, comecei a trabalhar. Você precisa começar pequeno. Ou seja, da edição do arquivo de manifesto. Vale lembrar que nosso aplicativo funcionará com a Internet e é muito importante obtermos a permissão apropriada:

Arquivo de manifesto
Vá para manifestos-> AndroidManifest.xml. Adicione permissão. O resultado é algo como isto:

<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.myapplication"> <uses-permission android:name="android.permission.INTERNET" /> ... </manifest> 

Agora vamos para a interface. Por enquanto, vamos nos concentrar na funcionalidade e não abusar de widgets. Portanto, coloquei apenas quatro widgets: título, caixa de texto e botões: indo e voltando.

Marcação de atividade
 <?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"> <TextView android:id="@+id/WeekNumber" android:layout_width="match_parent" android:layout_height="wrap_content" android:text=" " app:layout_constraintTop_toTopOf="parent" /> <EditText android:id="@+id/timetable" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginBottom="100dp" android:ems="10" android:inputType="textMultiLine" android:text="" app:layout_constraintTop_toBottomOf="@+id/WeekNumber" tools:layout_editor_absoluteX="0dp" /> <Button android:id="@+id/next" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="" app:layout_constraintBottom_toBottomOf="parent" tools:layout_editor_absoluteX="0dp"></Button> <Button android:id="@+id/down" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="" app:layout_constraintBottom_toTopOf="@+id/next" tools:layout_editor_absoluteX="0dp"></Button> </androidx.constraintlayout.widget.ConstraintLayout> 

Agora vamos começar a analisar. É aqui que o maravilhoso analisador de código aberto Jsoup nos ajuda. Eu imediatamente rejeitei a opção usando o WebView, pois achei esse método extremamente inconveniente. Além disso, eu realmente não queria usar um widget extra, sem o qual você pode fazer facilmente.

Conexão Jsoup
Adicione a dependência ao build.gradle:
 implementation 'org.jsoup:jsoup:1.11.1' 


Não esqueça que trabalhar com a web para Android é uma tarefa difícil. Para impedir que o aplicativo seja interrompido, você precisa trabalhar com a Web localizada fora do fluxo da UI. Portanto, usaremos a classe AsyncTask. Vamos colocar a funcionalidade básica nele e depois transferir os dados para o fluxo da interface do usuário.

Para aqueles que não estão familiarizados com o AsyncTask, quero dizer que essa classe deve estar localizada dentro da classe da sua atividade. A classe em si é mostrada abaixo.

Código de atividade com a classe AsyncTask
 package com.example.myapplication; import androidx.appcompat.app.AppCompatActivity; import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; public class MainActivity extends AppCompatActivity { public boolean offline; public String request; public String WeekNumber; public int count; // public TextView weeknumber; public EditText timetable; public Button next; public Button down; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); offline = false;//     count = 0;//      weeknumber = findViewById(R.id.WeekNumber); timetable = findViewById(R.id.timetable); next = findViewById(R.id.next); down = findViewById(R.id.down); getting AsyncTask = new getting(); AsyncTask.execute(); } class getting extends AsyncTask<String, String, String> { @Override protected void onPreExecute() { super.onPreExecute(); //         } @Override protected String doInBackground(String... params) { /*             html  */ String answer = "";//         .     String url = "https://ictis.sfedu.ru/rasp/HTML/82.htm";//     Document document = null; try { document = Jsoup.connect(url).get();//     answer = document.body().html();//     body  } catch (IOException e) { //   ,   ,     //    answer    txt  try { BufferedReader read = new BufferedReader(new InputStreamReader(openFileInput("timetable.txt"))); String str = ""; while ((str = read.readLine())!=null){ answer +=str; } read.close(); offline = true;//    } catch (FileNotFoundException ex) { //     ,    answer  answer = ""; ex.printStackTrace(); } catch (IOException ex) { ex.printStackTrace(); } } //    html // html      nolessone //   answer = answer.replace("","") .replace("","") .replace("<br>","br") .replace("<font face=\"Arial\" size=\"1\"></font><p align=\"CENTER\"><font face=\"Arial\" size=\"1\"></font>","nolessone")// ""    nolessone .replace(" ",""); return Jsoup.parse(answer).text();//      answer    UI- } @Override protected void onPostExecute(String result) { super.onPostExecute(result); /*            */ request = "";//   String temp = result.toString();//   //  ,   timetable.txt,        try { BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(openFileOutput("timetable.txt",MODE_PRIVATE))); writer.write(temp); writer.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } boolean start = false; for(String str:temp.split(": ")){ if(start) { //      newweek    request request += "newweek"+str.split("")[0] + "\n"; } start = true; } //      newday,     request = request.replace("","newday").replace("","newday") .replace("","newday").replace("","newday") .replace("","newday").replace("","newday"); /*    count = 0,       count = -1,    count = 1,    . */ Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.DAY_OF_YEAR,count); Date dayformat = calendar.getTime(); SimpleDateFormat format = new SimpleDateFormat("dd MMMM"); //  timetable.setText(request); if(offline && !temp.equals("")){ // ,      Toast.makeText(getApplicationContext(),"   !",Toast.LENGTH_LONG).show(); } //    ,    if(temp.equals("")){ Toast.makeText(getApplicationContext()," !",Toast.LENGTH_LONG).show(); } } } } 


Como resultado, obtemos os dados neste formulário:

Programação de uma semana


Vamos analisar os métodos que usamos:

Crie um elemento do tipo Documento

 Document document = null; 

Nós temos a página

 document = Jsoup.connect(url).get(); 

Agora obtemos o conteúdo da tag body

 answer = document.body().html(); 

O Jsoup também pode recuperar o conteúdo de outras tags principais. Por exemplo, você pode obter o título da página usando o método title () etc. Método Html () Retorna o código html, e text () é texto sem formatação sem tags html.

Após receber o código html, você pode convertê-lo em texto sem formatação, removendo todas as tags. Isso pode ser feito usando parse (htmlcode) .text ():

 return Jsoup.parse(answer).text(); 

Eu gostaria de compartilhar alguns métodos Jsoup mais úteis que não foram usados:

 Element link = document.select("tag");//    String url = link.attr("attribute"); //    

A imagem no spoiler acima é um exemplo de programação de uma semana. De fato, 20 dessas semanas serão devolvidas a nós. Agora, nossa tarefa é encontrar hoje neste conjunto de dados e exibi-lo.

Trazendo à mente


Então o que nós temos? Aprendemos como converter o código html de uma página em uma string que pode ser facilmente analisada. Isso pode ser feito facilmente usando os métodos de string .split () e .replace ().

Em geral, o algoritmo terá esta aparência.

Primeiro, obtemos a data desejada no Android. Então fazemos dois ciclos, um aninhado no outro. O primeiro ciclo percorre as semanas, o segundo, que está dentro, percorre os dias da semana. Se a data do dia coincidir com a data recebida do Android, exibiremos a programação deste dia na caixa de texto. No entanto, todos podem escrever esse algoritmo à sua maneira. Anexei minha versão de sua implementação.

Código completo da atividade
 package com.example.myapplication; import androidx.appcompat.app.AppCompatActivity; import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; public class MainActivity extends AppCompatActivity { public boolean offline; public String request; public String WeekNumber; public int count; // public TextView weeknumber; public EditText timetable; public Button next; public Button down; public void formating(String day){ String DayTimetable = ""; String[] weeks = request.split("newweek"); String DayData = "";//      /*                 less      tich       aud  ,      ( -230) */ String less[] = new String[7]; String tich[] = new String[7]; String aud[] = new String[7]; for(String thisweek:weeks){//   if(thisweek.indexOf(day) != -1) {//        ... WeekNumber = thisweek.split(" ")[0];//   for(String thisday:thisweek.split("newday")){//      if(thisday.indexOf(day) != -1) {//       , ... // ,       newless //      . . .  . thisday = thisday.replace("no","newless") .replace(".","newless.") .replace(".","newless.") .replace(".","newless."); int i = 0; for(String thislessone:thisday.split("newless")) {//      if(i != 0) { String[] ScienceInformation = thislessone.replace("br ","").split("br"); String science = ScienceInformation[0]; science = science.replace("lessone",""); String ticher = ""; if(ScienceInformation.length > 1) ticher = ScienceInformation[1]; DayTimetable += i + "-:  - " + science+"\n"+ticher+"\n\n"; ticher = ticher.replace("-","@-").replace("-","@-") .replace("-","@-").replace("-","@-") .replace("-","@-").replace("-","@-") .replace("-","@-").replace("K-","@K-"); String Auditory; if(ticher.split("@").length == 2){ Auditory = ": "+ticher.split("@")[1]; }else Auditory = ": ";//     ticher = ticher.split("@")[0]; if(ticher.length() >0){ ticher = ": "+ticher; }else{ ticher = ""; } if(i==1){ less[i-1] = "1- (8:00-9:35) "+science; } if(i==2){ less[i-1] = "2- (9:50-11:25) "+science; } if(i==3){ less[i-1] = "3- (11:55-13:30) "+science; } if(i==4){ less[i-1] = "4- (13:45-15:20) "+science; } if(i==5){ less[i-1] = "5- (15:50-17:25) "+science; } if(i==6){ less[i-1] = "6- (17:40-19:15) "+science; } if(i==7){ less[i-1] = "7- (19:30-21:05) "+science; } tich[i-1] = ticher; aud[i-1] = Auditory; }else DayData = thislessone;// i=0  thislessone     i++; } } } } } timetable.setText(DayData);//  for(int i = 0; i <=6; i++){ timetable.setText(timetable.getText()+"\n"+less[i]+tich[i]+aud[i]);// ,      (   ) } weeknumber.setText(" "+ WeekNumber + " ");//   } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); offline = false;//     count = 0; weeknumber = findViewById(R.id.WeekNumber); timetable = findViewById(R.id.timetable); next = findViewById(R.id.next); down = findViewById(R.id.down); getting getting = new getting(); getting.execute(); //      next.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { count++; Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.DAY_OF_YEAR,count); Date dayformat = calendar.getTime(); SimpleDateFormat format = new SimpleDateFormat("dd MMMM"); formating(format.format(dayformat)); } }); down.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { count--; Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.DAY_OF_YEAR,count); Date dayformat = calendar.getTime(); SimpleDateFormat format = new SimpleDateFormat("dd MMMM"); formating(format.format(dayformat)); } }); } class getting extends AsyncTask<String, String, String> { @Override protected void onPreExecute() { super.onPreExecute(); //         getSupportActionBar().setTitle("..."); } @Override protected String doInBackground(String... params) { /*             html  */ String answer = "";//         .     String url = "https://ictis.sfedu.ru/rasp/HTML/82.htm";//     Document document = null; try { document = Jsoup.connect(url).get();//     answer = document.body().html();//     body  } catch (IOException e) { //   ,   ,     //    answer    txt  try { BufferedReader read = new BufferedReader(new InputStreamReader(openFileInput("timetable.txt"))); String str = ""; while ((str = read.readLine())!=null){ answer +=str; } read.close(); offline = true;//    } catch (FileNotFoundException ex) { //     ,    answer  answer = ""; ex.printStackTrace(); } catch (IOException ex) { ex.printStackTrace(); } } //    html // html      nolessone //   answer = answer.replace("","") .replace("","") .replace("<br>","br") .replace("<font face=\"Arial\" size=\"1\"></font><p align=\"CENTER\"><font face=\"Arial\" size=\"1\"></font>","nolessone") .replace(" ",""); return Jsoup.parse(answer).text();//      answer    UI- } @Override protected void onPostExecute(String result) { super.onPostExecute(result); /*            */ request = "";//   String temp = result.toString();//   //  ,   timetable.txt,        try { BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(openFileOutput("timetable.txt",MODE_PRIVATE))); writer.write(temp); writer.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } boolean start = false; for(String str:temp.split(": ")){ if(start) { //      newweek    request request += "newweek"+str.split("")[0] + "\n"; } start = true; } //      newday,     request = request.replace("","newday").replace("","newday") .replace("","newday").replace("","newday") .replace("","newday").replace("","newday"); /*    count = 0,       count = -1,    count = 1,    . */ Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.DAY_OF_YEAR,count); Date dayformat = calendar.getTime(); SimpleDateFormat format = new SimpleDateFormat("dd MMMM"); // ,      formating(format.format(dayformat)); if(offline && !temp.equals("")){ // ,      Toast.makeText(getApplicationContext(),"   !",Toast.LENGTH_LONG).show(); } //    ,    if(temp.equals("")){ Toast.makeText(getApplicationContext()," !",Toast.LENGTH_LONG).show(); } getSupportActionBar().setTitle(""); } } } 


a busca da agenda ocorre no método formating (). Ao enviar uma data para o método de entrada, obteremos uma programação para esse dia. Para que possamos implementar facilmente o código dos botões "voltar" e "próximo"

Próximo código do botão:

 count++; Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.DAY_OF_YEAR,count); Date dayformat = calendar.getTime(); SimpleDateFormat format = new SimpleDateFormat("dd MMMM"); formating(format.format(dayformat));//  formating 

Usando o Calendário, obtemos a data de hoje. Usando o método add, adicionamos o número de dias registrados na contagem à data de hoje. O código para o botão voltar será semelhante, apenas a contagem precisará diminuir o valor.

Captura de tela


Conclusão


Obviamente, você pode trabalhar no design, mas esse é outro tópico. Eu só queria compartilhar as tecnologias básicas. Nos spoilers abaixo, anexei capturas de tela com um design aprimorado. Também adicionei várias funções, por exemplo: configurações, a capacidade de selecionar um grupo de estudo, etc. O aplicativo em si pode ser visualizado um pouco mais tarde, assim que eu o lembrar.

Captura de tela do aplicativo

Source: https://habr.com/ru/post/pt483284/


All Articles