Unity与Android通信的中间件
2.1.1 Fragment和Activity都需要实现的接口——IBaseView
/**
* Description:Basic interface of all {@link Activity}
* or
* {@link Fragment}
* or
* {@link android.app.Fragment}
* <p>
* Creator:yankebin
* CreatedAt:2018/12/18
*/
public interface IBaseView {
/**
* Return the layout resource
*
* @return Layout Resource
*/
@LayoutRes
int contentViewLayoutId();
/**
* Call after {@link Activity#onCreate(Bundle)}
* or
* {@link Fragment#onCreateView(LayoutInflater, ViewGroup, Bundle)}
* or
* {@link android.app.Fragment#onCreateView(LayoutInflater, ViewGroup, Bundle)}
*
* @param params
* @param contentView
*/
void onViewCreated(@NonNull Bundle params, @NonNull View contentView);
/**
* Call after
* {@link Activity#onDestroy()} (Bundle)}
* or
* {@link Fragment#onDestroyView()}
* or
* {@link android.app.Fragment#onDestroyView()}
*/
void onVieDestroyed();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
这个类很简单的,只是统一下Activity、Fragment、v4包的Fragment的一些抽象方法,方便封装BaseActivity、BaseFragment、BaseFragmentV4。比如contentViewLayoutId方法,就是让开发者可以直接返回布局的id,而不用自己去写加载布局的代码。
2.1.2 显示Unity端视图的模块的基类——BaseActivity、BaseFragment、BaseFragmentV4
/**
* Description:Activity基类
* Creator:yankebin
* CreatedAt:2018/12/18
*/
public abstract class BaseActivity extends AppCompatActivity implements IBaseView {
protected View mainContentView;
protected Unbinder unbinder;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mainContentView = getLayoutInflater().inflate(contentViewLayoutId(),
(ViewGroup) getWindow().getDecorView(), false);
setContentView(mainContentView);
unbinder = ButterKnife.bind(this, mainContentView);
Bundle bundle = getIntent().getExtras();
if (null == bundle) {
bundle = new Bundle();
}
if (null != savedInstanceState) {
bundle.putAll(savedInstanceState);
}
onViewCreated(bundle, mainContentView);
}
@Override
protected void onDestroy() {
onVieDestroyed();
if (null != unbinder) {
unbinder.unbind();
}
mainContentView = null;
super.onDestroy();
}
}
/**
* Description:app包下Fragment作为基类封装
* Creator:yankebin
* CreatedAt:2018/12/18
*/
@Deprecated
public abstract class BaseFragment extends Fragment implements IBaseView {
protected Unbinder unbinder;
protected View mainContentView;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
if (null == mainContentView) {
mainContentView = inflater.inflate(contentViewLayoutId(), container, false);
unbinder = ButterKnife.bind(this, mainContentView);
Bundle bundle = getArguments();
if (null == bundle) {
bundle = new Bundle();
}
if (null != savedInstanceState) {
bundle.putAll(savedInstanceState);
}
onViewCreated(bundle, mainContentView);
} else {
unbinder = ButterKnife.bind(this, mainContentView);
}
return mainContentView;
}
@Override
public void onDetach() {
mainContentView = null;
super.onDetach();
}
@Override
public void onDestroyView() {
onVieDestroyed();
if (null != unbinder) {
unbinder.unbind();
}
if (mainContentView != null && mainContentView.getParent() != null) {
((ViewGroup) mainContentView.getParent()).removeView(mainContentView);
}
super.onDestroyView();
}
}
/**
* Description:V4包下Fragment作为基类封装
* Creator:yankebin
* CreatedAt:2018/12/18
*/
public abstract class BaseFragmentV4 extends Fragment implements IBaseView {
protected Unbinder unbinder;
protected View mainContentView;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
if (null == mainContentView) {
mainContentView = inflater.inflate(contentViewLayoutId(), container, false);
unbinder = ButterKnife.bind(this, mainContentView);
Bundle bundle = getArguments();
if (null == bundle) {
bundle = new Bundle();
}
if (null != savedInstanceState) {
bundle.putAll(savedInstanceState);
}
onViewCreated(bundle, mainContentView);
} else {
unbinder = ButterKnife.bind(this, mainContentView);
}
return mainContentView;
}
@Override
public void onDetach() {
mainContentView = null;
super.onDetach();
}
@Override
public void onDestroyView() {
onVieDestroyed();
if (null != unbinder) {
unbinder.unbind();
}
if (mainContentView != null && mainContentView.getParent() != null) {
((ViewGroup) mainContentView.getParent()).removeView(mainContentView);
}
super.onDestroyView();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
这三个类主要是简化了开发者加载布局相关的代码,以及简化生命周期回调的方法数量,让开发者只关心该关注的生命周期回调。
2.1.3 显示Unity端视图的模块的基类进一步封装——UnityPlayerActivity、UnityPlayerFragment、UnityPlayerFragmentV4
UnityPlayerActivity:
/**
* Desription:Base Unity3D Player Activity.
* <BR/>
* If you want to implement Fragment to load the Unity scene, then the Fragment needs to refer to {@link UnityPlayerFragment} and {@link UnityPlayerFragmentV4}
* To implement, then overload the {@link #generateIOnUnity3DCallDelegate(UnityPlayer, Bundle)} method to return the conforming Fragment.
* <BR/>
* Creator:yankebin
* <BR/>
* CreatedAt:2018/12/1
*/
public abstract class UnityPlayerActivity extends BaseActivity implements IGetUnity3DCall, IOnUnity3DCall, IUnityPlayerContainer {
protected UnityPlayer mUnityPlayer; // don't change the name of this variable; referenced from native code
protected IOnUnity3DCall mOnUnity3DCallDelegate;
@Override
@CallSuper
public void onViewCreated(@NonNull Bundle params, @NonNull View contentView) {
initUnityPlayer(params);
}
/**
* Initialize Unity3D related
*
* @param bundle {@link Bundle}
*/
protected void initUnityPlayer(@NonNull Bundle bundle) {
mUnityPlayer = new UnityPlayer(this);
mOnUnity3DCallDelegate = generateIOnUnity3DCallDelegate(mUnityPlayer, bundle);
if (null != mOnUnity3DCallDelegate) {
if (mOnUnity3DCallDelegate instanceof Fragment) {
getSupportFragmentManager().beginTransaction().replace(unityPlayerContainerId(),
(Fragment) mOnUnity3DCallDelegate, ((Fragment) mOnUnity3DCallDelegate)
.getClass().getName()).commit();
} else if (mOnUnity3DCallDelegate instanceof android.app.Fragment) {
getFragmentManager().beginTransaction().replace(unityPlayerContainerId(),
(android.app.Fragment) mOnUnity3DCallDelegate, ((android.app.Fragment) mOnUnity3DCallDelegate)
.getClass().getName()).commit();
} else if (mOnUnity3DCallDelegate instanceof View) {
final ViewGroup unityContainer = findViewById(unityPlayerContainerId());
unityContainer.addView((View) mOnUnity3DCallDelegate);
} else {
throw new IllegalArgumentException("Not support type : " + mOnUnity3DCallDelegate.toString());
}
} else {
mOnUnity3DCallDelegate = this;
final ViewGroup unityContainer = findViewById(unityPlayerContainerId());
unityContainer.addView(mUnityPlayer);
mUnityPlayer.requestFocus();
}
}
@Override
protected void onNewIntent(Intent intent) {
// To support deep linking, we need to make sure that the client can get access to
// the last sent intent. The clients access this through a JNI api that allows them
// to get the intent set on launch. To update that after launch we have to manually
// replace the intent with the one caught here.
setIntent(intent);
}
// Quit Unity
@Override
protected void onDestroy() {
if (null != mUnityPlayer) {
mUnityPlayer.quit();
}
super.onDestroy();
}
// Pause Unity
@Override
protected void onPause() {
super.onPause();
if (null != mUnityPlayer) {
mUnityPlayer.pause();
}
}
// Resume Unity
@Override
protected void onResume() {
super.onResume();
if (null != mUnityPlayer) {
mUnityPlayer.resume();
}
}
@Override
protected void onStart() {
super.onStart();
if (null != mUnityPlayer) {
mUnityPlayer.start();
}
}
@Override
protected void onStop() {
super.onStop();
if (null != mUnityPlayer) {
mUnityPlayer.stop();
}
}
// Low Memory Unity
@Override
public void onLowMemory() {
super.onLowMemory();
if (null != mUnityPlayer) {
mUnityPlayer.lowMemory();
}
}
// Trim Memory Unity
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
if (level == TRIM_MEMORY_RUNNING_CRITICAL) {
if (null != mUnityPlayer) {
mUnityPlayer.lowMemory();
}
}
}
// This ensures the layout will be correct.
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (null != mUnityPlayer) {
mUnityPlayer.configurationChanged(newConfig);
}
}
// Notify Unity of the focus change.
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (null != mUnityPlayer) {
mUnityPlayer.windowFocusChanged(hasFocus);
}
}
// For some reason the multiple keyevent type is not supported by the ndk.
// Force event injection by overriding dispatchKeyEvent().
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_MULTIPLE) {
if (null != mUnityPlayer) {
return mUnityPlayer.injectEvent(event);
}
}
return super.dispatchKeyEvent(event);
}
// Pass any events not handled by (unfocused) views straight to UnityPlayer
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
return super.onKeyUp(keyCode, event);
}
if (null != mUnityPlayer) {
return mUnityPlayer.injectEvent(event);
}
return super.onKeyUp(keyCode, event);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
return super.onKeyDown(keyCode, event);
}
if (null != mUnityPlayer) {
return mUnityPlayer.injectEvent(event);
}
return super.onKeyDown(keyCode, event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (null != mUnityPlayer) {
return mUnityPlayer.injectEvent(event);
}
return super.onTouchEvent(event);
}
/*API12*/
public boolean onGenericMotionEvent(MotionEvent event) {
if (null != mUnityPlayer) {
return mUnityPlayer.injectEvent(event);
}
return super.onGenericMotionEvent(event);
}
@Nullable
@Override
public Context gatContext() {
return this;
}
@NonNull
@Override
public IOnUnity3DCall getOnUnity3DCall() {
//Perhaps this method is called after Unity is created after the activity is created,
// so there is no problem for the time being.
return mOnUnity3DCallDelegate;
}
@Nullable
protected IOnUnity3DCall generateIOnUnity3DCallDelegate(@NonNull UnityPlayer unityPlayer,
@Nullable Bundle bundle) {
return null;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
别看这个类有200多行,其实绝大部分代码都是重载,是为了满足Unity端的需求,真正要关注的方法就那么三四个,只有generateIOnUnity3DCallDelegate(@NonNull UnityPlayer unityPlayer,@Nullable Bundle bundle)这个方法需要继承的实现者去关注,这个方法的作用就是生成真正去加载和显示Unity端使视图的模块,不关你是View也好,Fragment也好,只要你实现了IOnUnity3DCall接口和IUnityPlayerContainer接口,你就可以加载和显示Unity端的视图。
默认情况下,继承至UnityPlayerActivity的类就是加载和显示Unity端视图的模块,除非你重写generateIOnUnity3DCallDelegate(@NonNull UnityPlayer unityPlayer,@Nullable Bundle bundle)方法,返回合适的代理,这个可以从initUnityPlayer(@NonNull Bundle bundle) 方法里面直观的看出来。
如果你还想再进一步,实现了IOnUnity3DCall接口和IUnityPlayerContainer接口的代理模块还想让其他模块来显示Unity短的View,那么实现了IOnUnity3DCall接口和IUnityPlayerContainer接口的代理模块就可以在实现IGetUnity3DCall接口,重写generateIOnUnity3DCallDelegate(@NonNull UnityPlayer unityPlayer,@Nullable Bundle bundle)方法,返回合适的代理。当然,可能大多数情况下我们也不需要这么做吧。
基于以上的原理,要让Fragment作为显示Unity短的View的模块的方法就呼之欲出了:
v4包下的Fragment
/**
* Description:The base Unity3D fragment, the Activity holding this Fragment needs to implement the {@link IGetUnity3DCall} interface and implement {@link IGetUnity3DCall#getOnUnity3DCall()}
* 方法返回此Fragment的实例
* Creator:yankebin
* CreatedAt:2018/12/1
*/
public abstract class UnityPlayerFragmentV4 extends BaseFragmentV4 implements IOnUnity3DCall, IUnityPlayerContainer {
protected UnityPlayer mUnityPlayer; // don't change the name of this variable; referenced from native code
public void setUnityPlayer(@NonNull UnityPlayer mUnityPlayer) {
this.mUnityPlayer = mUnityPlayer;
}
@Override
@CallSuper
public void onViewCreated(@NonNull Bundle params, @NonNull View contentView) {
if (null != mUnityPlayer) {
final ViewGroup unityContainer = contentView.findViewById(unityPlayerContainerId());
unityContainer.addView(mUnityPlayer);
mUnityPlayer.requestFocus();
}
}
@Nullable
@Override
public Context gatContext() {
return getActivity();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
app包下的Fragment
/**
* Description:The base Unity3D fragment, the Activity holding this Fragment needs to implement the {@link IGetUnity3DCall} interface and implement {@link IGetUnity3DCall#getOnUnity3DCall()}
* Method returns an instance of this Fragment
* Creator:yankebin
* CreatedAt:2018/12/1
*/
public abstract class UnityPlayerFragment extends BaseFragment implements IOnUnity3DCall, IUnityPlayerContainer {
protected UnityPlayer mUnityPlayer; // don't change the name of this variable; referenced from native code
public void setUnityPlayer(@NonNull UnityPlayer mUnityPlayer) {
this.mUnityPlayer = mUnityPlayer;
}
@Override
@CallSuper
public void onViewCreated(@NonNull Bundle params, @NonNull View contentView) {
if (null != mUnityPlayer) {
final ViewGroup unityContainer = contentView.findViewById(unityPlayerContainerId());
unityContainer.addView(mUnityPlayer);
mUnityPlayer.requestFocus();
}
}
@Nullable
@Override
public Context gatContext() {
return getActivity();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
两者的实现完全一致,只是为了让开发者少封装一种Fragment。我想,当你们看到这里的时候,心中对如何让一个ViewGroup或者Dialog显示Unity端的View的方法已经很清晰了吧。
---------------------