如何在15分钟内谈论Android的主要组件

引言


本文将讨论如何向以前不熟悉Android编程的人介绍其主要组件。 有趣的是,表明一切并不像许多人想象的那样困难。 同时,只需15分钟即可完成操作,而无需对一些基本理论进行解释,每个人都可以自己读一遍,然后再提出澄清问题。


当我第一次尝试这样做时,我对自己感到不愉快。 我的“简单易懂”的解释变得无聊,在此框架内,人们拼命地试图抓住巨大的面目,并简略地讲出一切。 毋庸置疑,这样一个故事不太可能引起人们的兴趣,反而使您的对话者感到害怕,同时减少了您自己做某事的欲望,即使您以前计划中有一个小型计算器也是如此。


在互联网上发布大量有关此主题的文章已经不是什么秘密了,但是在我的情况下,叙述将略有不同:只有视觉实践,没有定义和其他细节。 也就是说,我们看起来-我们看到-我们对正在发生的事情发表评论。 在我看来,它看起来非常简单明了,代码段也很小且非常简单,可以在自己的项目中快速使用。 在我看来,这种方法提供了对经典Android工具的相当广泛的概述,并且在编写第一个应用程序时,会出现“我应该如何使用组件X”这一更具体的问题,而不是“我应该使用什么”的问题。 如果需要,有关此人的所有详细信息已经可以自己找到。


所以走吧!


学习组成部分


安装应用程序,启动它,然后...现在,MainActivity已经打开了。 问题“为什么确切”将在以后回答。


首先,让我们看一下它的来源-main_activity.xml,其中声明了所有接口元素。 它们张贴在LinearLayout中,因此在这里不太可能出现问题。


查看代码
<LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/textView" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Android Demo Application" /> <TextView android:id="@+id/textView3" android:layout_width="match_parent" android:layout_height="wrap_content" android:text=" 1" /> <Button android:id="@+id/buttonShowToast" android:layout_width="match_parent" android:layout_height="wrap_content" android:text=" Toast" /> ... </LinearLayout> 

简单的组件


吐司


查看图片


现在,进入MainActivity.java及其界面的第一个按钮-“ Show Toast”(弹出通知)。
在main_activity.xml中找到按钮的标识符,然后在MainActivity.java中转到其OnClickListener。


查看代码
 Button btn = findViewById(R.id.buttonShowToast); btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(getApplicationContext(), "This is a Toast", Toast.LENGTH_LONG).show(); } }); 

事实证明,要显示一个弹出通知,只需一行代码即可! 很好,不是吗?


与其他活动的互动


查看图片

第一次活动

第二次活动


现在,让我们尝试转到应用程序主页之外的某个地方。 例如,转到另一个这样的页面! 转到“与另一个活动的交互”-我们进入与其他控件的另一个活动。 一个应用程序中的不同活动如何在它们之间传输数据? 现在是时候讨论两种不同的机制了:值的恒定存储-shared_prefs以及startActivityForResult / onActivityResult(我不想在一开始就插入它,但是仍然要简短:如果您使用startActivityForResult从打开的活动中启动一个活动,那么在第二个活动完成之后将在第一个活动中称为onActivityResult。如果尚未清除,请不要发出警告)。
当然,请在实践中进行演示!


shared_prefs中的条目:


查看代码
 String valueToSave = "test"; SharedPreferences.Editor editor = getSharedPreferences("demoapp", MODE_PRIVATE).edit(); editor.putString("myValue", valueToSave); editor.apply(); 

从shared_prefs读取:


查看代码
 SharedPreferences prefs = getSharedPreferences("demoapp", MODE_PRIVATE); String storedValue = prefs.getString("myValue", "NOT_FOUND"); 

onActivityResult的示例-请参见应用程序源。


保存和还原设置


查看图片


既然我们提到了shared_prefs,让我们以它们结尾。 我们转到“保存和恢复设置”,在该页面中,我们将看到具有各种字段类型的典型帐户的典型卡(请注意,它们的类型仅由一个变量设置-一个开关)。 我们将这些字段的内容保存在shared_prefs中,然后将其还原。 到目前为止,还有一个单独的按钮,没有onResume-我们尚未到达它们!
保存:


查看代码
 SharedPreferences.Editor editor = getSharedPreferences(MY_PREFS_NAME, MODE_PRIVATE).edit(); EditText et = findViewById(R.id.editTextName); String name = et.getText().toString(); editor.putString("name", name); editor.apply(); 

我们恢复:


查看代码
 SharedPreferences prefs = getSharedPreferences(MY_PREFS_NAME, MODE_PRIVATE); EditText et = findViewById(R.id.editTextName); et.setText(prefs.getString("name", "")); ... 

简单菜单


查看图片


下一部分是一个简单的菜单。 我们从中了解到,其中没有什么复杂的-只需设置onCreateOptionsMenu并用my_menu.xml中的结构填充即可。


查看代码
 @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.my_menu, menu); return true; } 

弹出菜单


紧接其后-弹出菜单按钮。 即使存在设置存储器和子菜单,它也比上一个更有趣。
可以在应用程序的源代码中查看弹出菜单的代码。


音频播放器


查看图片


使用音频播放器的示例,除了播放音乐没有什么复杂的事实之外,您还可以演示seekBar与某些东西(在这种情况下,与媒体播放器当前位置)的连接。 在音量控制-也没有什么超自然的。 奖金显示错误。 使用媒体播放器打开活动,开始播放,然后按“返回”按钮。 音乐继续播放,无法停止了……问题! 如何解决-我们稍后会发现。
音频播放器的代码可以在应用程序的源代码中找到。


网页浏览器


好吧,作为第一部分的总结,我们证明了Web浏览器中也没有任何神灵。


查看图片


查看代码
 WebView view = findViewById(R.id.webView); view.setWebViewClient(new WebViewClient()); view.getSettings().setJavaScriptEnabled(true); view.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE); view.loadUrl("https://google.com"); 

下一部分已经很难了。


服务和通知


广播接收器


查看图片


回想一下我们如何将结果从一项活动传递到另一项活动。 结果以某种方式(我们还不知道如何)证明,当第二个活动关闭时,结果飞到第一个活动的onActivityResult中。 但是我们可以将结果传输到应用程序中的任何地方吗? 是的,我们可以宣布并注册一个收听者,该收听者可以从程序的任何位置收听我们的声音。 这样的侦听器称为BroadcastReceiver。


查看代码
 public BroadcastReceiver MyReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { Toast.makeText(getApplicationContext(), "Broadcast receiver: received!", Toast.LENGTH_LONG).show(); } }; 

在这里,您不能没有意图,但要有最原始的意图:就目前而言,它们已经足够我们使用的事实被发送到某个公用总线,并且根据预定的动作,无论我们身在何处,BroadcastReceiver都会听到我们的声音。


查看代码
 IntentFilter filter = new IntentFilter(); filter.addAction("MyCustomActionName"); registerReceiver(MyReceiver, filter); sendBroadcast(new Intent("MyCustomActionName")); 

目前,我们对什么是Receiver有了基本的了解,但是不清楚为什么需要它:这很正常,现在它将变得更加容易。


服务简单


查看图片


无缝地转到服务,在那里BroadcastReceiver将找到其完整的应用程序。 我建议不要立即说出Android服务是什么,而是建议自己开始进行演示。 运行服务,该服务在后台开始工作。 同时,我们将注册两个接收者:一个处于活动状态,另一个处于服务状态。
需要服务中的接收方来证明,尽管执行了后台操作,但我们始终可以从Activity中获取它。


查看代码
 public BroadcastReceiver MyServiceReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { Toast.makeText(getApplicationContext(), "Toast from Service: I hear you!", Toast.LENGTH_LONG).show(); } }; 

需要Activity中的Receiver才能在TextView中显示服务的结果。


查看代码
 public BroadcastReceiver MyPrintReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { Toast.makeText(getApplicationContext(), "pong", Toast.LENGTH_SHORT).show(); TextView tv = findViewById(R.id.textViewSimpleServiceStatus); String msg = intent.getStringExtra("msg"); tv.setText(msg); } }; 

并且,为了充分理解,当从服务向活动发送消息时,我们将显示Toast(“ ping”)。 当我们在Activity中收到消息并在TextView中实际绘制值时,我们将显示Toast(“ pong”)。
让服务的主要任务是向活动发送此类“ ping”,活动的任务是仅在其界面中显示它们。


查看代码
 Handler handler = new Handler(); runnable = new Runnable() { public void run() { if (running) { printMsg("Service is still running " + running); handler.postDelayed(runnable, 5000); //   } else { printMsg("Service exited"); } } }; handler.postDelayed(runnable, 5000); 

稍后将详细考虑处理程序操作的示例,目前这只是每5秒发送一次ping的一种方式。


现在,在启动服务之后,我们会看到Toast(“服务已创建!”)并发送乒乓通知。 并且在TextView中,来自服务的消息开始到达。


很棒,我们看到了后台执行。 现在关闭应用程序! 在最新版本的Android上,我们将看到以下内容:服务已重新启动(Toast(“服务已创建!”)将出现)并发送“ Ping”。 同时,没有“乒乓球”-毕竟,没有更多的活动可以处理它们! 几秒钟后,我的智能手机上的ping也停止了。 服务被销毁。 打开能量设置,关闭针对我们应用程序的优化,然后再次执行该过程。 现在该服务没有被破坏,甚至在程序关闭后,我们也看到稳定进入的“ ping”。 但是,当然,这里并不能保证,这样的服务不会持续很长时间。 从开发人员的角度来看,这可能很糟糕,但是让我们通过一个简单的用户的眼光来看一下:我们是否希望任何应用程序在后台如此自由,不受惩罚地吃电池? 几乎没有 然后如何在后台充分工作?


为此,您只需要将此通知用户。 这是Android的强制性要求,在有通知通道的情况下,它不仅使您能够启动常规功能,而且还可以启动Foreground服务,该服务将在后台完全运行,而不会冒被淘汰的危险。
添加到我们的服务的onCreate中:


查看代码
 String CHANNEL_ID = "my_channel_01"; NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "Channel human readable title", NotificationManager.IMPORTANCE_DEFAULT); ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel); Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID) .setContentTitle("") .setContentText("").build(); startForeground(1, notification); 

我们将使用以下命令运行它:


查看代码
 startForegroundService(new Intent(getApplicationContext(), TestServiceForeground.class)); 

我们将返回演示应用程序,将节能设置恢复为其原始状态。 现在,我们将启动前台服务。 当服务运行时关闭应用程序,我们注意到该服务不再重启也不被破坏,而是稳定地继续在后台运行,每5秒发送一次“ ping”。 同时,通知图标将挂在通知中。


查看图片


用户可以根据需要隐藏这些通知,但是在创建通知通道时您不能以编程方式隐藏它们。


浮动按钮(叠加层)


查看图片


了解服务是什么之后,您可以继续使用其最简单,最明显的用途-使用浮动按钮。 在当前的Android设备上,您不能像这样声明“叠加”渲染权限-您需要明确要求用户“在所有窗口上方”的权限。 然后,我们单击“绘制覆盖图”,然后看到一个浮动图标,该图标的后面确实有一个侦听其点击的服务。


发送通知


查看图片


在大多数Android应用程序中很有可能会派上用场的另一个重要功能是向用户发送通知。 让我们简要看一下它是如何工作的。 首先(在当前的Android上),我们需要创建一个通知通道-是的,是的,在现代应用程序中通常可以大量使用的通知通道之一。


查看代码
 private void createNotificationChannel() { try { CharSequence channelName = CHANNEL_ID; String channelDesc = "channelDesc"; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { int importance = NotificationManager.IMPORTANCE_LOW; NotificationChannel channel = new NotificationChannel(CHANNEL_ID, channelName, importance); channel.setDescription(channelDesc); NotificationManager notificationManager = getSystemService(NotificationManager.class); assert notificationManager != null; NotificationChannel currChannel = notificationManager.getNotificationChannel(CHANNEL_ID); if (currChannel == null) { notificationManager.createNotificationChannel(channel); Toast.makeText(getApplicationContext(), "channel created", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(getApplicationContext(), "channel exists", Toast.LENGTH_SHORT).show(); } } } catch (Exception e) { } } 

好了,那么您可以无限制地向其发送通知。


查看代码
 public void setNotify() { try { Intent snoozeIntent = new Intent("ActionFromNotify"); PendingIntent snoozePendingIntent = PendingIntent.getBroadcast(this, 0, snoozeIntent, 0); String title = "Start"; Intent intent = new Intent(this, MainActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); NotificationCompat.Action action = new NotificationCompat.Action.Builder(R.drawable.ic_launcher_background, title, snoozePendingIntent).build(); NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this, CHANNEL_ID) .setSmallIcon(R.drawable.ic_launcher_background) .setContentTitle("MyNotification") .setContentText("Text here") .setPriority(NotificationCompat.PRIORITY_LOW) .setContentIntent(null) .setOngoing(true) //   .setSound(null) .addAction(action); NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); int notificationId = (int) (System.currentTimeMillis() / 4); notificationManager.notify(notificationId, mBuilder.build()); } catch (Exception e) { Log.e("error", e.toString()); } } 

发送通知服务


查看图片


您会发现,直接从活动发送通知并不令人印象深刻。 我们仍然习惯了其他东西。 但是到目前为止,我们对后台执行了解多少? 只是有服务。 因此,我们不要着急制作一个简单的服务(为简单起见,甚至不包括Foreground),该服务每5秒发送一次新通知。 同意,这不仅比通过按钮发送通知更漂亮,而且印象深刻。


在看似复杂的服务(如果您以前从未遇到过)之后稍作停顿,我们将考虑四个易于理解的控件。 然后我们再次转向复杂的材料-流动。


附加组件


数据表


查看图片


让我们从数据表开始暂停。 研究源代码留给读者。


选项卡式窗口


查看图片


下一站是一个选项卡式窗口。 借助简单的TabView,您可以在一个屏幕上放置多个活动。


查看代码
 public class TabsActivity extends TabActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.tabs_activity); //  TabHost TabHost tabHost = getTabHost(); TabHost.TabSpec tabSpec; tabSpec = tabHost.newTabSpec("tag1"); tabSpec.setIndicator(""); tabSpec.setContent(new Intent(this, SaveRestorePrefsActivity.class)); tabHost.addTab(tabSpec); tabSpec = tabHost.newTabSpec("tag2"); tabSpec.setIndicator(""); tabSpec.setContent(new Intent(this, FloatingMenuActivity.class)); tabHost.addTab(tabSpec); ... } } 

显示结构对象:片段和表


结构对象的输出可以使用片段和表来完成。
这就是碎片填充的样子


查看图片


所以桌子


查看图片


活动生命周期


查看图片


逐渐结束我们的停顿,我们进入了活动的生命周期。 现在是时候发现除了onCreate之外还有许多其他方法。 首先,通过一小段代码和弹出式通知,比任何解释都有助于更好地理解它们。


查看代码
 @Override protected void onResume() { super.onResume(); Toast.makeText(getApplicationContext(), "onResume -    ", Toast.LENGTH_SHORT).show(); } @Override protected void onDestroy() { super.onDestroy(); Toast.makeText(getApplicationContext(), "onDestroy -  ", Toast.LENGTH_SHORT).show(); } @Override protected void onPause() { super.onPause(); Toast.makeText(getApplicationContext(), "onPause -      ", Toast.LENGTH_SHORT).show(); } 

这同样适用于OnTouchListener'ov和onTextChanged。


查看代码
 CheckBox cb = findViewById(R.id.checkBoxChangeExample); cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { printMsg("CheckBox - OnCheckedChangeListener: new value is checked = " + isChecked); } }); SeekBar seekBar = (SeekBar) findViewById(R.id.seekBarChangeExample); seekBar.setMax(100); seekBar.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { Integer progress = ((SeekBar)v).getProgress(); printMsg("SeekBar - OnTouchListener: new value is = " + progress.toString()); return false; } }); 

延迟,并行和定期执行


我们转向故事中最困难的部分-延迟执行和并行执行。


延迟执行:处理程序


查看图片


让我们从Handler开始潜水。 什么是处理程序的细节-一个人以后会自己阅读,我们不会剥夺他的这种乐​​趣。 但是,由于我们正在考虑它,因此重要的是要了解主要内容-它允许您执行待处理的任务,而不是并行执行。
显示出来。 让我们创建一个处理程序,向其中添加任务“在5秒内输出Toast”。 我们看到Toast被撤回,不需要睡眠(在整个程序执行过程中暂停)。


查看代码
 Handler handler = new Handler(); Runnable r = new Runnable() { public void run() { Toast.makeText(getApplicationContext(), "Delayed task executed", Toast.LENGTH_SHORT).show(); } }; handler.postDelayed(r, 5000); 

现在将循环任务添加到处理程序中:


查看代码
 Runnable r = new Runnable() { public void run() { Toast.makeText(getApplicationContext(), "Delayed task executed", Toast.LENGTH_SHORT).show(); handler.postDelayed(this, 5000); //   } }; handler.postDelayed(r, 5000); 

确保每5秒运行一次后,清除Handler


查看代码
 handler.removeCallbacksAndMessages(null); 

有待证明,来自Handler的任务执行不会并行发生。 最简单的显示方法是将它加载得很重,同时又很简单。 就像...而(真正的)不睡觉! 十秒钟,以免完全杀死应用程序。


查看代码
 Runnable r = new Runnable() { public void run() { long initTime = System.currentTimeMillis(); boolean timeElapsed = false; while(!timeElapsed){ if(System.currentTimeMillis() - initTime > 10000 ){ timeElapsed = true; //   ,   ! (  ,    ).         sleep } } } }; Toast.makeText(getApplicationContext(), "Hard Delayed task started", Toast.LENGTH_SHORT).show(); handler.postDelayed(r, 100); 

启动这样的任务,我们看到应用程序在这10秒钟内没有响应我们的点击-它正在完全忙于处理复杂的周期。 此示例得出的第二个结论是,您不能在与接口相同的线程中运行资源密集型任务。 UI线程应始终是空闲的,并且应尽快确定其功能。 明确禁止用户界面流中的某些操作:例如,如果Android尝试访问用户界面流中的Internet,则会使应用程序崩溃。


并发执行:流


查看图片


现在的逻辑问题是如何创建新线程并并行工作?
我们同时展示了流的创建及其并行操作的事实。 让我们创建一个流并用相同的任务加载它,因此,对于habdler,我们将其留给空闲接口10秒钟。


查看代码
 Thread thread = new Thread() { @Override public void run() { try { sendMsgUsingBroadcast("Thread started"); long initTime = System.currentTimeMillis(); boolean timeElapsed = false; while(!timeElapsed){ if(System.currentTimeMillis() - initTime > 10000 ){ timeElapsed = true; } } sendMsgUsingBroadcast("Thread stopped"); } catch (Exception e) { sendMsgUsingBroadcast("Thread error " + e.toString()); } } }; thread.start(); 

创建,加载-接口工作! 线程的执行确实是并行发生的。
下一个问题是如何控制流量。 重要的是要了解,线程只有在其所有源代码都执行完后才会完成。 您不能只是夺走河流。 现在该考虑一下shared_prefs并将该变量应用于同步了:让running = true变量与流的开始一起设置。 在其每次迭代中,线程都检查是否正在执行running == true,如果不是,则完成其执行。


查看代码
 Thread thread = new Thread() { @Override public void run() { try { sendMsgUsingBroadcast("Thread started"); SharedPreferences prefs = getSharedPreferences(MY_PREFS_NAME, MODE_PRIVATE); while (true) { if (!prefs.getBoolean("running", false)) { break; } else { try { Thread.sleep(100); } catch (Exception e) {} } } sendMsgUsingBroadcast("Thread stopped"); } catch (Exception e) { sendMsgUsingBroadcast("Thread error " + e.toString()); } } }; thread.start(); 

因此,我们进一步走了几步,现在我们不仅了解服务,而且了解处理程序和流。 这些工具似乎足以开始编写具有常规用户通知功能之一的应用程序。 但是它们具有一个特征:我们都不会失控一秒钟,因此所有人都团结在一起,并且总是被迫处于一种无休止的循环中,这种循环在大多数时间都处于休眠状态,偶尔会醒来检查是否满足一些条件,以便理解-推断用户通知或长时间入睡。 这更令人头疼:如果我们的服务或应用程序终止了电池优化程序,又为什么要花那么多不必要的小哨子来吞噬这么多智能手机资源呢? , , , - , ?


: AlarmManager


AlarmManager.



, — sendBroadcast Action, BroadcastReceiver!


BroadcastReceiver, , , :


 public class MyAlarmServiceReceiver extends BroadcastReceiver { private String CHANNEL_ID = "MyNotificationsChannel"; @Override public void onReceive(Context context, Intent intent) { Toast.makeText(context, "MyAlarmServiceReceiver onReceive", Toast.LENGTH_SHORT).show(); setNotify("Notify from AlarmManager", context); } } 

AlarmManager Receiver 15 :


 AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); Intent intent = new Intent(AlarmActivity.this, MyAlarmServiceReceiver.class); PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(System.currentTimeMillis()); calendar.set(Calendar.HOUR_OF_DAY, 1); calendar.set(Calendar.MINUTE, 10); //alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, 1 * 60 * 1000, pendingIntent); // not repeating - just one run, if needed alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), AlarmManager.INTERVAL_FIFTEEN_MINUTES, pendingIntent); 

: 15 .


( ) .




Logcat Reader , .
( ) , .


 String LOG_TAG = "MYDEMOAPP"; Log.i(LOG_TAG, "Test info log"); Log.d(LOG_TAG, "Test debug log"); Log.e(LOG_TAG, "Test error log"); 

, , MainActivity, , .





结论


, Android. , , , : , . , , - Android-.

Source: https://habr.com/ru/post/zh-CN483878/


All Articles