Test practic 1
Exemplu de colocviu rezolvat: https://ocw.cs.pub.ro/courses/eim/colocvii/colocviu01
Cerințe pentru colocviul 1
- un cont GitHub personal;
- Android SDK (imagine pentru API 36);
- Android Studio;
- un dispozitiv mobil:
- fizic (bring your own device); sau
- virtual: Genymotion, AVD.
Rezolvări
Proiectul Android Studio cu rezolvările complete este disponibil pe contul de GitHub al disciplinei.
A.1
a) Crearea depozitului GitHub
- Autentifică-te în GitHub (Sign in).
- Creează un proiect nou (New Repository).
- Configurează repository-ul:
- denumire;
- tip: public;
- modul de inițializare:
- local, cu
git init; sau - la distanță, cu
git clone(depozitul la distanță nu trebuie să fie vid în această situație);
- local, cu
- fișier README cu numele studentului;
- .gitignore (Android);
b) Clonarea depozitului
git clone https://www.github.com/perfectstudent/PracticalTest01
Poți specifica un director țintă după URL.
c) Crearea aplicației Android
Urmează indicațiile din secțiunea Crearea unei aplicații Android în Android Studio.
A.2 Interfața grafică (layout)
Fișier XML în /res/layout.
Variante de implementare
1) Un singur RelativeLayout
Aliniere orizontală:
android:layout_alignLeft/android:layout_alignRightAliniere verticală:android:layout_alignTop/android:layout_alignBottom
<!-- activity_practical_test01_main.xml -->
<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="wrap_content"
tools:context="ro.pub.cs.systems.eim.practicaltest01.PracticalTest01MainActivity">
<Button
android:id="@+id/navigate_to_secondary_activity_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:text="@string/navigate_to_secondary_activity" />
<EditText
android:id="@+id/left_edit_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:saveEnabled="false"
android:enabled="false"
android:inputType="number"
android:ems="7"
android:layout_alignParentLeft="true"
android:layout_below="@id/navigate_to_secondary_activity_button"
android:gravity="center" />
<EditText
android:id="@+id/right_edit_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:saveEnabled="false"
android:enabled="false"
android:inputType="number"
android:ems="7"
android:layout_alignParentRight="true"
android:layout_below="@id/navigate_to_secondary_activity_button"
android:gravity="center" />
<Button
android:id="@+id/left_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/left_edit_text"
android:layout_below="@id/left_edit_text"
android:layout_alignRight="@id/left_edit_text"
android:text="@string/press_me" />
<Button
android:id="@+id/right_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/right_edit_text"
android:layout_alignRight="@id/right_edit_text"
android:layout_below="@id/right_edit_text"
android:text="@string/press_me_too" />
</RelativeLayout>
2) LinearLayout-uri îmbricate
Pentru proporții:
android:layout_weight(seteazălayout_widthsaulayout_heightla0dppe direcția dorită)
<!-- activity_practical_test01_main.xml -->
<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="wrap_content"
android:orientation="vertical"
tools:context="ro.pub.cs.systems.eim.practicaltest01.PracticalTest01MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/left_edit_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:enabled="false"
android:inputType="number"
android:gravity="center" />
<EditText
android:id="@+id/right_edit_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:enabled="false"
android:inputType="number"
android:gravity="center" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:baselineAligned="false">
<ScrollView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
<Button
android:id="@+id/left_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/press_me" />
</ScrollView>
<ScrollView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
<Button
android:id="@+id/right_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/press_me_too" />
</ScrollView>
</LinearLayout>
</LinearLayout>
3) LinearLayout + GridLayout (2×2)
<!-- activity_practical_test01_main.xml -->
<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:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context="ro.pub.cs.systems.eim.practicaltest01.PracticalTest01MainActivity">
<Button
android:id="@+id/navigate_to_secondary_activity_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/navigate_to_secondary_activity"/>
<GridLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:rowCount="2"
android:columnCount="2">
<EditText
android:id="@+id/left_edit_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:saveEnabled="false"
android:enabled="false"
android:inputType="number"
android:ems="6"
android:layout_row="0"
android:layout_column="0"
android:layout_gravity="center"
android:gravity="center"/>
<EditText
android:id="@+id/right_edit_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:saveEnabled="false"
android:enabled="false"
android:inputType="number"
android:ems="6"
android:layout_row="0"
android:layout_column="1"
android:layout_gravity="center"
android:gravity="center" />
<Button
android:id="@+id/left_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_row="1"
android:layout_column="0"
android:layout_gravity="center"
android:text="@string/press_me" />
<Button
android:id="@+id/right_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_row="1"
android:layout_column="1"
android:layout_gravity="center"
android:text="@string/press_me_too" />
</GridLayout>
</LinearLayout>
Încărcarea layout-ului în activitate
// PracticalTest01MainActivity.java
public class PracticalTest01MainActivity extends AppCompatActivity {
private EditText leftEditText;
private EditText rightEditText;
private Button pressMeButton, pressMeTooButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_practical_test01_main);
leftEditText = findViewById(R.id.left_edit_text);
rightEditText = findViewById(R.id.right_edit_text);
pressMeButton = findViewById(R.id.press_me_button);
pressMeTooButton = findViewById(R.id.press_me_too_button);
leftEditText.setText(String.valueOf(0));
rightEditText.setText(String.valueOf(0));
}
}
În Android Studio, auto-import: Alt+Enter.
B.1 Tratarea evenimentelor UI
- definește o clasă ascultător (
OnClickListener); - înregistrează instanța pe controalele grafice.
public class PracticalTest01MainActivity extends AppCompatActivity {
private ButtonClickListener buttonClickListener = new ButtonClickListener();
private class ButtonClickListener implements View.OnClickListener {
@Override
public void onClick(View view) {
int leftNumberOfClicks = Integer.valueOf(leftEditText.getText().toString());
int rightNumberOfClicks = Integer.valueOf(rightEditText.getText().toString());
switch(view.getId()) {
case R.id.press_me_button:
leftNumberOfClicks++;
leftEditText.setText(String.valueOf(leftNumberOfClicks));
break;
case R.id.press_me_too_button:
rightNumberOfClicks++;
rightEditText.setText(String.valueOf(rightNumberOfClicks));
break;
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ...
pressMeButton.setOnClickListener(buttonClickListener);
pressMeTooButton.setOnClickListener(buttonClickListener);
}
}
B.2 Salvarea/restaurarea stării
a) Dezactivarea auto-save pe controale
<LinearLayout ... >
<EditText
android:id="@+id/left_edit_text"
android:saveEnabled="false"
... />
<EditText
android:id="@+id/right_edit_text"
android:saveEnabled="false"
... />
</LinearLayout>
b) onSaveInstanceState / onRestoreInstanceState
public class PracticalTest01MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ...
if (savedInstanceState != null) {
if (savedInstanceState.containsKey(Constants.LEFT_COUNT)) {
leftEditText.setText(savedInstanceState.getString(Constants.LEFT_COUNT));
} else {
leftEditText.setText(String.valueOf(0));
}
if (savedInstanceState.containsKey(Constants.RIGHT_COUNT)) {
rightEditText.setText(savedInstanceState.getString(Constants.RIGHT_COUNT));
} else {
rightEditText.setText(String.valueOf(0));
}
} else {
leftEditText.setText(String.valueOf(0));
rightEditText.setText(String.valueOf(0));
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
outState.putString(Constants.LEFT_COUNT, leftEditText.getText().toString());
outState.putString(Constants.RIGHT_COUNT, rightEditText.getText().toString());
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
if (savedInstanceState.containsKey(Constants.LEFT_COUNT)) {
leftEditText.setText(savedInstanceState.getString(Constants.LEFT_COUNT));
} else {
leftEditText.setText(String.valueOf(0));
}
if (savedInstanceState.containsKey(Constants.RIGHT_COUNT)) {
rightEditText.setText(savedInstanceState.getString(Constants.RIGHT_COUNT));
} else {
rightEditText.setText(String.valueOf(0));
}
}
}
c) Simulare distrugere/relansare activitate
- Părăsește activitatea (Menu) →
onPause()/onStop()→onSaveInstanceState(). - Terminate Application din Android Monitor.
- Redeschide activitatea din meniu →
onCreate()→onStart()→onResume()→onRestoreInstanceState().
C.1 Definirea unei activități
- în
AndroidManifest.xml(etichetă<activity>+<intent-filter>); - layout XML în
/res/layout; - clasă Java derivată din
ActivitysauAppCompatActivity.
Manifest
<!-- AndroidManifest.xml -->
<manifest ... >
<application ... >
<activity android:name=".view.PracticalTest01SecondaryActivity">
<intent-filter>
<action android:name="ro.pub.cs.systems.eim.practicaltest01.intent.action.PracticalTest01SecondaryActivity" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
</manifest>
Layout secundar
<!-- activity_practical_test01_secondary.xml -->
<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="ro.pub.cs.systems.eim.practicaltest01.PracticalTest01SecondaryActivity">
<TextView
android:id="@+id/number_of_clicks_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center">
<Button
android:id="@+id/ok_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ok" />
<Button
android:id="@+id/cancel_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/cancel" />
</LinearLayout>
</LinearLayout>
Cod activitate secundară
// PracticalTest01SecondaryActivity.java
public class PracticalTest01SecondaryActivity extends AppCompatActivity {
private TextView numberOfClicksTextView;
private Button okButton, cancelButton;
private ButtonClickListener buttonClickListener = new ButtonClickListener();
private class ButtonClickListener implements View.OnClickListener {
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.ok_button:
setResult(RESULT_OK, null);
break;
case R.id.cancel_button:
setResult(RESULT_CANCELED, null);
break;
}
finish();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_practical_test01_secondary);
numberOfClicksTextView = findViewById(R.id.number_of_clicks_text_view);
Intent intent = getIntent();
if (intent != null && intent.getExtras().containsKey(Constants.NUMBER_OF_CLICKS)) {
int numberOfClicks = intent.getIntExtra(Constants.NUMBER_OF_CLICKS, -1);
numberOfClicksTextView.setText(String.valueOf(numberOfClicks));
}
okButton = findViewById(R.id.ok_button);
okButton.setOnClickListener(buttonClickListener);
cancelButton = findViewById(R.id.cancel_button);
cancelButton.setOnClickListener(buttonClickListener);
}
}
C.2 Navigarea către activitatea secundară
Buton în layout
<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="wrap_content"
android:orientation="vertical"
tools:context="ro.pub.cs.systems.eim.practicaltest01.PracticalTest01MainActivity">
<Button
android:id="@+id/navigate_to_secondary_activity_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:text="@string/navigate_to_secondary_activity" />
</LinearLayout>
Cod în activitatea principală
public class PracticalTest01MainActivity extends AppCompatActivity {
private Button navigateToSecondaryActivityButton;
private ButtonClickListener buttonClickListener = new ButtonClickListener();
private class ButtonClickListener implements View.OnClickListener {
@Override
public void onClick(View view) {
switch(view.getId()) {
case R.id.navigate_to_secondary_activity_button:
Intent intent = new Intent(getApplicationContext(), PracticalTest01SecondaryActivity.class);
int numberOfClicks = Integer.parseInt(leftEditText.getText().toString()) +
Integer.parseInt(rightEditText.getText().toString());
intent.putExtra(Constants.NUMBER_OF_CLICKS, numberOfClicks);
startActivityForResult(intent, Constants.SECONDARY_ACTIVITY_REQUEST_CODE);
break;
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ...
navigateToSecondaryActivityButton = findViewById(R.id.navigate_to_secondary_activity_button);
navigateToSecondaryActivityButton.setOnClickListener(buttonClickListener);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
if (requestCode == Constants.SECONDARY_ACTIVITY_REQUEST_CODE) {
Toast.makeText(this, "The activity returned with result " + resultCode, Toast.LENGTH_LONG).show();
}
}
}
D.1 Serviciu Android (started service)
Manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
<application ...>
<!-- other components -->
<service
android:name=".service.PracticalTest01Service"
android:enabled="true"
android:exported="false" />
</application>
</manifest>
Implementare serviciu
// PracticalTest01Service.java
public class PracticalTest01Service extends Service {
private ProcessingThread processingThread = null;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
int firstNumber = intent.getIntExtra(Constants.FIRST_NUMBER, -1);
int secondNumber = intent.getIntExtra(Constants.SECOND_NUMBER, -1);
processingThread = new ProcessingThread(this, firstNumber, secondNumber);
processingThread.start();
return Service.START_REDELIVER_INTENT;
}
@Override
public IBinder onBind(Intent intent) { return null; }
@Override
public void onDestroy() {
processingThread.stopThread();
}
}
Thread de procesare
// ProcessingThread.java
public class ProcessingThread extends Thread {
private Context context = null;
private boolean isRunning = true;
private Random random = new Random();
private double arithmeticMean;
private double geometricMean;
public ProcessingThread(Context context, int firstNumber, int secondNumber) {
this.context = context;
arithmeticMean = (firstNumber + secondNumber) / 2.0;
geometricMean = Math.sqrt(firstNumber * secondNumber);
}
@Override
public void run() {
Log.d(Constants.PROCESSING_THREAD_TAG, "Thread has started! PID: " + Process.myPid() + " TID: " + Process.myTid());
while (isRunning) {
sendMessage();
sleep10s();
}
Log.d(Constants.PROCESSING_THREAD_TAG, "Thread has stopped!");
}
private void sendMessage() {
Intent intent = new Intent();
intent.setAction(Constants.actionTypes[random.nextInt(Constants.actionTypes.length)]);
intent.putExtra(Constants.BROADCAST_RECEIVER_EXTRA,
new Date(System.currentTimeMillis()) + " " + arithmeticMean + " " + geometricMean);
context.sendBroadcast(intent);
}
private void sleep10s() {
try { Thread.sleep(10000); }
catch (InterruptedException e) { e.printStackTrace(); }
}
public void stopThread() { isRunning = false; }
}
Pornire/Oprire serviciu din activitate
private class ButtonClickListener implements View.OnClickListener {
@Override
public void onClick(View view) {
int leftNumberOfClicks = Integer.parseInt(leftEditText.getText().toString());
int rightNumberOfClicks = Integer.parseInt(rightEditText.getText().toString());
if (leftNumberOfClicks + rightNumberOfClicks > Constants.NUMBER_OF_CLICKS_THRESHOLD
&& serviceStatus == Constants.SERVICE_STOPPED) {
Intent intent = new Intent(getApplicationContext(), PracticalTest01Service.class);
intent.putExtra(Constants.FIRST_NUMBER, leftNumberOfClicks);
intent.putExtra(Constants.SECOND_NUMBER, rightNumberOfClicks);
getApplicationContext().startService(intent);
serviceStatus = Constants.SERVICE_STARTED;
}
}
}
@Override
protected void onDestroy() {
Intent intent = new Intent(this, PracticalTest01Service.class);
stopService(intent);
super.onDestroy();
}
D.2 BroadcastReceiver
Receiver + filtrare
private MessageBroadcastReceiver messageBroadcastReceiver = new MessageBroadcastReceiver();
private class MessageBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.d(Constants.BROADCAST_RECEIVER_TAG,
intent.getStringExtra(Constants.BROADCAST_RECEIVER_EXTRA));
}
}
private IntentFilter intentFilter = new IntentFilter();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_practical_test01_main);
for (int i = 0; i < Constants.actionTypes.length; i++) {
intentFilter.addAction(Constants.actionTypes[i]);
}
}
@Override
protected void onResume() {
super.onResume();
registerReceiver(messageBroadcastReceiver, intentFilter);
}
@Override
protected void onPause() {
unregisterReceiver(messageBroadcastReceiver);
super.onPause();
}
D.3 Comenzi Git utile
# adaugă toate modificările
git add *
# commit cu mesaj
git commit -m "finished tasks for PracticalTest01"
# push pe master
git push origin master
Configurare utilizator (dacă e necesar):
git config --global user.name "Perfect Student"
git config --global user.email perfectstudent@cs.pub.ro