UE UPL调研
Posted 东东7_7
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UE UPL调研相关的知识,希望对你有一定的参考价值。
UPL调研
基本思路:
使用UPL中的xml语言,使用他来介入安卓的打包过程,将我们需要的功能添加到UE工程中
UPL
定义:UPL 全称 Unreal Plugin Language,主要的作用是参加ue的打包过程
理解:UPL对应的就是安卓工程中的activty_main.xml(找个文件的补充),它规定了安卓包构建的方式和顺序
所以我们在开发的过程中,可以通过UPL在Gameactivty增加我们写的java代码来保证使用C++来在安卓的环境下来使用java的代码
<!-- 导入对象到 GameActivity.java -->
<gameActivityImportAdditions>
<insert>
import android.net.Uri;
import android.net.ConnectivityManager;
</insert>
</gameActivityImportAdditions>
<!-- 导入 java方法到 GameActivity.java -->
<gameActivityClassAdditions> </gameActivityClassAdditions>
<!-- 增加 代码到 OnCreateAdditions函数 -->
<gameActivityOnCreateAdditions></gameActivityOnCreateAdditions>
通过这些方法就可以将代码增加到Gameactivity.java,这些具体的定义可以参考
UnrealBuildTool/System/UnrealPluginLanguage.csUE的源码中的注释
Java函数签名
JNI:JNI全称Java Native Interface,及java原生接口,主要用于jave调用其他语言代码,其他语言调用java代码。
我们通过C++ 去java的方法,通常是使用函数签名来获取Java中的函数,函数签名定义了函数的名称,返回值和参数类型
public int AndroidThunkJava_GetCurCpu()
int result = 0;
FileReader fr = null;
BufferedReader br = null;
比如上面的这个java函数,他的函数签名就为 () I;,其中括号中为参数列表,I表示返回值为int
他的签名计算是有一个规则,可以参考下表
- String:Ljava/lang/String;
- Object:Ljava/lang/Object;
以上两条为补充内容,他不在基本类型中,所以为特殊表示
public string AndroidThunkJava_GetCurCpu(int val)
//他的函数签名为 (I)Ljava/lang/String;
JNI:Java to C++
UE 给我们的游戏生成的 GameActivity 中也声明了很多的 native 函数,这些函数是在 C++ 实现的,在 Java 中执行到这些函数会自动调用到引擎的 C++ 代码中:
public native int nativeGetCPUFamily();
public native boolean nativeSupportsNEON();
public native void nativeSetAffinityInfo(boolean bEnableAffinity, int bigCoreMask, int littleCoreMask);
public native void nativeSetConfigRulesVariables(String[] KeyValuePairs);
public native boolean nativeIsShippingBuild();
public native void nativeSetAndroidStartupState(boolean bDebuggerAttached);
public native void nativeSetGlobalActivity(boolean bUseExternalFilesDir, boolean bPublicLogFiles, String internalFilePath, String externalFilePath, boolean bOBBInAPK, String APKPath);
public native void nativeSetObbFilePaths(String OBBMainFilePath, String OBBPatchFilePath);
public native void nativeSetWindowInfo(boolean bIsPortrait, int DepthBufferPreference);
public native void nativeSetObbInfo(String ProjectName, String PackageName, int Version, int PatchVersion, String AppType);
public native void nativeSetAndroidVersionInformation( String AndroidVersion, String PhoneMake, String PhoneModel, String PhoneBuildNumber, String OSLanguage );
public native void nativeSetSurfaceViewInfo(int width, int height);
public native void nativeSetSafezoneInfo(boolean bIsPortrait, float left, float top, float right, float bottom);
public native void nativeConsoleCommand(String commandString);
public native void nativeVirtualKeyboardChanged(String contents);
public native void nativeVirtualKeyboardResult(boolean update, String contents);
public native void nativeVirtualKeyboardSendKey(int keyCode);
public native void nativeVirtualKeyboardSendTextSelection(String contents, int selStart, int selEnd);
public native void nativeVirtualKeyboardSendSelection(int selStart, int selEnd);
public native void nativeInitHMDs();
public native void nativeResumeMainInit();
public native void nativeOnActivityResult(GameActivity activity, int requestCode, int resultCode, Intent data);
public native void nativeGoogleClientConnectCompleted(boolean bSuccess, String accessToken);
public native void nativeVirtualKeyboardShown(int left, int top, int right, int bottom);
public native void nativeVirtualKeyboardVisible(boolean bShown);
public native void nativeOnConfigurationChanged(boolean bPortrait);
public native void nativeOnInitialDownloadStarted();
public native void nativeOnInitialDownloadCompleted();
public native void nativeHandleSensorEvents(float[] tilt, float[] rotation_rate, float[] gravity, float[] acceleration);
简单例子
在java中声明一个简单函数
<gameActivityClassAdditions>
<insert>
public native void nativeDoTester(String Msg);
</insert>
</gameActivityClassAdditions>
在C++代码的任何位置去实现这个代码就好
#if PLATFORM_ANDROID
JNI_METHOD void Java_com_epicgames_unreal_GameActivity_nativeDoTester
(JNIEnv* jenv, jobject thiz, jstring msg)
if (JNIEnv* Env = FAndroidApplication::GetJavaEnv())
FString FinalResult = FJavaHelper::FStringFromLocalRef(Env, msg);
MyFunc::Name = FinalResult;
com.epicgames_unreal 是 UE 生成的 GameActivity.java 的包名 (package com.epicgames_unreal)。
可以看到,在 C++ 中实现 JNIMETHOD 的函数名是以下规则:.
RType Java_PACKAGENAME_CLASSNAME_FUNCNAME(JNIEnv*,jobject thiz,Oher…)
JNI:C++ to Java
通过上面的转化,我们知道了怎么通过函数签名来找到java代码,我们在UE中就可以通过函数名和函数签名来在C++中调用java
其中通常用到的为下面三个头文件
// Runtime/Launch/Public/Android
#include "Android/AndroidJNI.h"
// Runtime/Core/Public/Android
#include "Android/AndroidJavaEnv.h"
// Runtime/Core/Public/Android
#include "Android/AndroidJava.h"
想要在 UE 中调用到它,首先要获取它的 jmethodID,需要通过函数所属的类、函数名字,签名三种信息来获取:
if (JNIEnv* Env = FAndroidApplication::GetJavaEnv())
//通过FindMethod来获取他的jmethodID ,其中需要填类名,函数名字,签名
static jmethodID Method = FJavaWrapper::FindMethod(Env, FJavaWrapper::GameActivityClassID, "AndroidThunkJava_GetCurCpu", "()I", false);
//找到jmethodID 后,通过CallIntMethod就可以调用到函数
jint Result = FJavaWrapper::CallIntMethod(Env, FJavaWrapper::GameActivityThis, Method);
return Result;
注:当返回值为string时还需要转化为UE的字符串才可以使用
FString FinalResult = FJavaHelperEx::FStringFromLocalRef(Env,JstringResult);
遇到的问题
- 找不到头文件 #include “Android/AndroidJNI.h”
解决:需要在bulid.cs中添加
if (Target.Platform == UnrealTargetPlatform.Android)
PrivateDependencyModuleNames.AddRange(new string[] "Launch", "AndroidPermission" );
string PluginPath = Utils.MakePathRelativeTo(ModuleDirectory, Target.RelativeEnginePath);
AdditionalPropertiesForReceipt.Add("AndroidPlugin", Path.Combine(PluginPath, "Android_clik_APL.xml"));
-
直接在VS平台编译JNI代码无法通过编译
解决:需要在编译时增加预处理宏,保证在安卓平台时才可以运行
#if PLATFORM_ANDROID
#endif -
导入对象出现错误
解决:
如果有如下错误:提示下面两个包找不到
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
可以改为:
import androidx.core.app.ActivityCompat;
import androidx.appcompat.app.AppCompatActivity;
这里库找不到可以替换为
import android.support.annotation.
替换为
import androidx.annotation;
4. 示例代码的修改,以下代码的删除,这里会导致软件闪退
<gameActivityOnPauseAdditions>
<insert>
android.util.Log.d("UE4", "onPause");
nativeUELog("onPause");
</insert>
</gameActivityOnPauseAdditions>
<gameActivityOnResumeAdditions>
<insert>
android.util.Log.d("UE4", "onResume");
nativeUELog("onResume");
</insert>
</gameActivityOnResumeAdditions>
功能实现效果
通过UMG在屏幕生成按钮,点击按钮后触发事件,调用JAVA函数,获取到CPU数值,并显示在屏幕右上角
java中native方法
定义:他的作用就是一个java调用非java接口的方法,他只是在java中声明,而具体的实现是在C语言中实现,其实很类似于C++中的extern c,是编译器的一种方法
public class IHaveNatives
native public void Native1( int x ) ;
native static public long Native2() ;
native synchronized private float Native3( Object o ) ;
native void Native4( int[] ary ) throws Exception ;
举例是这样的,native可以与其他的java标识符连用,他的暗示是这些函数是有实现的
扩展:GameActivity.java文件的学习
1.1 安卓项目文件结构
activity定义:用户看得见摸的着的是手机屏幕。我们要在手机屏幕上显示文字图像等信息,并对用户的点击滑动等等操作作出不同的反应。 App中与用户交互的大任由Activity来承担。当用户手指点击手机屏幕时,Android系统检测到屏幕发生的事情,将这一事件分发对应的App处理。这里要注意,activity接收到的是系统给的信息。系统会判断这些交互消息该给哪个app来处理。
入口函数:public class GameActivity extends NativeActivity
在gameActivity他的入口类为GameActivity ,是继承自NativeActivity,也就是他的页面
生命周期:
- Oncreate 布局的初始化
- Onstart 启动的时候的状态
- Onresume 渲染的完成
- Onpause 暂停后去停止
- Onstop 回到桌面
- Onresart 重新开始
同时在UE的UPL中也提供相应的接口供我们使用,在其中添加代码,下面是一些举例
* <!-- optional additions to GameActivity onCreate in GameActivity.java -->
* <gameActivityOnCreateAdditions> </gameActivityOnCreateAdditions>
*
* <!-- optional additions to GameActivity onDestroy in GameActivity.java -->
* <gameActivityOnDestroyAdditions> </gameActivityOnDestroyAdditions>
*
* <!-- optional additions to GameActivity onConfigurationChanged in GameActivity.java -->
* <gameActivityonConfigurationChangedAdditions> </gameActivityonConfigurationChangedAdditions>
*
* <!-- optional additions to GameActivity onStart in GameActivity.java -->
* <gameActivityOnStartAdditions> </gameActivityOnStartAdditions>
*
* <!-- optional additions to GameActivity onStop in GameActivity.java -->
* <gameActivityOnStopAdditions> </gameActivityOnStopAdditions>
*
* <!-- optional additions to GameActivity onPause in GameActivity.java -->
* <gameActivityOnPauseAdditions> </gameActivityOnPauseAdditions>
*
* <!-- optional additions to GameActivity onResume in GameActivity.java -->
* <gameActivityOnResumeAdditions> </gameActivityOnResumeAdditions>
*
2.1 Activity 启动,携带参数启动
通常activity离不开intent这个类,通常把信息包含在intent对象中,然后执行启动,常用的方法是 startActivity (Intent intent) ,我们在activity中通信的过程中的信息就可以听过intent这个类去传递
//实现一个简单的跳转
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(getApplicationContext(),myActivity2.class);
intent.putExtra("13",100);
startActivities(new Intent[]intent);
2.2 任务,返回栈
任务指的是在执行特定作业的时候与用户交互的一系列Activity,这些Activity按照各组打开顺序排列在堆栈中.
2.3 Avtivity的四种模式
2.4 Service综述
service是一种在后台执行,长时间运行操作而不提供界面的应用组件,服务可由其他组件启动,就是用户在切换到其他应用的时候,服务仍在后台运行
2.4.1 前台,后台服务与绑定
前台:前台服务执行一些用户能注意到的操作。例如,音频应用会使用前台服务来播放音频曲目。前台服务必 须显示通知。 即使用户停止与应用的交互,前台服务仍会继续运行
后台:后台服务执行用户不会直接注意到的操作。例如,如果应用使用某个服务来压缩其存储空间,则此服务通常是后台服务。
绑定服务:当应用组件通过调用 bindService() 绑定到服务时,服务即处于绑定状态。 绑定服务会提供客户端-服务器接口,以便组件与服务进行交互、发送请求、接收结果,甚至是利用进程间通信 (IPC) 跨进程执行,这些操作仅当与另一个应用组件绑定时,绑定服务才会运行。多个组件可同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。
2.4.2 启动服务
startService(Intent(applicationContext, ServiceStartDemo::class.java))
调用方法后,ServiceStartDemo服务会启动起来。 首次启动的话,服务会走 onCreate 和
onStartCommand 方法。 初始化性质的代码,放在 onCreate 。
2.4.3 停止前台服务
在Service中调用 stopForeground(boolean) 方法,能停止前台,但是不退出整个服务。 这个boolean
表示是否取消掉前台服务的通知。false表示保留通知。
3.1 Fragment基础概念
定义:Fragment直译为碎片,Fragment表示FragmentActivity中行为或界面的一部分,你可以在Activity中组合多个片段,从而构建多窗格界面,其实可以理解为一个子Activity,但是片段必须始终托管在Activity中,他的生命周期也直接受Activity的影响
Fragment的优点
- Fragment加载灵活,替换方便。定制你的UI,在不同尺寸的屏幕上创建合适的UI,提高用户体
验。 - 可复用,页面布局可以使用多个Fragment,不同的控件和内容可以分布在不同的Fragment上。
- 使用Fragment,可以少用一些Activity。一个Activity可以管辖多个Fragment。
3.2 fragment的生命周期
使用DialogFragment来完成一个弹窗的功能
- 在onCreate方法中接收传入的数据。传递数据使用了Bundle
- 在onCreateView方法中,使用上文建立的layout
- 在onViewCreated方法中进行ui操作
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
public class SimpleDialog extends DialogFragment
public static final String K_TITLE = "k_title"; // 传输数据时用到的key
public static final String K_CONTENT = "k_content";
private String title;
private String content;
@Override
public void onCreate(@Nullable Bundle savedInstanceState)
super.onCreate(savedInstanceState);
Bundle in = getArguments();
if (in != null)
title = in.getString(K_TITLE);
content = in.getString(K_CONTENT);
//作用是将片段布局插入到父级viewGrroup中
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)
return inflater.inflate(R.layout.dialog_simple, container, false);
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState)
super.onViewCreated(view, savedInstanceState);
TextView titleTv = view.findViewById(R.id.title_tv);
TextView contentTv = view.findViewById(R.id.content_tv);
titleTv.setText(title);
contentTv.setText(content);
定义的layout文件如下
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="12dp">
<TextView
android:id="@+id/title_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="#111111"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/content_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:gravity="center"
android:textColor="#111111"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title_tv" />
</androidx.constraintlayout.widget.ConstraintLayout>
最后在activity中调用
public class MainActivity extends AppCompatActivity
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
popSimpleDialog1("123", "test");
private void popSimpleDialog1(String title, String content)
SimpleDialog dialog = new SimpleDialog();
Bundle bundle = new Bundle();
bundle.putString(SimpleDialog.K_TITLE, title);
bundle.putString(SimpleDialog.K_CONTENT, content);
dialog.setArguments(bundle);
//getSupportFragmentManager为获取FragmentTransaction实例
dialog.show(getSupportFragmentManager(), "one-tag");
最终实现的效果如下图所示
java中native方法
定义:他的作用就是一个java调用非java接口的方法,他只是在java中声明,而具体的实现是在C语言中实现,其实很类似于C++中的extern c,是编译器的一种方法
public class IHaveNatives
native public void Native1( int x ) ;
native static public long Native2() ;
native synchronized private float Native3( Object o ) ;
native void Native4( int[] ary ) throws Exception ;
扩展:GameActivity.java文件的学习
1.1 安卓项目文件结构
activity定义:用户看得见摸的着的是手机屏幕。我们要在手机屏幕上显示文字图像等信息,并对用户的点击滑动等等操作作出不同的反应。 App中与用户交互的大任由Activity来承担。当用户手指点击手机屏幕时,Android系统检测到屏幕发生的事情,将这一事件分发对应的App处理。这里要注意,activity接收到的是系统给的信息。系统会判断这些交互消息该给哪个app来处理。
入口函数:public class GameActivity extends NativeActivity
在gameActivity他的入口类为GameActivity ,是继承自NativeActivity,也就是他的页面
生命周期:
- Oncreate 布局的初始化
- Onstart 启动的时候的状态
- Onresume 渲染的完成
- Onpause 暂停后去停止
- Onstop 回到桌面
- Onresart 重新开始
同时在UE的UPL中也提供相应的接口供我们使用,在其中添加代码,下面是一些举例
* <!-- optional additions to GameActivity onCreate in GameActivity.java -->
* <gameActivityOnCreateAdditions> </gameActivityOnCreateAdditions>
*
* <!-- optional additions to GameActivity onDestroy in GameActivity.java -->
* <gameActivityOnDestroyAdditions> </gameActivityOnDestroyAdditions>
*
* <!-- optional additions to GameActivity onConfigurationChanged in GameActivity.java -->
* <gameActivityonConfigurationChangedAdditions> </gameActivityonConfigurationChangedAdditions>
*
* <!-- optional additions to GameActivity onStart in GameActivity.java -->
* <gameActivityOnStartAdditions> </gameActivityOnStartAdditions>
*
* <!-- optional additions to GameActivity onStop in GameActivity.java -->
* <gameActivityOnStopAdditions> </gameActivityOnStopAdditions>
*
* <!-- optional additions to GameActivity onPause in GameActivity.java -->
* <gameActivityOnPauseAdditions> </gameActivityOnPauseAdditions>
*
* <!-- optional additions to GameActivity onResume in GameActivity.java -->
* <gameActivityOnResumeAdditions> </gameActivityOnResumeAdditions>
*
2.1 Activity 启动,携带参数启动
通常activity离不开intent这个类,通常把信息包含在intent对象中,然后执行启动,常用的方法是 startActivity (Intent intent) ,我们在activity中通信的过程中的信息就可以听过intent这个类去传递
//实现一个简单的跳转
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(getApplicationContext(),myActivity2.class);
intent.putExtra("wdf",100);
startActivities(new Intent[]intent);
2.2 任务,返回栈
任务指的是在执行特定作业的时候与用户交互的一系列Activity,这些Activity按照各组打开顺序排列在堆栈中.
2.3 Avtivity的四种模式
2.4 Service综述
service是一种在后台执行,长时间运行操作而不提供界面的应用组件,服务可由其他组件启动,就是用户在切换到其他应用的时候,服务仍在后台运行
2.4.1 前台,后台服务与绑定
**前台:**前台服务执行一些用户能注意到的操作。例如,音频应用会使用前台服务来播放音频曲目。前台服务必 须显示通知。 即使用户停止与应用的交互,前台服务仍会继续运行
**后台:**后台服务执行用户不会直接注意到的操作。例如,如果应用使用某个服务来压缩其存储空间,则此服务通常是后台服务。
**绑定服务:**当应用组件通过调用 bindService() 绑定到服务时,服务即处于绑定状态。 绑定服务会提供客户端-服务器接口,以便组件与服务进行交互、发送请求、接收结果,甚至是利用进程间通信 (IPC) 跨进程执行,这些操作仅当与另一个应用组件绑定时,绑定服务才会运行。多个组件可同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。
2.4.2 启动服务
startService(Intent(applicationContext, ServiceStartDemo::class.java))
调用方法后,ServiceStartDemo服务会启动起来。 首次启动的话,服务会走 onCreate 和
onStartCommand 方法。 初始化性质的代码,放在 onCreate 。
2.4.3 停止前台服务
在Service中调用 stopForeground(boolean) 方法,能停止前台,但是不退出整个服务。 这个boolean
表示是否取消掉前台服务的通知。false表示保留通知。
3.1 Fragment基础概念
定义:Fragment直译为碎片,Fragment表示FragmentActivity中行为或界面的一部分,你可以在Activity中组合多个片段,从而构建多窗格界面,其实可以理解为一个子Activity,但是片段必须始终托管在Activity中,他的生命周期也直接受Activity的影响
Fragment的优点
- Fragment加载灵活,替换方便。定制你的UI,在不同尺寸的屏幕上创建合适的UI,提高用户体
验。 - 可复用,页面布局可以使用多个Fragment,不同的控件和内容可以分布在不同的Fragment上。
- 使用Fragment,可以少用一些Activity。一个Activity可以管辖多个Fragment。
3.2 fragment的生命周期
使用DialogFragment来完成一个弹窗的功能
- 在onCreate方法中接收传入的数据。传递数据使用了Bundle
- 在onCreateView方法中,使用上文建立的layout
- 在onViewCreated方法中进行ui操作
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
public class SimpleDialog extends DialogFragment
public static final String K_TITLE = "k_title"; // 传输数据时用到的key
public static final String K_CONTENT = "k_content";
private String title;
private String content;
@Override
public void onCreate(@Nullable Bundle savedInstanceState)
super.onCreate(savedInstanceState);
Bundle in = getArguments();
if (in != null)
title = in.getString(K_TITLE);
content = in.getString(K_CONTENT);
//作用是将片段布局插入到父级viewGrroup中
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)
return inflater.inflate(R.layout.dialog_simple, container, false);
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState)
super.onViewCreated(view, savedInstanceState);
TextView titleTv = view.findViewById(R.id.title_tv);
TextView contentTv = view.findViewById(R.id.content_tv);
titleTv.setText(title);
contentTv.setText(content);
定义的layout文件如下
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="12dp">
<TextView
android:id="@+id/title_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="#111111"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/content_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:gravity="center"
android:textColor="#111111"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title_tv" />
</androidx.constraintlayout.widget.ConstraintLayout>
public class MainActivity extends AppCompatActivity
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
popSimpleDialog1("王东方", "test");
private void popSimpleDialog1(String title, String content)
SimpleDialog dialog = new SimpleDialog();
Bundle bundle = new Bundle();
bundle.putString(SimpleDialog.K_TITLE, title);
bundle.putString(SimpleDialog.K_CONTENT, content);
dialog.setArguments(bundle);
//getSupportFragmentManager为获取FragmentTransaction实例
dialog.show(getSupportFragmentManager(), "one-tag");
最终实现的效果如下图所示
res应用资源
资源分类的视图如下把资源放进对应的目录后,可使用在项目 R 类中生成的资源ID来访问这些资源。形如 R.drawable.icon , R.layout.main_activity 。 R 类是自动生成的。代表resources。
以上是关于UE UPL调研的主要内容,如果未能解决你的问题,请参考以下文章